From bf2c191bb3ccb937bec9d2a5cfeaf66c4317786d Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 00:23:11 +1100 Subject: [PATCH 001/184] drm/apple: share panel fades and gate T6041 adaptive sync Use the shared 250 ms nits ramp on desktop 26.6 panels and account brightness only after the matching accepted present completes. Keep the loader level until a request, support zero nits, and retain the restore target across blanking and power changes. Validate internal refresh ranges without losing their fixed timings, retain fractional rates and sync polarity from TimingElements, and keep desktop presentation deadlines for fixed ProMotion. Add opt-in adaptive sync with fixed-refresh retry and watchdog recovery, leaving the established fixed-refresh default intact. Exercise ramps, real mode parsing, ranges and adaptive-sync callback failures in the host runner, and extend the backlight and mode KUnit suites. Assisted-by: LLM Signed-off-by: Ryan Murray --- Documentation/gpu/apple-t6041-panel.rst | 29 ++ drivers/gpu/drm/apple/dcp-internal.h | 11 + drivers/gpu/drm/apple/dcp-refresh.h | 36 +++ drivers/gpu/drm/apple/dcp.c | 17 +- drivers/gpu/drm/apple/dcp_backlight.c | 126 ++++++-- drivers/gpu/drm/apple/dcp_backlight.h | 49 +++ drivers/gpu/drm/apple/dcp_backlight_test.c | 36 +++ drivers/gpu/drm/apple/iomfb.c | 28 +- drivers/gpu/drm/apple/iomfb_template.c | 147 ++++++--- drivers/gpu/drm/apple/parser.c | 47 ++- drivers/gpu/drm/apple/parser_mode_test.c | 28 ++ .../selftests/drivers/gpu/apple-dcp/README | 11 + .../drivers/gpu/apple-dcp/display.py | 278 ++++++++++++++++++ .../selftests/drivers/gpu/apple-dcp/rate.py | 2 + .../selftests/drivers/gpu/apple-dcp/run.py | 7 +- 15 files changed, 760 insertions(+), 92 deletions(-) create mode 100644 Documentation/gpu/apple-t6041-panel.rst create mode 100644 drivers/gpu/drm/apple/dcp-refresh.h create mode 100644 tools/testing/selftests/drivers/gpu/apple-dcp/display.py diff --git a/Documentation/gpu/apple-t6041-panel.rst b/Documentation/gpu/apple-t6041-panel.rst new file mode 100644 index 0000000000000..ba1b9de0f4094 --- /dev/null +++ b/Documentation/gpu/apple-t6041-panel.rst @@ -0,0 +1,29 @@ +T6041 internal panel +=================== + +Desktop 26.6 firmware uses its own IOMFB instance. Panel brightness is in +integer nits from zero to the panel ceiling, with the same 250 ms fade used +by the H17P panel. The backlight interface reports the requested target and +``actual_brightness`` follows accepted, completed presents. A registration +default does not change the brightness inherited from the boot loader. +Ambient-light policy can use the normal backlight interface, just as a slider +does. Requests made while blanked or suspended preserve the restore target. + +Fixed refresh modes keep the timing and color IDs supplied by DCP. The driver +does not synthesize lower refresh modes from a 120 Hz mode. Fractional rates +remain in 16.16 Hz until the DRM pixel clock is calculated. A ProMotion mode +needs desktop presentation deadlines even with adaptive sync disabled. + +Adaptive sync is experimental and disabled by default. To qualify the internal +panel, boot with ``appledrm.t6041_vrr=1``. This enables the connector capability +only for the internal main display and a valid adaptive-sync range. The +existing internal ProMotion convention supplies 24 Hz to the mode rate when +the firmware omits both range fields; an explicit partial or invalid range +leaves the timing usable at fixed refresh. + +A rejected adaptive-sync parameter or mode selection gets one retry with +adaptive sync disabled. An adaptive swap that stops completing invokes the +swap watchdog and re-applies the active CRTC at fixed refresh. Either failure +latches adaptive sync off until reboot. A transport that stops replying cannot +be recovered by issuing another command on its busy channel; it still requires +a reboot. Use the previous kernel entry if a panel fails qualification. diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 5950b5883d28f..2f6e107fa43e8 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -228,6 +228,9 @@ enum dcp_test_point { * for such a DCP only. */ struct dcp_desktop_state { + /* Protected by backlight.lock, independent of framebuffer retirement. */ + struct dcp_backlight_swap backlight; + /* Queued swap; too large for the shared swap union. */ struct dcp_swap_submit_req_v26_6_desktop swap; /* Opaque client identity the firmware's IOMFB calls carry. */ @@ -238,6 +241,11 @@ struct dcp_desktop_state { * element before the real one is selected. */ bool mode_live; + /* An opt-in adaptive-sync failure stays fixed until the next boot. */ + bool vrr_failed; + bool vrr_requested; + u32 present_id; + bool present_pending; /* * GAMMA_LUT: the firmware's own table, read before the first change; * the table being sent; the LUT it is composed from, if any. @@ -287,6 +295,8 @@ struct dcp_desktop_state { struct dcpext_scanout; struct dcp_hdmi; +bool dcp_desktop_vrr_supported(struct apple_dcp *dcp); + struct apple_dcp { /* Generation-specific wire/transaction state, sharing the common KMS owner. */ struct iomfb_m4 *v26_6; @@ -669,6 +679,7 @@ struct apple_dcp { void (*kick)(struct apple_dcp *dcp); /* Steps a running fade; fade_start is its start in jiffies. */ struct delayed_work fade_work; + struct delayed_work retry_work; unsigned long fade_start; /* Rate limit for firmware re-send requests. */ unsigned long resent_at; diff --git a/drivers/gpu/drm/apple/dcp-refresh.h b/drivers/gpu/drm/apple/dcp-refresh.h new file mode 100644 index 0000000000000..eea5058cc3645 --- /dev/null +++ b/drivers/gpu/drm/apple/dcp-refresh.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0-only OR MIT */ +/* Copyright 2026 Ryan Murray */ +#ifndef __APPLE_DCP_REFRESH_H__ +#define __APPLE_DCP_REFRESH_H__ + +#include + +/* All rates are unsigned 16.16 Hz, including fractional fixed timings. */ +static inline bool dcp_refresh_range(u32 rate, u32 minimum, u32 maximum, + bool internal, u32 *low, u32 *high) +{ + if (!minimum && !maximum && internal && + rate >= (120U << 16) - (120U << 16) / 500 && + rate <= (120U << 16)) { + minimum = 24U << 16; + maximum = rate; + } + /* A partial, inverted or out-of-mode range leaves a fixed timing. */ + if (minimum < (1U << 16) || minimum >= maximum || + maximum > rate || maximum > (65535U << 16)) + return false; + *low = minimum; + *high = maximum; + return true; +} + +/* + * Desktop 26.6 uses its own presentation clock. A deadline long past asks + * for the next permitted refresh. ARM counter values can stall the swap. + */ +static inline u64 dcp_desktop_present_deadline(bool have_surface, bool required) +{ + return have_surface && required ? 120 : 0; +} + +#endif diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 8a2e2c5bcbf10..03815b0f86e6b 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -909,9 +909,22 @@ VISIBLE_IF_KUNIT void dcp_swap_watchdog(struct work_struct *work) container_of(to_delayed_work(work), struct apple_dcp, swap_watchdog_wq); + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && + (!READ_ONCE(dcp->desktop->present_pending) || + time_before(jiffies, dcp->swap_watchdog_deadline))) + return; + if (!iomfb_retire_dropped_present_h17p(dcp)) return; + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && + READ_ONCE(dcp->vrr_enabled)) { + WRITE_ONCE(dcp->desktop->vrr_failed, true); + WRITE_ONCE(dcp->vrr_enabled, false); + dcp_backlight_desktop_finish(dcp, + READ_ONCE(dcp->desktop->present_id), true, false); + dev_warn(dcp->dev, "adaptive swap timed out, restoring fixed refresh\n"); + } dev_warn(dcp->dev, "swap not completed, retraining the display\n"); dcp_mode_invalidate(&dcp->mode_state); dcp_drm_crtc_vblank(dcp->crtc); @@ -922,7 +935,9 @@ VISIBLE_IF_KUNIT void dcp_swap_watchdog(struct work_struct *work) void dcp_swap_watchdog_arm(struct apple_dcp *dcp) { - if (dcp_is_typec_output(dcp)) { + if (dcp_is_typec_output(dcp) || + (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && + READ_ONCE(dcp->vrr_enabled))) { dcp->swap_watchdog_deadline = jiffies + msecs_to_jiffies(DCP_SWAP_WATCHDOG_MS); mod_delayed_work(system_dfl_wq, &dcp->swap_watchdog_wq, diff --git a/drivers/gpu/drm/apple/dcp_backlight.c b/drivers/gpu/drm/apple/dcp_backlight.c index e9bc471654cde..d32f52f11890b 100644 --- a/drivers/gpu/drm/apple/dcp_backlight.c +++ b/drivers/gpu/drm/apple/dcp_backlight.c @@ -104,6 +104,18 @@ bool dcp_backlight_active(struct apple_dcp *dcp) READ_ONCE(dcp->backlight.state.ready); } +static bool dcp_backlight_desktop(struct apple_dcp *dcp) +{ + return dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && + READ_ONCE(dcp->backlight.state.ready); +} + +static void dcp_backlight_desktop_kick(struct apple_dcp *dcp) +{ + if (!READ_ONCE(dcp->quiescing) && !READ_ONCE(dcp->crashed)) + schedule_work(&dcp->bl_update_wq); +} + int dcp_backlight_takeover(struct apple_dcp *dcp, u32 millinits) { u32 maximum, nits; @@ -130,7 +142,7 @@ static int dcp_get_brightness(struct backlight_device *bd) if (READ_ONCE(dcp->quiescing)) return -ENODEV; - if (dcp_backlight_active(dcp)) { + if (dcp_backlight_active(dcp) || dcp_backlight_desktop(dcp)) { spin_lock_irqsave(&dcp->backlight.lock, flags); actual = dcp->backlight.state.actual; spin_unlock_irqrestore(&dcp->backlight.lock, flags); @@ -227,9 +239,19 @@ static void dcp_backlight_fade_work(struct work_struct *work) msecs_to_jiffies(DCP_BL_FADE_STEP_MS)); } +static void dcp_backlight_retry_work(struct work_struct *work) +{ + struct apple_dcp *dcp = container_of(to_delayed_work(work), + struct apple_dcp, backlight.retry_work); + + if (!READ_ONCE(dcp->quiescing) && !READ_ONCE(dcp->crashed)) + dcp_backlight_kick(dcp); +} + void dcp_backlight_stop(struct apple_dcp *dcp) { cancel_delayed_work_sync(&dcp->backlight.fade_work); + cancel_delayed_work_sync(&dcp->backlight.retry_work); } static void dcp_backlight_release(void *data) @@ -246,6 +268,7 @@ int dcp_backlight_init(struct apple_dcp *dcp, struct device *dev) { spin_lock_init(&dcp->backlight.lock); INIT_DELAYED_WORK(&dcp->backlight.fade_work, dcp_backlight_fade_work); + INIT_DELAYED_WORK(&dcp->backlight.retry_work, dcp_backlight_retry_work); return devm_add_action_or_reset(dev, dcp_backlight_release, dcp); } @@ -350,6 +373,64 @@ unsigned int dcp_backlight_retry_delay(struct apple_dcp *dcp) return delay; } +/* Power changes jump; a target chosen while off is restored on enable. */ +void dcp_backlight_desktop_power(struct apple_dcp *dcp, bool on) +{ + unsigned long flags; + + if (!dcp_backlight_desktop(dcp)) + return; + if (!on) + dcp_backlight_stop(dcp); + spin_lock_irqsave(&dcp->backlight.lock, flags); + dcp->backlight.state.dpms_off = !on; + if (dcp->backlight.state.controlled) + dcp_bl_jump(&dcp->backlight.state); + spin_unlock_irqrestore(&dcp->backlight.lock, flags); +} + +/* Reserve only after swap_start succeeds, using its actual firmware ID. */ +bool dcp_backlight_desktop_prepare(struct apple_dcp *dcp, u32 id, + bool have_surface, u32 *nits) +{ + unsigned long flags; + int ret; + + if (!dcp_backlight_desktop(dcp)) + return false; + spin_lock_irqsave(&dcp->backlight.lock, flags); + ret = dcp_bl_swap_prepare(&dcp->backlight.state, + &dcp->desktop->backlight, id, have_surface); + if (!ret) + *nits = dcp->desktop->backlight.present.nits; + spin_unlock_irqrestore(&dcp->backlight.lock, flags); + return !ret; +} + +void dcp_backlight_desktop_finish(struct apple_dcp *dcp, u32 id, + bool reply, bool accepted) +{ + unsigned long flags; + unsigned int delay; + bool finished, pending; + + if (!dcp_backlight_desktop(dcp)) + return; + spin_lock_irqsave(&dcp->backlight.lock, flags); + finished = dcp_bl_swap_finish(&dcp->backlight.state, + &dcp->desktop->backlight, id, reply, accepted); + delay = dcp_bl_retry_delay(&dcp->backlight.state); + pending = dcp->backlight.state.dirty; + spin_unlock_irqrestore(&dcp->backlight.lock, flags); + if (!finished || READ_ONCE(dcp->quiescing) || READ_ONCE(dcp->crashed)) + return; + if (delay) + mod_delayed_work(system_dfl_wq, &dcp->backlight.retry_work, + msecs_to_jiffies(delay)); + else if (pending && (!reply || accepted)) + dcp_backlight_kick(dcp); +} + #define SCALE_FACTOR (1 << 10) static u32 interpolate(int val, int min, int max, u32 *tbl, size_t tbl_size) @@ -416,7 +497,8 @@ static int drm_crtc_set_brightness(struct apple_dcp *dcp) if (ret) goto out; - if (!dcp->brightness.update) + if (dcp_backlight_desktop(dcp) ? !dcp_backlight_pending(dcp) : + !dcp->brightness.update) goto out; crtc_state = drm_atomic_get_crtc_state(commit, crtc); @@ -425,6 +507,10 @@ static int drm_crtc_set_brightness(struct apple_dcp *dcp) goto out; } + /* A request made while DPMS is off is restored on the enable swap. */ + if (dcp_backlight_desktop(dcp) && !crtc_state->active) + goto out; + /* Re-present the current scanout when brightness is the only change. */ ret = drm_atomic_add_affected_planes(commit, crtc); if (ret) @@ -471,7 +557,8 @@ int dcp_backlight_update(struct apple_dcp *dcp) /* Wait 1 vblank cycle in the hope an atomic swap has already updated * the brightness */ - msleep((1001 + 23) / 24); // 42ms for 23.976 fps + if (!dcp_backlight_desktop(dcp)) + msleep((1001 + 23) / 24); // 42ms for 23.976 fps return drm_crtc_set_brightness(dcp); } @@ -486,7 +573,7 @@ static int dcp_set_brightness(struct backlight_device *bd) if (READ_ONCE(dcp->quiescing)) return -ENODEV; - if (dcp_backlight_active(dcp)) { + if (dcp_backlight_active(dcp) || dcp_backlight_desktop(dcp)) { struct dcp_backlight_state *state = &dcp->backlight.state; bool fade; u32 old; @@ -510,24 +597,11 @@ static int dcp_set_brightness(struct backlight_device *bd) return ret; } - /* - * Desktop 26.x swaps carry nits. A level of 0 must not turn the - * panel off (DPMS does that); only a blanked panel still gets 0. - */ - if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && !brightness && - !bd->props.brightness) - brightness = min(bd->props.max_brightness, 2); - ret = drm_modeset_lock_single_interruptible(&dcp->crtc->base.mutex); if (ret) return ret; - if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) { - dcp->brightness.nits = brightness; - dcp->brightness.requested = true; - } else { - dcp->brightness.dac = calculate_dac(dcp, brightness); - } + dcp->brightness.dac = calculate_dac(dcp, brightness); dcp->brightness.update = true; drm_modeset_unlock(&dcp->crtc->base.mutex); @@ -591,7 +665,21 @@ int dcp_backlight_register(struct apple_dcp *dcp) if (!props.brightness) props.brightness = min_t(u32, DCP_DESKTOP_DEFAULT_NITS, props.max_brightness); - dcp->brightness.nits = props.brightness; + spin_lock_irqsave(&dcp->backlight.lock, flags); + if (!dcp->backlight.state.ready) { + int ret = dcp_bl_init(&dcp->backlight.state, + props.max_brightness, false, 0, + true, props.brightness); + + if (ret) { + spin_unlock_irqrestore(&dcp->backlight.lock, flags); + return ret; + } + dcp->backlight.state.fade = true; + dcp->backlight.kick = dcp_backlight_desktop_kick; + } + props.brightness = dcp->backlight.state.target; + spin_unlock_irqrestore(&dcp->backlight.lock, flags); } bl_dev = devm_backlight_device_register(dev, "apple-panel-bl", dev, dcp, diff --git a/drivers/gpu/drm/apple/dcp_backlight.h b/drivers/gpu/drm/apple/dcp_backlight.h index 92ac07b2e9d9d..a1ac47f166678 100644 --- a/drivers/gpu/drm/apple/dcp_backlight.h +++ b/drivers/gpu/drm/apple/dcp_backlight.h @@ -272,8 +272,57 @@ static inline unsigned int dcp_bl_retry_delay(const struct dcp_backlight_state * return 100U << (state->retries - 1); } +/* A desktop present can complete before its submit reply arrives. */ +struct dcp_backlight_swap { + struct dcp_backlight_present present; + u32 id; + bool pending; + bool accepted; + bool completed; +}; + +static inline int dcp_bl_swap_prepare(struct dcp_backlight_state *state, + struct dcp_backlight_swap *swap, + u32 id, bool have_surface) +{ + struct dcp_backlight_present present; + int ret = dcp_bl_prepare(state, have_surface, &present); + + if (ret) + return ret; + *swap = (struct dcp_backlight_swap) { + .present = present, + .id = id, + .pending = true, + }; + return 0; +} + +static inline bool dcp_bl_swap_finish(struct dcp_backlight_state *state, + struct dcp_backlight_swap *swap, + u32 id, bool reply, bool accepted) +{ + if (!swap->pending || swap->id != id) + return false; + if (reply) + swap->accepted = accepted; + else + swap->completed = true; + if ((!reply || accepted) && !(swap->accepted && swap->completed)) + return false; + swap->pending = false; + return dcp_bl_complete(state, swap->present.sequence, + swap->accepted && swap->completed); +} + struct apple_dcp; +void dcp_backlight_desktop_power(struct apple_dcp *dcp, bool on); +bool dcp_backlight_desktop_prepare(struct apple_dcp *dcp, u32 id, + bool have_surface, u32 *nits); +void dcp_backlight_desktop_finish(struct apple_dcp *dcp, u32 id, + bool reply, bool accepted); + /* * kick only queues work; it must not submit a top-level RPC from the caller. * The central IOMFB queue owns surface references, retries, and wire encoding. diff --git a/drivers/gpu/drm/apple/dcp_backlight_test.c b/drivers/gpu/drm/apple/dcp_backlight_test.c index 70379571a5dd3..61cf775c683fc 100644 --- a/drivers/gpu/drm/apple/dcp_backlight_test.c +++ b/drivers/gpu/drm/apple/dcp_backlight_test.c @@ -501,6 +501,41 @@ static void backlight_fade_blank_test(struct kunit *test) KUNIT_EXPECT_EQ(test, dcp_bl_effective(&state), 400U); } +static void backlight_desktop_swap_test(struct kunit *test) +{ + struct dcp_backlight_state state; + struct dcp_backlight_swap swap = {}; + + KUNIT_ASSERT_EQ(test, dcp_bl_init(&state, 600, true, 300, false, 0), 0); + state.fade = true; + KUNIT_ASSERT_EQ(test, dcp_bl_request(&state, 600, false, false), 0); + KUNIT_EXPECT_TRUE(test, dcp_bl_fade_step(&state, 125, 250)); + KUNIT_EXPECT_EQ(test, state.level, 450U); + KUNIT_ASSERT_EQ(test, dcp_bl_swap_prepare(&state, &swap, 42, true), 0); + /* A completion alone must not publish an unaccepted panel level. */ + KUNIT_EXPECT_FALSE(test, dcp_bl_swap_finish(&state, &swap, 42, false, true)); + KUNIT_EXPECT_EQ(test, state.actual, 300U); + KUNIT_EXPECT_FALSE(test, dcp_bl_swap_finish(&state, &swap, 41, true, true)); + KUNIT_EXPECT_TRUE(test, dcp_bl_swap_finish(&state, &swap, 42, true, true)); + KUNIT_EXPECT_EQ(test, state.actual, 450U); + KUNIT_EXPECT_FALSE(test, dcp_bl_swap_finish(&state, &swap, 42, true, false)); + KUNIT_EXPECT_FALSE(test, dcp_bl_fade_step(&state, 250, 250)); + KUNIT_ASSERT_EQ(test, dcp_bl_swap_prepare(&state, &swap, 43, true), 0); + KUNIT_EXPECT_TRUE(test, dcp_bl_swap_finish(&state, &swap, 43, true, false)); + KUNIT_EXPECT_EQ(test, state.actual, 450U); + KUNIT_EXPECT_EQ(test, dcp_bl_retry_delay(&state), 100U); + KUNIT_ASSERT_EQ(test, dcp_bl_swap_prepare(&state, &swap, 44, true), 0); + KUNIT_EXPECT_FALSE(test, dcp_bl_swap_finish(&state, &swap, 44, true, true)); + KUNIT_EXPECT_TRUE(test, dcp_bl_swap_finish(&state, &swap, 44, false, true)); + KUNIT_EXPECT_EQ(test, state.actual, 600U); + /* A lit zero is a valid request, with the same fade as the Neo. */ + KUNIT_ASSERT_EQ(test, dcp_bl_request(&state, 0, false, false), 0); + KUNIT_EXPECT_TRUE(test, dcp_bl_fade_step(&state, 125, 250)); + KUNIT_EXPECT_EQ(test, state.level, 300U); + KUNIT_EXPECT_FALSE(test, dcp_bl_fade_step(&state, 250, 250)); + KUNIT_EXPECT_EQ(test, state.level, 0U); +} + static struct kunit_case backlight_cases[] = { KUNIT_CASE(backlight_millinits_takeover_test), KUNIT_CASE(backlight_takeover_test), @@ -515,6 +550,7 @@ static struct kunit_case backlight_cases[] = { KUNIT_CASE(backlight_fallback_test), KUNIT_CASE(backlight_fallback_same_level_test), KUNIT_CASE(backlight_seed_known_test), + KUNIT_CASE(backlight_desktop_swap_test), KUNIT_CASE(backlight_fade_test), KUNIT_CASE(backlight_fade_restart_test), KUNIT_CASE(backlight_fade_blank_test), diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index c0bef660c25ec..2c93f24d5a006 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -90,15 +90,23 @@ static inline u64 dcpep_ack(enum dcp_context_id id) return dcpep_msg(id, 0, 0) | IOMFB_MSG_ACK; } -/* - * Adaptive sync through IOMFBParameter 14 black-screens the J616s panel on - * the desktop 26.6 firmware: the firmware accepts the VRR mode, then no swap - * completes. Do not offer it there. - */ +/* Keep the established fixed-refresh default until panel qualification. */ +static bool t6041_vrr; +module_param(t6041_vrr, bool, 0444); +MODULE_PARM_DESC(t6041_vrr, "Enable experimental adaptive sync on the T6041 internal panel"); + +bool dcp_desktop_vrr_supported(struct apple_dcp *dcp) +{ + return t6041_vrr && dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && + dcp_has_panel(dcp) && dcp->main_display && + !READ_ONCE(dcp->desktop->vrr_failed); +} + static bool dcp_mode_vrr_supported(struct apple_dcp *dcp, const struct dcp_display_mode *mode) { - return dcp->fw_compat != DCP_FIRMWARE_V_26_6_DESKTOP || !mode->vrr; + return dcp->fw_compat != DCP_FIRMWARE_V_26_6_DESKTOP || + dcp_desktop_vrr_supported(dcp); } /* @@ -1950,7 +1958,7 @@ int dcp_get_modes(struct drm_connector *connector) if (!dcp_mode_vrr_supported(dcp, &modes[i])) continue; if (modes[i].vrr) { - u16 lo = modes[i].min_vrr >> 16; + u16 lo = DIV_ROUND_UP(modes[i].min_vrr, 1U << 16); u16 hi = modes[i].max_vrr >> 16; if (!min_vfreq || lo < min_vfreq) @@ -1998,7 +2006,7 @@ static int dcp_desktop_modeset(struct apple_dcp *dcp, if (d->trial_modeset_ok) { d->trial_modeset_ok = false; - if (d->mode_live && lookup_mode(dcp, &crtc_state->mode, &matched) && + if (!crtc_state->vrr_enabled && d->mode_live && lookup_mode(dcp, &crtc_state->mode, &matched) && matched.timing_mode_id == d->trial_timing_mode_id && matched.color_mode_id == d->trial_color_mode_id) { dev_dbg(dcp->dev, "keeping the takeover mode " DRM_MODE_FMT "\n", @@ -2258,7 +2266,9 @@ int dcp_crtc_atomic_modeset(struct drm_crtc *crtc, return 0; modeset = drm_atomic_crtc_needs_modeset(crtc_state) || - !READ_ONCE(dcp->mode_state.valid); + !READ_ONCE(dcp->mode_state.valid) || + (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && + crtc_state->vrr_enabled != dcp->desktop->vrr_requested); if (!modeset) return 0; diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 246498a8e843c..3d9465ee5561e 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -32,6 +32,7 @@ #include "iomfb.h" #include "iomfb_internal.h" #include "parser.h" +#include "dcp-refresh.h" #include "dcp-property.h" #include "trace.h" #include "version_utils.h" @@ -458,6 +459,11 @@ static void dcpep_cb_swap_complete(struct apple_dcp *dcp, } if (dcp->hw.iomfb_method_profile != DCP_IOMFB_METHODS_H17G) retire = iomfb_present_complete_h17p(dcp); +#endif +#if DCP_FW_DESKTOP_26(DCP_FW_VER) + if (resp->swap_id == READ_ONCE(dcp->desktop->present_id)) + WRITE_ONCE(dcp->desktop->present_pending, false); + dcp_backlight_desktop_finish(dcp, resp->swap_id, false, true); #endif trace_iomfb_swap_complete(dcp, resp->swap_id); dcp->last_swap_id = resp->swap_id; @@ -1820,6 +1826,12 @@ static void dcp_swap_cleared(struct apple_dcp *dcp, void *data, void *cookie) if (ret) status = ~0U; +#if DCP_FW_DESKTOP_26(DCP_FW_VER) + if (status && swap_id == READ_ONCE(dcp->desktop->present_id)) + WRITE_ONCE(dcp->desktop->present_pending, false); + dcp_backlight_desktop_finish(dcp, swap_id, true, !status); +#endif + if (status) { /* An aborted present has already been reported. */ if (ret != -ECANCELED) @@ -2033,6 +2045,8 @@ void DCP_FW_NAME(iomfb_poweron)(struct apple_dcp *dcp) ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(10000)); #if DCP_FW_DESKTOP_26(DCP_FW_VER) dcp->desktop->poweron_result = ret > 0 ? 0 : ret == 0 ? 1 : 2; + if (ret > 0) + dcp_backlight_desktop_power(dcp, true); #endif if (ret == 0) { @@ -2154,6 +2168,9 @@ void DCP_FW_NAME(iomfb_poweroff)(struct apple_dcp *dcp) if (dcp->hw.iomfb_method_profile == DCP_IOMFB_METHODS_H17G) dcp_prepare_clear_swap(dcp, swap); #else +#if DCP_FW_DESKTOP_26(DCP_FW_VER) + dcp_backlight_desktop_power(dcp, false); +#endif dcp_prepare_clear_swap(dcp, swap); #endif @@ -2566,6 +2583,12 @@ static void dcp_swapped(struct apple_dcp *dcp, void *data, void *cookie) if (ret) status = ~0U; +#if DCP_FW_DESKTOP_26(DCP_FW_VER) + if (status && swap_id == READ_ONCE(dcp->desktop->present_id)) + WRITE_ONCE(dcp->desktop->present_pending, false); + dcp_backlight_desktop_finish(dcp, swap_id, true, !status); +#endif + if (status) { /* An aborted present has already been reported. */ if (ret != -ECANCELED) @@ -2601,6 +2624,20 @@ static void dcp_swap_started(struct apple_dcp *dcp, void *data, void *cookie) if (dcp_present_retires(dcp)) dcp_arm_retained_framebuffers(dcp, resp->swap_id); +#if DCP_FW_DESKTOP_26(DCP_FW_VER) + WRITE_ONCE(dcp->desktop->present_id, resp->swap_id); + WRITE_ONCE(dcp->desktop->present_pending, true); + if (dcp_has_panel(dcp)) { + u32 nits; + bool have_surface = false; + + for (unsigned int i = 0; i < SWAP_SURFACES; i++) + have_surface |= !DCP_FW_SWAP(dcp).surf_null[i]; + if (dcp_backlight_desktop_prepare(dcp, resp->swap_id, + have_surface, &nits)) + dcp_swap_set_brightness(&DCP_FW_SWAP(dcp).swap, nits); + } +#endif trace_iomfb_swap_submit(dcp, resp->swap_id); dcp_h17p_prepare_swap(dcp); dcp_swap_submit(dcp, false, &DCP_FW_SWAP(dcp), dcp_swapped, NULL); @@ -2808,6 +2845,9 @@ struct dcp_modeset_cookie { struct dcp_set_digital_out_mode_req actual; struct dcp_set_digital_out_mode_req bootstrap; bool use_bootstrap; + bool vrr; + bool program_sync; + u32 min_vrr; }; /* @@ -2834,6 +2874,30 @@ static void dcp_get_mode_info(struct apple_dcp *dcp, sizeof(struct dcp_get_mode_info_resp_v26_6), &req, cb, cookie); } +static void dcp_modeset_select(struct apple_dcp *dcp, void *out, void *cookie); +static void dcp_modeset_sync(struct apple_dcp *dcp, void *cookie); + +static void dcp_modeset_fixed_fallback(struct apple_dcp *dcp, + struct dcp_modeset_cookie *modeset) +{ + dev_warn(dcp->dev, "adaptive sync failed, restoring fixed refresh\n"); + WRITE_ONCE(dcp->desktop->vrr_failed, true); + modeset->vrr = false; + dcp_modeset_sync(dcp, modeset); +} + +static void dcp_modeset_actual_done(struct apple_dcp *dcp, void *data, + void *cookie) +{ + struct dcp_modeset_cookie *modeset = cookie; + + if (data && get_unaligned_le32(data) && modeset->vrr) { + dcp_modeset_fixed_fallback(dcp, modeset); + return; + } + complete_set_digital_out_mode(dcp, data, cookie); +} + static void dcp_modeset_after_actual_info(struct apple_dcp *dcp, void *data, void *cookie) { @@ -2844,7 +2908,7 @@ static void dcp_modeset_after_actual_info(struct apple_dcp *dcp, void *data, return; } dcp_set_digital_out_mode(dcp, false, &modeset->actual, - complete_set_digital_out_mode, cookie); + dcp_modeset_actual_done, cookie); } static void dcp_modeset_after_bootstrap(struct apple_dcp *dcp, void *data, @@ -2886,6 +2950,36 @@ static void dcp_modeset_select(struct apple_dcp *dcp, void *out, void *cookie) dcp_modeset_after_actual_info, cookie); } +static void dcp_modeset_sync_done(struct apple_dcp *dcp, void *data, + void *cookie) +{ + struct dcp_modeset_cookie *modeset = cookie; + + if (!data || get_unaligned_le32(data)) { + if (data && modeset->vrr) { + dcp_modeset_fixed_fallback(dcp, modeset); + return; + } + complete_set_digital_out_mode(dcp, data, cookie); + return; + } + dcp_modeset_select(dcp, NULL, cookie); +} + +static void dcp_modeset_sync(struct apple_dcp *dcp, void *cookie) +{ + struct dcp_modeset_cookie *modeset = cookie; + struct dcp_set_parameter_dcp param; + + /* Match the desktop parameter record, including its unused padding. */ + memset(¶m, 0xaa, sizeof(param)); + param.param = IOMFBPARAM_ADAPTIVE_SYNC; + memset(param.value, 0, 3 * sizeof(u64)); + param.value[0] = modeset->vrr ? modeset->min_vrr : 0; + param.count = 3; + dcp_set_parameter_dcp(dcp, false, ¶m, dcp_modeset_sync_done, cookie); +} + int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, struct drm_crtc_state *crtc_state) { @@ -2927,11 +3021,9 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, .timing_mode_id = mode->timing_mode_id }; - /* - * Suspected VRR modes need swap timestamps, see iomfb_flush(). - * Adaptive sync itself stays off, see dcp_mode_vrr_supported(). - */ + /* ProMotion needs desktop timestamps in fixed-120 mode too. */ dcp->use_timestamps = mode->vrr; + dcp->desktop->vrr_requested = crtc_state->vrr_enabled; /* * The firmware changes a fixed panel's mode through the swap geometry, @@ -2949,6 +3041,12 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, return -ENOMEM; cookie = &modeset->wait; modeset->actual = dcp->mode; + modeset->program_sync = dcp_has_panel(dcp) && dcp->main_display && + (dcp_desktop_vrr_supported(dcp) || dcp->vrr_enabled || + READ_ONCE(dcp->desktop->vrr_failed)); + modeset->vrr = modeset->program_sync && mode->vrr && + crtc_state->vrr_enabled && dcp_desktop_vrr_supported(dcp); + modeset->min_vrr = mode->min_vrr; cookie->status = ~0U; init_completion(&cookie->done); @@ -2971,7 +3069,10 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, modeset->bootstrap.timing_mode_id); } - dcp_modeset_select(dcp, NULL, cookie); + if (modeset->program_sync) + dcp_modeset_sync(dcp, cookie); + else + dcp_modeset_select(dcp, NULL, cookie); /* * The DCP firmware has an internal timeout of ~8 seconds for @@ -3001,6 +3102,7 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, jiffies_to_msecs(ret)); ret = 0; } + dcp->vrr_enabled = !ret && modeset->vrr; kref_put(&cookie->refcount, release_wait_cookie); dcp->desktop->mode_live = !ret; @@ -3262,20 +3364,9 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru } #if DCP_FW_DESKTOP_26(DCP_FW_VER) - if (has_surface && dcp->use_timestamps) { - /* - * Timestamp types 1, 2 and 7 hold the time the swap should be - * shown, in the firmware's clock. A target long past shows it - * at the next refresh: 120 Hz in the fixed mode, as soon as - * the panel allows with adaptive sync. The architectural - * counter must not go here: after a VRR mode set the firmware - * then never shows the swap, and it holds a swap whose target - * it reads as more than about a frame ahead. - */ - req->swap.timestamp[0] = 120; - req->swap.timestamp[1] = 120; - req->swap.timestamp[6] = 120; - } + /* Types 1, 2 and 7 carry the same desktop presentation deadline. */ + req->swap.timestamp[0] = req->swap.timestamp[1] = req->swap.timestamp[6] = + dcp_desktop_present_deadline(has_surface, dcp->use_timestamps); #else if (has_surface && (dcp->use_timestamps || dcp->vrr_enabled)) { u64 submit_timestamp = dcp->swap_submit_timestamp; @@ -3310,20 +3401,8 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru dcp->hw.iomfb_method_profile == DCP_IOMFB_METHODS_H17G; #endif #if DCP_FW_DESKTOP_26(DCP_FW_VER) - /* - * The firmware publishes no initial level, so nits stays at the - * registration default until userspace writes the backlight. Sending - * that from the forced post-poweron update can turn the panel off: - * keep the level the boot loader set until one is requested. - */ - if (update_brightness && !dcp->brightness.requested) { - dcp->brightness.update = false; - update_brightness = false; - } - if (update_brightness) { - dcp_swap_set_brightness(&req->swap, dcp->brightness.nits); - dcp->brightness.update = false; - } + /* The shared ramp is encoded after swap_start, at submission time. */ + dcp->brightness.update = false; #else if (update_brightness) { #if DCP_FW_LAYOUT_14_7(DCP_FW_VER) diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index cecad5a1fb866..612232bd394df 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -14,6 +14,7 @@ #endif #include "parser.h" +#include "dcp-refresh.h" #include "trace.h" #define DCP_PARSE_HEADER 0xd3 @@ -723,7 +724,8 @@ static int parse_mode(struct dcp_parse_ctx *handle, struct timing_color_restrictions limits = {}; struct dcp_color_mode unsafe_rgb8 = { .score = -1 }; bool have_horiz = false, have_vert = false, have_virtual = false; - bool duplicate_scope = false; + bool duplicate_scope = false, vrr_ambiguous = false; + unsigned int vrr_seen = 0; s64 min_vrr = 0, max_vrr = 0; u64 clock; s64 id = -1; @@ -773,16 +775,20 @@ static int parse_mode(struct dcp_parse_ctx *handle, else if (!strcmp(key, "HorizontalAttributes")) { duplicate_scope |= have_horiz; have_horiz = true; - ret = parse_dimension(it.handle, &horiz, external_sdr); + ret = parse_dimension(it.handle, &horiz, external_sdr || internal); } else if (!strcmp(key, "VerticalAttributes")) { duplicate_scope |= have_vert; have_vert = true; - ret = parse_dimension(it.handle, &vert, external_sdr); - } else if (!strcmp(key, "MinimumVariableRefreshRate")) + ret = parse_dimension(it.handle, &vert, external_sdr || internal); + } else if (!strcmp(key, "MinimumVariableRefreshRate")) { + vrr_ambiguous |= !!(vrr_seen & 1); + vrr_seen |= 1; ret = parse_int_bound(it.handle, &min_vrr, 0, U32_MAX); - else if (!strcmp(key, "MaximumVariableRefreshRate")) + } else if (!strcmp(key, "MaximumVariableRefreshRate")) { + vrr_ambiguous |= !!(vrr_seen & 2); + vrr_seen |= 2; ret = parse_int_bound(it.handle, &max_vrr, 0, U32_MAX); - else if (!strcmp(key, "ColorModes")) + } else if (!strcmp(key, "ColorModes")) ret = parse_color_modes(it.handle, out, external_sdr, &limits, handle->automatic_1080p180_rgb8 ? &unsafe_rgb8 : NULL); @@ -869,27 +875,11 @@ static int parse_mode(struct dcp_parse_ctx *handle, if (is_virtual) return -EINVAL; - /* - * An internal ProMotion panel carries no EDID or DisplayID, so DCP - * reports no adaptive-sync range for it. Assume the ProMotion floor of - * 24 Hz up to whatever rate the mode itself advertises. - */ - if (internal && vert.precise_sync_rate >> 16 == 120) { - out->min_vrr = 24 << 16; - out->max_vrr = vert.precise_sync_rate; - out->vrr = true; - } - - /* - * Refresh rates are reported by DCP as 16.16 fixed-point Hz. - * External H17P skips adaptive-sync programming: keep the fixed timing - * available, but do not advertise its sink VRR range as driver support. - */ - if (!external_sdr && min_vrr && max_vrr > min_vrr) { - out->min_vrr = min_vrr; - out->max_vrr = max_vrr; - out->vrr = true; - } + /* External H17P keeps fixed timings without adaptive-sync support. */ + if (!external_sdr && !vrr_ambiguous) + out->vrr = dcp_refresh_range(vert.precise_sync_rate, min_vrr, + max_vrr, internal, + &out->min_vrr, &out->max_vrr); clock = calculate_clock(&horiz, &vert); if (!clock || clock > INT_MAX) @@ -918,7 +908,8 @@ static int parse_mode(struct dcp_parse_ctx *handle, .height_mm = height_mm, }; - if (external_sdr && horiz.sync_polarity >= 0 && vert.sync_polarity >= 0) + if ((external_sdr || internal) && horiz.sync_polarity >= 0 && + vert.sync_polarity >= 0) mode->flags = (horiz.sync_polarity ? DRM_MODE_FLAG_PHSYNC : DRM_MODE_FLAG_NHSYNC) | (vert.sync_polarity ? DRM_MODE_FLAG_PVSYNC : DRM_MODE_FLAG_NVSYNC); out->metadata_timing_valid = external_sdr && !out->automatic_rgb8_quirk && diff --git a/drivers/gpu/drm/apple/parser_mode_test.c b/drivers/gpu/drm/apple/parser_mode_test.c index 40d0c8d9cd196..31b248c6fff87 100644 --- a/drivers/gpu/drm/apple/parser_mode_test.c +++ b/drivers/gpu/drm/apple/parser_mode_test.c @@ -7,6 +7,7 @@ #include #include "parser.h" +#include "dcp-refresh.h" struct dcp_mode_rank_case { const char *desc; @@ -40,7 +41,34 @@ static void dcp_mode_rank_test(struct kunit *test) c->above); } +static void dcp_refresh_range_test(struct kunit *test) +{ + u32 low = 0, high = 0; + + KUNIT_EXPECT_TRUE(test, dcp_refresh_range(120U << 16, 0, 0, + true, &low, &high)); + KUNIT_EXPECT_EQ(test, low, 24U << 16); + KUNIT_EXPECT_EQ(test, high, 120U << 16); + KUNIT_EXPECT_FALSE(test, dcp_refresh_range(60U << 16, 0, 0, + true, &low, &high)); + KUNIT_EXPECT_FALSE(test, dcp_refresh_range(120U << 16, 0, 0, + false, &low, &high)); + KUNIT_EXPECT_FALSE(test, dcp_refresh_range(120U << 16, 0, 120U << 16, + true, &low, &high)); + KUNIT_EXPECT_FALSE(test, dcp_refresh_range(120U << 16, 24U << 16, + 144U << 16, true, &low, &high)); + KUNIT_EXPECT_FALSE(test, dcp_refresh_range(120U << 16, 120U << 16, + 24U << 16, true, &low, &high)); + KUNIT_EXPECT_TRUE(test, dcp_refresh_range(120U << 16, 48U << 16, + 120U << 16, true, &low, &high)); + KUNIT_EXPECT_EQ(test, low, 48U << 16); + KUNIT_EXPECT_EQ(test, dcp_desktop_present_deadline(true, true), 120ULL); + KUNIT_EXPECT_EQ(test, dcp_desktop_present_deadline(false, true), 0ULL); + KUNIT_EXPECT_EQ(test, dcp_desktop_present_deadline(true, false), 0ULL); +} + static struct kunit_case dcp_mode_rank_test_cases[] = { + KUNIT_CASE(dcp_refresh_range_test), KUNIT_CASE_PARAM(dcp_mode_rank_test, dcp_mode_rank_gen_params), {} }; diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/README b/tools/testing/selftests/drivers/gpu/apple-dcp/README index c49b2d48949af..24437d55edfd8 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/README +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/README @@ -158,3 +158,14 @@ surviving an unrelated failed AGX request, both reply/timeout lock orderings, malformed replies, true owner failure, an unowned or invalid fault SID, probe rollback versus published DMA lifetime, exactly-once DRM event ownership, and mixed-owner, truncated, failed-translation and invalid-owner IOMMU tuples. + +Internal panel policy +--------------------- +The display group covers the shared nits ramp on a 600-nit panel, retargeting, +blanking, suspend, rejected submits and both submit/completion callback orders. +Synthetic TimingElements exercise 120 Hz and fractional lower refresh rates, +DCP color/timing identities, invalid geometry and adaptive-sync ranges. The +adaptive-sync callback test verifies the desktop parameter record, mode-info +ordering and one fixed-refresh retry after a rejected enable or modeset. +These tests do not establish which timings a particular panel reports or prove +refresh rate, flicker, brightness smoothness or adaptive-sync on hardware. diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/display.py b/tools/testing/selftests/drivers/gpu/apple-dcp/display.py new file mode 100644 index 0000000000000..fc76e557ee5a3 --- /dev/null +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/display.py @@ -0,0 +1,278 @@ +# SPDX-License-Identifier: GPL-2.0-only OR MIT +# Copyright 2026 Ryan Murray +"""Internal panel policy and real TimingElements parser, without hardware.""" +import copy +import struct +from common import block +import rate + + +def backlight(root): + header = (root / 'drivers/gpu/drm/apple/dcp_backlight.h').read_text() + header = '\n'.join(line for line in header.splitlines() + if not line.startswith('#include')) + return r''' +#include +#include +#include +#include +#include +typedef uint32_t u32; +typedef uint64_t u64; +typedef uint8_t u8; +#define min(a,b) ((a)<(b)?(a):(b)) +#define mul_u32_u32(a,b) ((u64)(a)*(b)) +#define div_u64(a,b) ((a)/(b)) +''' + header + r''' +static void completed(struct dcp_backlight_state *s, struct dcp_backlight_swap *p, + u32 id, bool early) +{ + u32 old=s->actual; + if(early) { + assert(!dcp_bl_swap_finish(s,p,id,false,true)); + assert(s->actual==old && s->in_flight); + assert(dcp_bl_swap_finish(s,p,id,true,true)); + } else { + assert(!dcp_bl_swap_finish(s,p,id,true,true)); + assert(s->actual==old && s->in_flight); + assert(dcp_bl_swap_finish(s,p,id,false,true)); + } + assert(!dcp_bl_swap_finish(s,p,id,false,true)); +} +int main(void) +{ + struct dcp_backlight_state s; + struct dcp_backlight_swap p={0}; + u32 id=1, nits; + assert(!dcp_bl_takeover_nits(600,600000,&nits) && nits==600); + /* A registration default must never change the loader's visible level. */ + assert(!dcp_bl_init(&s,600,false,0,true,250)); s.fade=true; + assert(!s.dirty && !s.level_known); + assert(dcp_bl_swap_prepare(&s,&p,id,true)==-EALREADY); + assert(!dcp_bl_request(&s,250,false,false)); + assert(!s.fading && s.dirty); + assert(dcp_bl_swap_prepare(&s,&p,id,false)==-ENODATA); + assert(!dcp_bl_swap_prepare(&s,&p,id,true)); + completed(&s,&p,id++,true); + assert(s.actual==250 && s.level_known); + assert(!dcp_bl_seed(&s,300)); + /* Same state as a slider request or an ALS retarget through backlight. */ + assert(!dcp_bl_request(&s,600,false,false) && s.fading); + for(u32 t=16;t<250;t+=16) { + u32 before=s.level; + assert(dcp_bl_fade_step(&s,t,250)); + assert(s.level>=before && s.level<600); + if(t==16) { + assert(!dcp_bl_swap_prepare(&s,&p,id,true)); + assert(dcp_bl_swap_finish(&s,&p,id++,true,false)); + assert(s.actual==250 && s.dirty && dcp_bl_retry_delay(&s)==100); + } + } + assert(!dcp_bl_fade_step(&s,250,250)); + assert(!dcp_bl_swap_prepare(&s,&p,id,true)); + assert(!dcp_bl_swap_finish(&s,&p,id+100,false,true)); + completed(&s,&p,id++,false); assert(s.actual==600); + assert(dcp_bl_request(&s,601,false,false)==-ERANGE); + assert(!dcp_bl_request(&s,0,false,false)); + assert(dcp_bl_fade_step(&s,125,250) && s.level==300); + assert(!dcp_bl_request(&s,100,false,false) && s.fade_from==300); + assert(dcp_bl_fade_step(&s,125,250) && s.level==200); + assert(!dcp_bl_request(&s,100,true,false)); + assert(!s.fading && dcp_bl_effective(&s)==0); + assert(!dcp_bl_request(&s,450,true,false) && dcp_bl_effective(&s)==0); + assert(!dcp_bl_request(&s,450,false,false) && s.level==450); + assert(!dcp_bl_request(&s,0,false,true) && dcp_bl_effective(&s)==0); + assert(!dcp_bl_request(&s,450,false,false) && s.level==450); + assert(!dcp_bl_dpms(&s,false) && dcp_bl_effective(&s)==0); + assert(!dcp_bl_dpms(&s,true) && dcp_bl_effective(&s)==450); + assert(!dcp_bl_resend(&s)); + return 0; +} +''' + + +def modes(root): + f = rate.parser_fixture(root) + # Use the exact production parser and public serializer already used by rate. + parser = f['parser'] + prefix = (f['prefix'] + f['epoch'] + f['metadata'] + f['header']).replace('static inline ', 'static inline __attribute__((unused)) ') + baseline = copy.deepcopy(f['timing_fixture'][0]) + checks = [] + blobs = [] + + def case(elements, expected, ranges=None): + raw = bytearray(struct.pack('','') + return r''' +#include +#include +#include +typedef uint32_t u32; +typedef uint64_t u64; +''' + h + r''' +int main(void) { + u32 lo=0,hi=0; + assert(dcp_refresh_range(120U<<16,0,0,true,&lo,&hi)); + assert(lo==24U<<16 && hi==120U<<16); + assert(!dcp_refresh_range(120U<<16,0,0,false,&lo,&hi)); + assert(!dcp_refresh_range(60U<<16,0,0,true,&lo,&hi)); + assert(!dcp_refresh_range(120U<<16,0,120U<<16,true,&lo,&hi)); + assert(!dcp_refresh_range(120U<<16,120U<<16,24U<<16,true,&lo,&hi)); + assert(!dcp_refresh_range(120U<<16,24U<<16,144U<<16,true,&lo,&hi)); + assert(dcp_desktop_present_deadline(true,true)==120); + assert(dcp_desktop_present_deadline(true,false)==0); + assert(dcp_desktop_present_deadline(false,true)==0); + return 0; +} +''' + + +def adaptive(root): + source=(root/'drivers/gpu/drm/apple/iomfb_template.c').read_text() + funcs='\n'.join(block(source, marker) for marker in ( + 'static void dcp_modeset_fixed_fallback(', + 'static void dcp_modeset_actual_done(', + 'static void dcp_modeset_after_actual_info(', + 'static void dcp_modeset_after_bootstrap(', + 'static void dcp_modeset_after_bootstrap_info(', + 'static void dcp_modeset_select(', + 'static void dcp_modeset_sync_done(', + 'static void dcp_modeset_sync(')) + cookie=block(source,'struct dcp_modeset_cookie {')+';\n' + main=(root/'drivers/gpu/drm/apple/iomfb.c').read_text() + gate=block(main,'bool dcp_desktop_vrr_supported(') + return r''' +#include +#include +#include +#include +typedef uint32_t u32; +typedef uint64_t u64; +#define READ_ONCE(x) (x) +#define WRITE_ONCE(x,v) ((x)=(v)) +#define DCP_FIRMWARE_V_26_6_DESKTOP 5 +#define IOMFBPARAM_ADAPTIVE_SYNC 14 +#define dev_warn(...) ((void)0) +struct dcp_wait_cookie { u32 status; bool done,replied; }; +struct dcp_set_digital_out_mode_req {u32 color_mode_id,timing_mode_id;}; +struct dcp_set_parameter_dcp { u32 param; u64 value[4]; u32 count; }; +struct apple_dcp {void *dev;int fw_compat;bool main_display,panel;struct {bool vrr_failed;} *desktop;}; +static bool t6041_vrr; +static bool dcp_has_panel(struct apple_dcp *d) {return d->panel;} +static u32 get_unaligned_le32(const void *p) {u32 v;memcpy(&v,p,4);return v;} +typedef void (*callback)(struct apple_dcp *,void *,void *); +static callback pending; +static void *cookie_pending; +static unsigned kind, parameters,infos,modes; +static struct dcp_set_parameter_dcp parameter; +static struct dcp_set_digital_out_mode_req requested; +static void dcp_modeset_select(struct apple_dcp *,void *,void *); +static void dcp_modeset_sync(struct apple_dcp *,void *); +static void dcp_set_parameter_dcp(struct apple_dcp *d,bool oob, + struct dcp_set_parameter_dcp *p,callback cb,void *c) +{assert(!pending && !oob);parameter=*p;pending=cb;cookie_pending=c;kind=1;parameters++;} +static void dcp_get_mode_info(struct apple_dcp *d,const struct dcp_set_digital_out_mode_req *m,callback cb,void *c) +{assert(!pending);requested=*m;pending=cb;cookie_pending=c;kind=2;infos++;} +static void dcp_set_digital_out_mode(struct apple_dcp *d,bool oob, + struct dcp_set_digital_out_mode_req *m,callback cb,void *c) +{assert(!pending && !oob);requested=*m;pending=cb;cookie_pending=c;kind=3;modes++;} +static void complete_set_digital_out_mode(struct apple_dcp *d,void *p,void *c) +{struct dcp_wait_cookie *w=c;w->done=true;w->replied=!!p;if(p)w->status=get_unaligned_le32(p);} +''' + cookie + gate + funcs + r''' +static void reply(struct apple_dcp *d,unsigned expected,u32 *status) +{assert(kind==expected && pending);callback cb=pending;void *c=cookie_pending;pending=NULL;cb(d,status,c);} +static void initialize(struct dcp_modeset_cookie *m) +{memset(m,0,sizeof(*m));m->vrr=true;m->min_vrr=24U<<16;m->actual=(struct dcp_set_digital_out_mode_req){11,12};m->bootstrap=(struct dcp_set_digital_out_mode_req){13,12};} +static void selected(struct apple_dcp *d,struct dcp_modeset_cookie *m) +{u32 zero=0;reply(d,2,&zero);assert(requested.color_mode_id==11);reply(d,3,&zero);assert(m->wait.done && m->wait.replied && !m->wait.status);} +int main(void) +{ + struct apple_dcp d={.fw_compat=DCP_FIRMWARE_V_26_6_DESKTOP,.main_display=true,.panel=true}; + __typeof__(*d.desktop) desktop={0};d.desktop=&desktop; + assert(!dcp_desktop_vrr_supported(&d));t6041_vrr=true; + assert(dcp_desktop_vrr_supported(&d)); + d.main_display=false;assert(!dcp_desktop_vrr_supported(&d));d.main_display=true; + d.panel=false;assert(!dcp_desktop_vrr_supported(&d));d.panel=true; + d.fw_compat=3;assert(!dcp_desktop_vrr_supported(&d));d.fw_compat=DCP_FIRMWARE_V_26_6_DESKTOP; + struct dcp_modeset_cookie m;u32 zero=0,error=1; + initialize(&m);dcp_modeset_sync(&d,&m); + assert(parameter.param==14 && parameter.count==3 && parameter.value[0]==24U<<16); + assert(!parameter.value[1] && !parameter.value[2] && parameter.value[3]==0xaaaaaaaaaaaaaaaaULL); + reply(&d,1,&zero);selected(&d,&m);assert(m.vrr && !desktop.vrr_failed); + /* Rejected enable is cleared before a fixed timing is selected. */ + initialize(&m);dcp_modeset_sync(&d,&m);reply(&d,1,&error); + assert(desktop.vrr_failed && !dcp_desktop_vrr_supported(&d) && !m.vrr && !parameter.value[0]); + reply(&d,1,&zero);selected(&d,&m); + /* A rejected modeset gets one fixed-refresh retry. */ + desktop.vrr_failed=false;initialize(&m);dcp_modeset_sync(&d,&m); + reply(&d,1,&zero);reply(&d,2,&zero);reply(&d,3,&error); + assert(!m.vrr && desktop.vrr_failed && kind==1 && !parameter.value[0]); + reply(&d,1,&zero);selected(&d,&m); + /* Failed disable and absent replies must stop, without claiming success. */ + initialize(&m);dcp_modeset_sync(&d,&m);reply(&d,1,&error);reply(&d,1,&error); + assert(m.wait.done && m.wait.status && !pending); + initialize(&m);dcp_modeset_sync(&d,&m);reply(&d,1,NULL); + assert(m.wait.done && !m.wait.replied && !pending); + /* Bootstrap keeps the DCP's actual color/timing identities. */ + desktop.vrr_failed=false;initialize(&m);m.use_bootstrap=true; + dcp_modeset_sync(&d,&m);reply(&d,1,&zero); + assert(requested.color_mode_id==13 && requested.timing_mode_id==12); + reply(&d,2,&zero);reply(&d,3,&zero);selected(&d,&m); + assert(parameters && infos && modes); + return 0; +} +''' diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py b/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py index 7bd087c364c52..61000228c8b49 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py @@ -48,6 +48,8 @@ def wire(value, out): metadata = block(metadata, 'struct dcp_display_metadata {') + ';\n' header = (base / 'parser.h').read_text() header = header[header.index('struct apple_dcp;'):header.index('int parse(')] + refresh = (base / 'dcp-refresh.h').read_text().replace('#include ', '') + header = refresh + '\n' + header # Header helpers stay header-like: unused here, as in any includer. header = header.replace('static inline ', 'static inline __attribute__((unused)) ') edid_header = (root / 'include/drm/drm_edid.h').read_text() diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/run.py b/tools/testing/selftests/drivers/gpu/apple-dcp/run.py index 54bcad84819d2..6913134d2a006 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/run.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/run.py @@ -29,6 +29,7 @@ import rate import boot import m4 +import display def parking_unit(root): @@ -40,7 +41,7 @@ def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--source', type=Path, default=Path(__file__).resolve().parents[6]) parser.add_argument('--output', type=Path, help='retain generated C, commands and results here') - parser.add_argument('--group', choices=('all', 'edid', 'rate', 'phy', 'lifecycle', 'boot', 'quiesce', 'm4'), default='all') + parser.add_argument('--group', choices=('all', 'edid', 'rate', 'phy', 'lifecycle', 'boot', 'quiesce', 'm4', 'display'), default='all') args = parser.parse_args() root = args.source.resolve() output = args.output or Path(tempfile.mkdtemp(prefix='apple-dcp-selftest-')) @@ -51,6 +52,10 @@ def main(): 'orientation-faults', 'backend-change', 'safe-first', 'failed-flip-return', 'live-park')) tests = [ + ('display', 'panel-backlight-ramp', display.backlight, ((),), ()), + ('display', 'panel-mode-catalog', display.modes, ((),), ()), + ('display', 'panel-refresh-range', display.refresh, ((),), ()), + ('display', 'panel-adaptive-fallback', display.adaptive, ((),), ()), ('m4', 'm4-wire-golden', lambda r: m4.golden_check(r, output), (), ()), ('m4', 'm4-protocol', m4.protocol, ((),), ('-Wno-unused-function', '-Wno-unused-const-variable')), ('m4', 'm4-common-plane', m4.plane, ((),), ('-Wno-unused-function', '-Wno-unused-const-variable')), From 9065d892529fa1b13a414076712799339429f358 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 00:28:41 +1100 Subject: [PATCH 002/184] drm/apple: preserve takeover brightness and validate desktop VRR requests Accept the first bounded powerlog hint before backlight registration, including a valid zero-nit hint. Otherwise report the same midpoint default as H17P while preserving the loader level. Handle firmware brightness resend requests through the shared policy. Reject unsupported desktop VRR requests before commit, allow the recovery commit to restore fixed refresh, and withdraw the capability after a failed adaptive modeset. Retry a rejected bootstrap at fixed refresh too. Extend the host tests for takeover hints and atomic VRR admission. Align the new documentation with the display maintainer patterns. Assisted-by: LLM Signed-off-by: Ryan Murray --- ...41-panel.rst => apple-dcp-t6041-panel.rst} | 2 + MAINTAINERS | 1 + drivers/gpu/drm/apple/dcp-internal.h | 3 ++ drivers/gpu/drm/apple/dcp.c | 7 ++- drivers/gpu/drm/apple/dcp_backlight.c | 31 +++++++---- drivers/gpu/drm/apple/dcp_backlight.h | 2 +- drivers/gpu/drm/apple/iomfb.c | 18 ++++++- drivers/gpu/drm/apple/iomfb_template.c | 23 +++++--- drivers/gpu/drm/apple/parser_mode_test.c | 14 ++--- drivers/gpu/drm/apple/systemep.c | 5 +- .../drivers/gpu/apple-dcp/display.py | 54 ++++++++++++++++++- .../drivers/gpu/apple-dcp/lifecycle.py | 3 ++ .../selftests/drivers/gpu/apple-dcp/run.py | 1 + 13 files changed, 134 insertions(+), 30 deletions(-) rename Documentation/gpu/{apple-t6041-panel.rst => apple-dcp-t6041-panel.rst} (97%) diff --git a/Documentation/gpu/apple-t6041-panel.rst b/Documentation/gpu/apple-dcp-t6041-panel.rst similarity index 97% rename from Documentation/gpu/apple-t6041-panel.rst rename to Documentation/gpu/apple-dcp-t6041-panel.rst index ba1b9de0f4094..c24ebd1e4bfc4 100644 --- a/Documentation/gpu/apple-t6041-panel.rst +++ b/Documentation/gpu/apple-dcp-t6041-panel.rst @@ -1,3 +1,5 @@ +.. SPDX-License-Identifier: GPL-2.0-only OR MIT + T6041 internal panel =================== diff --git a/MAINTAINERS b/MAINTAINERS index 897becc509f69..517a184d7f5ce 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2062,6 +2062,7 @@ F: Documentation/gpu/apple-dcp-*.rst F: drivers/gpu/drm/apple/ F: drivers/gpu/drm/apple/dcp-fabric* F: tools/testing/selftests/drivers/gpu/apple-afkv2/ +F: tools/testing/selftests/drivers/gpu/apple-dcp/ APPLE PCIE CONTROLLER DRIVER M: Marc Zyngier diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 2f6e107fa43e8..2e45252cb7b84 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -230,6 +230,8 @@ enum dcp_test_point { struct dcp_desktop_state { /* Protected by backlight.lock, independent of framebuffer retirement. */ struct dcp_backlight_swap backlight; + bool inherited_brightness_valid; + u32 inherited_brightness; /* Queued swap; too large for the shared swap union. */ struct dcp_swap_submit_req_v26_6_desktop swap; @@ -296,6 +298,7 @@ struct dcpext_scanout; struct dcp_hdmi; bool dcp_desktop_vrr_supported(struct apple_dcp *dcp); +int dcp_desktop_vrr_check(struct apple_dcp *dcp, struct drm_crtc_state *state); struct apple_dcp { /* Generation-specific wire/transaction state, sharing the common KMS owner. */ diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 03815b0f86e6b..f22d678b5a8d1 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -921,8 +921,8 @@ VISIBLE_IF_KUNIT void dcp_swap_watchdog(struct work_struct *work) READ_ONCE(dcp->vrr_enabled)) { WRITE_ONCE(dcp->desktop->vrr_failed, true); WRITE_ONCE(dcp->vrr_enabled, false); - dcp_backlight_desktop_finish(dcp, - READ_ONCE(dcp->desktop->present_id), true, false); + dcp_backlight_desktop_finish(dcp, READ_ONCE(dcp->desktop->present_id), + true, false); dev_warn(dcp->dev, "adaptive swap timed out, restoring fixed refresh\n"); } dev_warn(dcp->dev, "swap not completed, retraining the display\n"); @@ -1218,6 +1218,9 @@ int dcp_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_commit *commi crtc_state->vrr_enabled) return -EINVAL; + if (dcp_desktop_vrr_check(dcp, crtc_state)) + return -EINVAL; + /* DRM core only caps the blob size; the gamma update copies a full table. */ if (crtc_state->color_mgmt_changed && crtc_state->gamma_lut && drm_color_lut_size(crtc_state->gamma_lut) != dcp_gamma_lut_size(pdev)) diff --git a/drivers/gpu/drm/apple/dcp_backlight.c b/drivers/gpu/drm/apple/dcp_backlight.c index d32f52f11890b..475be119789f1 100644 --- a/drivers/gpu/drm/apple/dcp_backlight.c +++ b/drivers/gpu/drm/apple/dcp_backlight.c @@ -22,9 +22,6 @@ #define MIN_BRIGHTNESS_PART2 103U #define MAX_BRIGHTNESS_PART2 510U -/* Initial level of a desktop 26.x panel, which reports none. */ -#define DCP_DESKTOP_DEFAULT_NITS 250U - /* * lookup for display brightness 2 to 99 nits * */ @@ -129,6 +126,20 @@ int dcp_backlight_takeover(struct apple_dcp *dcp, u32 millinits) if (ret) return ret; + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) { + unsigned long flags; + + /* Freeze the first hint before publishing the class device. */ + spin_lock_irqsave(&dcp->backlight.lock, flags); + if (!dcp->backlight.state.ready && + !dcp->desktop->inherited_brightness_valid) { + dcp->desktop->inherited_brightness = nits; + dcp->desktop->inherited_brightness_valid = true; + } + spin_unlock_irqrestore(&dcp->backlight.lock, flags); + return 0; + } + /* Keep the first bounded hint. Later reports are not user requests. */ return iomfb_configure_backlight_h17p(dcp, maximum, true, nits, false, 0); } @@ -391,7 +402,7 @@ void dcp_backlight_desktop_power(struct apple_dcp *dcp, bool on) /* Reserve only after swap_start succeeds, using its actual firmware ID. */ bool dcp_backlight_desktop_prepare(struct apple_dcp *dcp, u32 id, - bool have_surface, u32 *nits) + bool have_surface, u32 *nits) { unsigned long flags; int ret; @@ -418,7 +429,7 @@ void dcp_backlight_desktop_finish(struct apple_dcp *dcp, u32 id, return; spin_lock_irqsave(&dcp->backlight.lock, flags); finished = dcp_bl_swap_finish(&dcp->backlight.state, - &dcp->desktop->backlight, id, reply, accepted); + &dcp->desktop->backlight, id, reply, accepted); delay = dcp_bl_retry_delay(&dcp->backlight.state); pending = dcp->backlight.state.dirty; spin_unlock_irqrestore(&dcp->backlight.lock, flags); @@ -662,13 +673,13 @@ int dcp_backlight_register(struct apple_dcp *dcp) if (!dcp->brightness.maximum || dcp->brightness.maximum > INT_MAX) return -ERANGE; props.max_brightness = dcp->brightness.maximum; - if (!props.brightness) - props.brightness = min_t(u32, DCP_DESKTOP_DEFAULT_NITS, - props.max_brightness); + /* Match the Neo's reporting default without presenting a guess. */ + props.brightness = props.max_brightness / 2; spin_lock_irqsave(&dcp->backlight.lock, flags); if (!dcp->backlight.state.ready) { - int ret = dcp_bl_init(&dcp->backlight.state, - props.max_brightness, false, 0, + int ret = dcp_bl_init(&dcp->backlight.state, props.max_brightness, + dcp->desktop->inherited_brightness_valid, + dcp->desktop->inherited_brightness, true, props.brightness); if (ret) { diff --git a/drivers/gpu/drm/apple/dcp_backlight.h b/drivers/gpu/drm/apple/dcp_backlight.h index a1ac47f166678..6585e58436e2a 100644 --- a/drivers/gpu/drm/apple/dcp_backlight.h +++ b/drivers/gpu/drm/apple/dcp_backlight.h @@ -319,7 +319,7 @@ struct apple_dcp; void dcp_backlight_desktop_power(struct apple_dcp *dcp, bool on); bool dcp_backlight_desktop_prepare(struct apple_dcp *dcp, u32 id, - bool have_surface, u32 *nits); + bool have_surface, u32 *nits); void dcp_backlight_desktop_finish(struct apple_dcp *dcp, u32 id, bool reply, bool accepted); diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 2c93f24d5a006..6d5e91fa3fe35 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -102,6 +102,21 @@ bool dcp_desktop_vrr_supported(struct apple_dcp *dcp) !READ_ONCE(dcp->desktop->vrr_failed); } +int dcp_desktop_vrr_check(struct apple_dcp *dcp, struct drm_crtc_state *state) +{ + struct dcp_display_mode mode; + + if (dcp->fw_compat != DCP_FIRMWARE_V_26_6_DESKTOP || !state->vrr_enabled) + return 0; + /* Let the recovery commit re-apply an existing VRR state at fixed refresh. */ + if (READ_ONCE(dcp->desktop->vrr_failed)) + return 0; + if (!dcp_desktop_vrr_supported(dcp) || + !lookup_mode(dcp, &state->mode, &mode) || !mode.vrr) + return -EINVAL; + return 0; +} + static bool dcp_mode_vrr_supported(struct apple_dcp *dcp, const struct dcp_display_mode *mode) { @@ -2006,7 +2021,8 @@ static int dcp_desktop_modeset(struct apple_dcp *dcp, if (d->trial_modeset_ok) { d->trial_modeset_ok = false; - if (!crtc_state->vrr_enabled && d->mode_live && lookup_mode(dcp, &crtc_state->mode, &matched) && + if (!crtc_state->vrr_enabled && d->mode_live && + lookup_mode(dcp, &crtc_state->mode, &matched) && matched.timing_mode_id == d->trial_timing_mode_id && matched.color_mode_id == d->trial_color_mode_id) { dev_dbg(dcp->dev, "keeping the takeover mode " DRM_MODE_FMT "\n", diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 3d9465ee5561e..2123666a21cab 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -1140,7 +1140,11 @@ dcpep_cb_read_edt_data(struct apple_dcp *dcp, struct dcp_read_edt_data_req *req) static void iomfbep_cb_enable_backlight_message_ap_gated(struct apple_dcp *dcp, u8 *enabled) { -#if DCP_FW_H17(DCP_FW_VER) +#if DCP_FW_DESKTOP_26(DCP_FW_VER) + if (dcp_has_panel(dcp) && dcp_backlight_resend(dcp)) + schedule_work(&dcp->bl_update_wq); + return; +#elif DCP_FW_H17(DCP_FW_VER) /* H17P carries the level only in presents; ask for one more. */ if (dcp->hw.iomfb_method_profile != DCP_IOMFB_METHODS_H17G) { if (dcp_has_panel(dcp) && dcp_backlight_active(dcp) && @@ -2634,7 +2638,7 @@ static void dcp_swap_started(struct apple_dcp *dcp, void *data, void *cookie) for (unsigned int i = 0; i < SWAP_SURFACES; i++) have_surface |= !DCP_FW_SWAP(dcp).surf_null[i]; if (dcp_backlight_desktop_prepare(dcp, resp->swap_id, - have_surface, &nits)) + have_surface, &nits)) dcp_swap_set_brightness(&DCP_FW_SWAP(dcp).swap, nits); } #endif @@ -2917,7 +2921,7 @@ static void dcp_modeset_after_bootstrap(struct apple_dcp *dcp, void *data, struct dcp_modeset_cookie *modeset = cookie; if (!data || get_unaligned_le32(data)) { - complete_set_digital_out_mode(dcp, data, cookie); + dcp_modeset_actual_done(dcp, data, cookie); return; } dcp_get_mode_info(dcp, &modeset->actual, dcp_modeset_after_actual_info, @@ -3106,6 +3110,8 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, kref_put(&cookie->refcount, release_wait_cookie); dcp->desktop->mode_live = !ret; + if (READ_ONCE(dcp->desktop->vrr_failed) && dcp->connector) + dcp_queue_hotplug(dcp->connector); return ret; } #else @@ -3364,9 +3370,14 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru } #if DCP_FW_DESKTOP_26(DCP_FW_VER) - /* Types 1, 2 and 7 carry the same desktop presentation deadline. */ - req->swap.timestamp[0] = req->swap.timestamp[1] = req->swap.timestamp[6] = - dcp_desktop_present_deadline(has_surface, dcp->use_timestamps); + { + u64 deadline = dcp_desktop_present_deadline(has_surface, dcp->use_timestamps); + + /* Types 1, 2 and 7 carry the same desktop presentation deadline. */ + req->swap.timestamp[0] = deadline; + req->swap.timestamp[1] = deadline; + req->swap.timestamp[6] = deadline; + } #else if (has_surface && (dcp->use_timestamps || dcp->vrr_enabled)) { u64 submit_timestamp = dcp->swap_submit_timestamp; diff --git a/drivers/gpu/drm/apple/parser_mode_test.c b/drivers/gpu/drm/apple/parser_mode_test.c index 31b248c6fff87..877d012740df1 100644 --- a/drivers/gpu/drm/apple/parser_mode_test.c +++ b/drivers/gpu/drm/apple/parser_mode_test.c @@ -46,21 +46,21 @@ static void dcp_refresh_range_test(struct kunit *test) u32 low = 0, high = 0; KUNIT_EXPECT_TRUE(test, dcp_refresh_range(120U << 16, 0, 0, - true, &low, &high)); + true, &low, &high)); KUNIT_EXPECT_EQ(test, low, 24U << 16); KUNIT_EXPECT_EQ(test, high, 120U << 16); KUNIT_EXPECT_FALSE(test, dcp_refresh_range(60U << 16, 0, 0, - true, &low, &high)); + true, &low, &high)); KUNIT_EXPECT_FALSE(test, dcp_refresh_range(120U << 16, 0, 0, - false, &low, &high)); + false, &low, &high)); KUNIT_EXPECT_FALSE(test, dcp_refresh_range(120U << 16, 0, 120U << 16, - true, &low, &high)); + true, &low, &high)); KUNIT_EXPECT_FALSE(test, dcp_refresh_range(120U << 16, 24U << 16, - 144U << 16, true, &low, &high)); + 144U << 16, true, &low, &high)); KUNIT_EXPECT_FALSE(test, dcp_refresh_range(120U << 16, 120U << 16, - 24U << 16, true, &low, &high)); + 24U << 16, true, &low, &high)); KUNIT_EXPECT_TRUE(test, dcp_refresh_range(120U << 16, 48U << 16, - 120U << 16, true, &low, &high)); + 120U << 16, true, &low, &high)); KUNIT_EXPECT_EQ(test, low, 48U << 16); KUNIT_EXPECT_EQ(test, dcp_desktop_present_deadline(true, true), 120ULL); KUNIT_EXPECT_EQ(test, dcp_desktop_present_deadline(false, true), 0ULL); diff --git a/drivers/gpu/drm/apple/systemep.c b/drivers/gpu/drm/apple/systemep.c index a32f0a0bcf6a0..b8576aec5c6f5 100644 --- a/drivers/gpu/drm/apple/systemep.c +++ b/drivers/gpu/drm/apple/systemep.c @@ -100,8 +100,9 @@ static int powerlog_report(struct apple_epic_service *service, enum epic_subtype dev_dbg(dcp->dev, "systemep: mNits event: Nits: %u.%03u, iDAC: %u\n", mnits.millinits / 1000, mnits.millinits % 1000, mnits.idac); - if (dcp->fw_compat == DCP_FIRMWARE_H17P && - dcp->hw.iomfb_method_profile != DCP_IOMFB_METHODS_H17G) { + if ((dcp->fw_compat == DCP_FIRMWARE_H17P && + dcp->hw.iomfb_method_profile != DCP_IOMFB_METHODS_H17G) || + dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) { if (!dcp_has_panel(dcp)) return 0; ret = dcp_backlight_takeover(dcp, mnits.millinits); diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/display.py b/tools/testing/selftests/drivers/gpu/apple-dcp/display.py index fc76e557ee5a3..8ead9446fbe9b 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/display.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/display.py @@ -194,12 +194,13 @@ def adaptive(root): 'static void dcp_modeset_sync(')) cookie=block(source,'struct dcp_modeset_cookie {')+';\n' main=(root/'drivers/gpu/drm/apple/iomfb.c').read_text() - gate=block(main,'bool dcp_desktop_vrr_supported(') + gate=block(main,'bool dcp_desktop_vrr_supported(') + block(main,'int dcp_desktop_vrr_check(') return r''' #include #include #include #include +#include typedef uint32_t u32; typedef uint64_t u64; #define READ_ONCE(x) (x) @@ -212,6 +213,11 @@ def adaptive(root): struct dcp_set_parameter_dcp { u32 param; u64 value[4]; u32 count; }; struct apple_dcp {void *dev;int fw_compat;bool main_display,panel;struct {bool vrr_failed;} *desktop;}; static bool t6041_vrr; +struct drm_display_mode {int id;}; +struct drm_crtc_state {bool vrr_enabled;struct drm_display_mode mode;}; +struct dcp_display_mode {bool vrr;}; +static bool lookup_mode(struct apple_dcp *d,struct drm_display_mode *m,struct dcp_display_mode *out) +{out->vrr=m->id==120;return m->id==60 || m->id==120;} static bool dcp_has_panel(struct apple_dcp *d) {return d->panel;} static u32 get_unaligned_le32(const void *p) {u32 v;memcpy(&v,p,4);return v;} typedef void (*callback)(struct apple_dcp *,void *,void *); @@ -243,7 +249,13 @@ def adaptive(root): { struct apple_dcp d={.fw_compat=DCP_FIRMWARE_V_26_6_DESKTOP,.main_display=true,.panel=true}; __typeof__(*d.desktop) desktop={0};d.desktop=&desktop; + struct drm_crtc_state state={.vrr_enabled=true,.mode={120}}; + assert(dcp_desktop_vrr_check(&d,&state)==-EINVAL); assert(!dcp_desktop_vrr_supported(&d));t6041_vrr=true; + assert(!dcp_desktop_vrr_check(&d,&state)); + state.mode.id=60;assert(dcp_desktop_vrr_check(&d,&state)==-EINVAL); + state.vrr_enabled=false;assert(!dcp_desktop_vrr_check(&d,&state)); + state.vrr_enabled=true;state.mode.id=120; assert(dcp_desktop_vrr_supported(&d)); d.main_display=false;assert(!dcp_desktop_vrr_supported(&d));d.main_display=true; d.panel=false;assert(!dcp_desktop_vrr_supported(&d));d.panel=true; @@ -256,6 +268,7 @@ def adaptive(root): /* Rejected enable is cleared before a fixed timing is selected. */ initialize(&m);dcp_modeset_sync(&d,&m);reply(&d,1,&error); assert(desktop.vrr_failed && !dcp_desktop_vrr_supported(&d) && !m.vrr && !parameter.value[0]); + assert(!dcp_desktop_vrr_check(&d,&state)); /* Recovery re-applies fixed refresh. */ reply(&d,1,&zero);selected(&d,&m); /* A rejected modeset gets one fixed-refresh retry. */ desktop.vrr_failed=false;initialize(&m);dcp_modeset_sync(&d,&m); @@ -276,3 +289,42 @@ def adaptive(root): return 0; } ''' + + +def takeover(root): + prefix=backlight(root).split('static void completed(')[0] + prefix=prefix.replace('static inline ', 'static inline __attribute__((unused)) ') + function=block((root/'drivers/gpu/drm/apple/dcp_backlight.c').read_text(), + 'int dcp_backlight_takeover(') + return prefix + r''' +#define DCP_FIRMWARE_V_26_6_DESKTOP 5 +#define READ_ONCE(x) (x) +#define spin_lock_irqsave(p,f) do {assert(!*(p));*(p)=true;(f)=0;} while(0) +#define spin_unlock_irqrestore(p,f) do {assert(*(p));*(p)=false;(void)(f);} while(0) +struct apple_dcp { + int fw_compat; + struct {u32 maximum;} brightness; + struct {bool lock;struct dcp_backlight_state state;} backlight; + struct {bool inherited_brightness_valid;u32 inherited_brightness;} *desktop; +}; +static unsigned legacy; +bool dcp_backlight_active(struct apple_dcp *d) {return d->fw_compat==1 && d->backlight.state.ready;} +static int iomfb_configure_backlight_h17p(struct apple_dcp *d,u32 max,bool valid,u32 nits,bool fallback,u32 guessed) +{assert(max==600 && valid && nits==100 && !fallback && !guessed);legacy++;return 0;} +''' + function + r''' +int main(void) { + struct apple_dcp d={.fw_compat=5,.brightness={600}}; + __typeof__(*d.desktop) saved={0};d.desktop=&saved; + assert(!dcp_backlight_takeover(&d,0)); + assert(saved.inherited_brightness_valid && !saved.inherited_brightness && !legacy); + assert(!dcp_backlight_takeover(&d,500000) && !saved.inherited_brightness); + saved.inherited_brightness_valid=false; + assert(dcp_backlight_takeover(&d,1300000)==-ERANGE && !saved.inherited_brightness_valid); + assert(!dcp_backlight_takeover(&d,700000) && saved.inherited_brightness==600); + d.backlight.state.ready=true; saved.inherited_brightness_valid=false; + assert(!dcp_backlight_takeover(&d,100000) && !saved.inherited_brightness_valid); + d.fw_compat=1;d.backlight.state.ready=false; + assert(!dcp_backlight_takeover(&d,100000) && legacy==1); + return 0; +} +''' diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py b/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py index d7485a6681747..582156af8ca4d 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py @@ -1709,6 +1709,9 @@ def vrr(root): { (void)blob; assert(!"gamma LUT without a colour-management change"); return 0; } static unsigned int __attribute__((unused)) dcp_gamma_lut_size(struct platform_device *p) { (void)p; assert(!"gamma LUT without a colour-management change"); return 0; } +/* Desktop admission is covered by panel-adaptive-fallback. */ +static int dcp_desktop_vrr_check(struct apple_dcp *d, struct drm_crtc_state *s) +{ (void)d; (void)s; return 0; } /* Route-follow is for Type-C displays; this DCP has no Type-C routes. */ static bool __attribute__((unused)) typec_follow_crtc = true; static int __attribute__((unused)) dcp_typec_follow_check(struct apple_dcp *d, struct drm_crtc *c, diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/run.py b/tools/testing/selftests/drivers/gpu/apple-dcp/run.py index 6913134d2a006..265dfb9450a80 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/run.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/run.py @@ -53,6 +53,7 @@ def main(): 'failed-flip-return', 'live-park')) tests = [ ('display', 'panel-backlight-ramp', display.backlight, ((),), ()), + ('display', 'panel-brightness-takeover', display.takeover, ((),), ()), ('display', 'panel-mode-catalog', display.modes, ((),), ()), ('display', 'panel-refresh-range', display.refresh, ((),), ()), ('display', 'panel-adaptive-fallback', display.adaptive, ((),), ()), From 9df36a2c42b6f39a015ead5607b39ed2e157631c Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 00:29:56 +1100 Subject: [PATCH 003/184] drm/apple: keep ProMotion deadlines independent of VRR admission A malformed adaptive-sync range must leave the fixed 120 Hz timing usable. Keep its presentation-deadline requirement separate from VRR capability, and distinguish omitted range fields from explicit zeros. Notify capability withdrawal once per adaptive-sync failure so the fixed-refresh recovery does not generate repeated hotplug notifications. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/gpu/drm/apple/dcp-refresh.h | 11 ++++++++--- drivers/gpu/drm/apple/iomfb_template.c | 8 +++++--- drivers/gpu/drm/apple/parser.c | 4 +++- drivers/gpu/drm/apple/parser.h | 2 ++ .../selftests/drivers/gpu/apple-dcp/display.py | 5 ++++- 5 files changed, 22 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp-refresh.h b/drivers/gpu/drm/apple/dcp-refresh.h index eea5058cc3645..1b26758b6ff7b 100644 --- a/drivers/gpu/drm/apple/dcp-refresh.h +++ b/drivers/gpu/drm/apple/dcp-refresh.h @@ -5,13 +5,18 @@ #include +/* Accept nominal 120 Hz and its 1000/1001 fractional timing. */ +static inline bool dcp_refresh_is_promotion(u32 rate) +{ + return rate >= (120U << 16) - (120U << 16) / 500 && + rate <= (120U << 16); +} + /* All rates are unsigned 16.16 Hz, including fractional fixed timings. */ static inline bool dcp_refresh_range(u32 rate, u32 minimum, u32 maximum, bool internal, u32 *low, u32 *high) { - if (!minimum && !maximum && internal && - rate >= (120U << 16) - (120U << 16) / 500 && - rate <= (120U << 16)) { + if (!minimum && !maximum && internal && dcp_refresh_is_promotion(rate)) { minimum = 24U << 16; maximum = rate; } diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 2123666a21cab..2707fa6d80e7a 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -2851,6 +2851,7 @@ struct dcp_modeset_cookie { bool use_bootstrap; bool vrr; bool program_sync; + bool notify_vrr_failure; u32 min_vrr; }; @@ -2887,6 +2888,7 @@ static void dcp_modeset_fixed_fallback(struct apple_dcp *dcp, dev_warn(dcp->dev, "adaptive sync failed, restoring fixed refresh\n"); WRITE_ONCE(dcp->desktop->vrr_failed, true); modeset->vrr = false; + modeset->notify_vrr_failure = true; dcp_modeset_sync(dcp, modeset); } @@ -3026,7 +3028,7 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, }; /* ProMotion needs desktop timestamps in fixed-120 mode too. */ - dcp->use_timestamps = mode->vrr; + dcp->use_timestamps = mode->needs_timestamps; dcp->desktop->vrr_requested = crtc_state->vrr_enabled; /* @@ -3107,11 +3109,11 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, ret = 0; } dcp->vrr_enabled = !ret && modeset->vrr; + if (modeset->notify_vrr_failure && dcp->connector) + dcp_queue_hotplug(dcp->connector); kref_put(&cookie->refcount, release_wait_cookie); dcp->desktop->mode_live = !ret; - if (READ_ONCE(dcp->desktop->vrr_failed) && dcp->connector) - dcp_queue_hotplug(dcp->connector); return ret; } #else diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index 612232bd394df..ab84a8522ed73 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -878,8 +878,10 @@ static int parse_mode(struct dcp_parse_ctx *handle, /* External H17P keeps fixed timings without adaptive-sync support. */ if (!external_sdr && !vrr_ambiguous) out->vrr = dcp_refresh_range(vert.precise_sync_rate, min_vrr, - max_vrr, internal, + max_vrr, internal && !vrr_seen, &out->min_vrr, &out->max_vrr); + out->needs_timestamps = out->vrr || + (internal && dcp_refresh_is_promotion(vert.precise_sync_rate)); clock = calculate_clock(&horiz, &vert); if (!clock || clock > INT_MAX) diff --git a/drivers/gpu/drm/apple/parser.h b/drivers/gpu/drm/apple/parser.h index 97a75a8b046a0..ef9420c1109f9 100644 --- a/drivers/gpu/drm/apple/parser.h +++ b/drivers/gpu/drm/apple/parser.h @@ -120,6 +120,8 @@ struct dcp_display_mode { struct dcp_color_mode sdr; struct dcp_color_mode best; bool vrr; + /* ProMotion fixed timings still need presentation deadlines. */ + bool needs_timestamps; /* IsPreferred, outside the external SDR path */ bool preferred; bool metadata_timing_valid; diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/display.py b/tools/testing/selftests/drivers/gpu/apple-dcp/display.py index 8ead9446fbe9b..e047a65723d68 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/display.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/display.py @@ -115,6 +115,9 @@ def case(elements, expected, ranges=None): f'assert(m[{j}].timing_mode_id=={elements[j]["ID"]}); ' f'assert(m[{j}].mode.htotal==2200 && m[{j}].mode.vtotal==1125); ' f'assert(m[{j}].mode.flags==(DRM_MODE_FLAG_PHSYNC|DRM_MODE_FLAG_PVSYNC)); ') + for j, hz in enumerate(expected): + if hz >= 119.88: + check += f'assert(m[{j}].needs_timestamps); ' if ranges: for j, (lo, hi) in enumerate(ranges): check += f'assert(m[{j}].vrr=={int(bool(lo))}); ' @@ -135,7 +138,7 @@ def case(elements, expected, ranges=None): m=copy.deepcopy(supported[0]); m['VerticalAttributes']['PreciseSyncRate']=round(hz*65536) case([m], [hz], [(24<<16, round(hz*65536))]) for lo, hi, valid in ((48<<16,120<<16,True), (0,120<<16,False), - (60<<16,0,False), (120<<16,24<<16,False), + (60<<16,0,False), (0,0,False), (120<<16,24<<16,False), (24<<16,144<<16,False), (120<<16,120<<16,False)): m=copy.deepcopy(supported[0]); m['MinimumVariableRefreshRate']=lo; m['MaximumVariableRefreshRate']=hi case([m],[120],[(lo,hi) if valid else (0,0)]) From ff522318232732dc8c63c08e36736284d298c916 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 00:34:42 +1100 Subject: [PATCH 004/184] drm/apple: isolate late desktop completions during VRR recovery Let the receive callback and watchdog claim each desktop swap once. Track clear swaps too, ignore late or duplicate completions, and retain buffers displaced by a dropped swap until a later swap completes. Test watchdog recovery and stale completion isolation in the host runner and exercise desktop completion claims in KUnit. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/gpu/drm/apple/dcp-internal.h | 1 + drivers/gpu/drm/apple/dcp.c | 22 +++-- drivers/gpu/drm/apple/dcp_dropped_swap_test.c | 22 +++++ drivers/gpu/drm/apple/iomfb_template.c | 10 ++- .../selftests/drivers/gpu/apple-dcp/README | 4 +- .../drivers/gpu/apple-dcp/display.py | 84 +++++++++++++++++++ .../selftests/drivers/gpu/apple-dcp/run.py | 1 + 7 files changed, 133 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 2e45252cb7b84..f3c8ec3cc2fa5 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -297,6 +297,7 @@ struct dcp_desktop_state { struct dcpext_scanout; struct dcp_hdmi; +bool dcp_desktop_present_complete(struct apple_dcp *dcp, u32 id); bool dcp_desktop_vrr_supported(struct apple_dcp *dcp); int dcp_desktop_vrr_check(struct apple_dcp *dcp, struct drm_crtc_state *state); diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index f22d678b5a8d1..67f8aba5a5acd 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -891,6 +891,13 @@ static struct apple_dcp_afkep *dcp_afkep(struct apple_dcp *dcp, u8 endpoint) } } +/* RX and the watchdog may race to finish one present, never both. */ +bool dcp_desktop_present_complete(struct apple_dcp *dcp, u32 id) +{ + return id == READ_ONCE(dcp->desktop->present_id) && + xchg(&dcp->desktop->present_pending, false); +} + #define DCP_SWAP_WATCHDOG_MS 1000 #define DCP_SWAP_WATCHDOG_RETRAINS 5 @@ -909,10 +916,15 @@ VISIBLE_IF_KUNIT void dcp_swap_watchdog(struct work_struct *work) container_of(to_delayed_work(work), struct apple_dcp, swap_watchdog_wq); - if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && - (!READ_ONCE(dcp->desktop->present_pending) || - time_before(jiffies, dcp->swap_watchdog_deadline))) - return; + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) { + if (time_before(jiffies, dcp->swap_watchdog_deadline) || + !xchg(&dcp->desktop->present_pending, false)) + return; + /* Retain displaced buffers until a later present really completes. */ + dcp_unarm_retained_framebuffers(dcp, READ_ONCE(dcp->desktop->present_id)); + dcp_backlight_desktop_finish(dcp, READ_ONCE(dcp->desktop->present_id), + true, false); + } if (!iomfb_retire_dropped_present_h17p(dcp)) return; @@ -921,8 +933,6 @@ VISIBLE_IF_KUNIT void dcp_swap_watchdog(struct work_struct *work) READ_ONCE(dcp->vrr_enabled)) { WRITE_ONCE(dcp->desktop->vrr_failed, true); WRITE_ONCE(dcp->vrr_enabled, false); - dcp_backlight_desktop_finish(dcp, READ_ONCE(dcp->desktop->present_id), - true, false); dev_warn(dcp->dev, "adaptive swap timed out, restoring fixed refresh\n"); } dev_warn(dcp->dev, "swap not completed, retraining the display\n"); diff --git a/drivers/gpu/drm/apple/dcp_dropped_swap_test.c b/drivers/gpu/drm/apple/dcp_dropped_swap_test.c index b45010d38fac1..cd11dbc374aa8 100644 --- a/drivers/gpu/drm/apple/dcp_dropped_swap_test.c +++ b/drivers/gpu/drm/apple/dcp_dropped_swap_test.c @@ -497,7 +497,29 @@ static void retired_completion_in_flight_test(struct kunit *test) KUNIT_EXPECT_TRUE(test, READ_ONCE(dcp->crashed)); } +static void desktop_present_completion_test(struct kunit *test) +{ + struct apple_dcp *dcp = kunit_kzalloc(test, sizeof(*dcp), GFP_KERNEL); + + KUNIT_ASSERT_NOT_NULL(test, dcp); + dcp->desktop = kunit_kzalloc(test, sizeof(*dcp->desktop), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dcp->desktop); + dcp->desktop->present_id = 42; + dcp->desktop->present_pending = true; + KUNIT_EXPECT_FALSE(test, dcp_desktop_present_complete(dcp, 41)); + KUNIT_EXPECT_TRUE(test, dcp->desktop->present_pending); + KUNIT_EXPECT_TRUE(test, dcp_desktop_present_complete(dcp, 42)); + KUNIT_EXPECT_FALSE(test, dcp_desktop_present_complete(dcp, 42)); + /* A late completion cannot claim the replacement's event. */ + dcp->desktop->present_id = 43; + dcp->desktop->present_pending = true; + KUNIT_EXPECT_FALSE(test, dcp_desktop_present_complete(dcp, 42)); + KUNIT_EXPECT_TRUE(test, dcp->desktop->present_pending); + KUNIT_EXPECT_TRUE(test, dcp_desktop_present_complete(dcp, 43)); +} + static struct kunit_case dropped_swap_cases[] = { + KUNIT_CASE(desktop_present_completion_test), KUNIT_CASE(dropped_present_retired_test), KUNIT_CASE(dropped_present_buffers_test), KUNIT_CASE(completed_present_kept_test), diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 2707fa6d80e7a..234debba0c7dd 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -461,8 +461,9 @@ static void dcpep_cb_swap_complete(struct apple_dcp *dcp, retire = iomfb_present_complete_h17p(dcp); #endif #if DCP_FW_DESKTOP_26(DCP_FW_VER) - if (resp->swap_id == READ_ONCE(dcp->desktop->present_id)) - WRITE_ONCE(dcp->desktop->present_pending, false); + /* Late or duplicate completions cannot retire the replacement's flip. */ + if (!dcp_desktop_present_complete(dcp, resp->swap_id)) + return; dcp_backlight_desktop_finish(dcp, resp->swap_id, false, true); #endif trace_iomfb_swap_complete(dcp, resp->swap_id); @@ -1744,6 +1745,9 @@ static bool dcp_present_begin(struct apple_dcp *dcp, u32 swap_id) dcp->crashed = true; return false; } +#elif DCP_FW_DESKTOP_26(DCP_FW_VER) + WRITE_ONCE(dcp->desktop->present_id, swap_id); + WRITE_ONCE(dcp->desktop->present_pending, true); #endif return true; } @@ -2629,8 +2633,6 @@ static void dcp_swap_started(struct apple_dcp *dcp, void *data, void *cookie) dcp_arm_retained_framebuffers(dcp, resp->swap_id); #if DCP_FW_DESKTOP_26(DCP_FW_VER) - WRITE_ONCE(dcp->desktop->present_id, resp->swap_id); - WRITE_ONCE(dcp->desktop->present_pending, true); if (dcp_has_panel(dcp)) { u32 nits; bool have_surface = false; diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/README b/tools/testing/selftests/drivers/gpu/apple-dcp/README index 24437d55edfd8..f7de1b1179c1d 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/README +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/README @@ -166,6 +166,8 @@ blanking, suspend, rejected submits and both submit/completion callback orders. Synthetic TimingElements exercise 120 Hz and fractional lower refresh rates, DCP color/timing identities, invalid geometry and adaptive-sync ranges. The adaptive-sync callback test verifies the desktop parameter record, mode-info -ordering and one fixed-refresh retry after a rejected enable or modeset. +ordering and one fixed-refresh retry after a rejected enable or modeset. The +watchdog test checks fixed-refresh recovery, retained-buffer arming and late +completion isolation. These tests do not establish which timings a particular panel reports or prove refresh rate, flicker, brightness smoothness or adaptive-sync on hardware. diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/display.py b/tools/testing/selftests/drivers/gpu/apple-dcp/display.py index e047a65723d68..8ba1f95206837 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/display.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/display.py @@ -331,3 +331,87 @@ def takeover(root): return 0; } ''' + + +def watchdog(root): + source=(root/'drivers/gpu/drm/apple/dcp.c').read_text() + production='\n'.join(block(source, marker) for marker in ( + 'bool dcp_desktop_present_complete(', + 'VISIBLE_IF_KUNIT void dcp_swap_watchdog(', + 'void dcp_swap_watchdog_arm(', + 'void dcp_swap_watchdog_complete(')) + definitions='\n'.join(line for line in source.splitlines() + if line.startswith('#define DCP_SWAP_WATCHDOG_')) + return r''' +#include +#include +#include +#include +typedef uint32_t u32; +#define READ_ONCE(x) (x) +#define WRITE_ONCE(x,v) ((x)=(v)) +#define xchg(p,v) __atomic_exchange_n((p),(v),__ATOMIC_SEQ_CST) +#define VISIBLE_IF_KUNIT +#define DCP_FIRMWARE_V_26_6_DESKTOP 5 +#define dev_warn(...) ((void)0) +#define container_of(p,t,m) ((t *)((char *)(p)-offsetof(t,m))) +#define to_delayed_work(p) container_of(p,struct delayed_work,work) +#define msecs_to_jiffies(n) (n) +#define time_before(a,b) ((long)((a)-(b))<0) +#define system_dfl_wq NULL +static unsigned long jiffies; +struct work_struct {unsigned unused;}; +struct delayed_work {struct work_struct work;}; +struct apple_dcp { + int fw_compat;bool vrr_enabled,typec; + struct {u32 present_id;bool present_pending,vrr_failed;} *desktop; + struct {bool valid;} mode_state; + struct delayed_work swap_watchdog_wq; + unsigned long swap_watchdog_deadline; + unsigned swap_watchdog_retrains; + void *crtc,*connector,*dev; +}; +static unsigned scheduled,cancelled,vblanks,hotplugs,unarmed,rejected; +static bool allow_retire=true; +static bool iomfb_retire_dropped_present_h17p(struct apple_dcp *d) {return allow_retire;} +static void dcp_unarm_retained_framebuffers(struct apple_dcp *d,u32 id) +{assert(id==d->desktop->present_id);unarmed++;} +static void dcp_backlight_desktop_finish(struct apple_dcp *d,u32 id,bool reply,bool accepted) +{assert(id==d->desktop->present_id && reply && !accepted);rejected++;} +static void dcp_mode_invalidate(__typeof__(((struct apple_dcp *)0)->mode_state) *m) {m->valid=false;} +static void dcp_drm_crtc_vblank(void *c) {vblanks++;} +static void dcp_queue_hotplug(void *c) {hotplugs++;} +static bool dcp_is_typec_output(struct apple_dcp *d) {return d->typec;} +static void mod_delayed_work(void *q,struct delayed_work *w,unsigned long delay) +{assert(delay==1000);scheduled++;} +static void cancel_delayed_work(struct delayed_work *w) {cancelled++;} +''' + definitions + '\n' + production + r''' +int main(void) { + struct apple_dcp d={.fw_compat=5,.vrr_enabled=true,.mode_state={true},.connector=(void *)1}; + __typeof__(*d.desktop) state={.present_id=42,.present_pending=true};d.desktop=&state; + dcp_swap_watchdog_arm(&d);assert(scheduled==1 && d.swap_watchdog_deadline==1000); + jiffies=999;dcp_swap_watchdog(&d.swap_watchdog_wq.work); + assert(d.mode_state.valid && !hotplugs && !unarmed && state.present_pending); + assert(!dcp_desktop_present_complete(&d,41) && state.present_pending); + assert(dcp_desktop_present_complete(&d,42) && !state.present_pending); + assert(!dcp_desktop_present_complete(&d,42)); + dcp_swap_watchdog_complete(&d);assert(cancelled==1); + jiffies=1000;dcp_swap_watchdog(&d.swap_watchdog_wq.work);assert(!hotplugs); + /* Expiry owns this dropped swap, retains its buffers and triggers reset. */ + state.present_id=43;state.present_pending=true; + dcp_swap_watchdog(&d.swap_watchdog_wq.work); + assert(state.vrr_failed && !d.vrr_enabled && !state.present_pending); + assert(!d.mode_state.valid && hotplugs==1 && vblanks==1 && unarmed==1 && rejected==1); + assert(!dcp_desktop_present_complete(&d,43)); + /* A late completion cannot finish the new fixed-refresh present. */ + state.present_id=44;state.present_pending=true; + assert(!dcp_desktop_present_complete(&d,43) && state.present_pending); + assert(dcp_desktop_present_complete(&d,44)); + unsigned before=scheduled;dcp_swap_watchdog_arm(&d);assert(scheduled==before); + /* The established external watchdog remains armed on a Type-C output. */ + d.fw_compat=1;d.typec=true;dcp_swap_watchdog_arm(&d);assert(scheduled==before+1); + allow_retire=false;d.mode_state.valid=true; + dcp_swap_watchdog(&d.swap_watchdog_wq.work);assert(d.mode_state.valid && hotplugs==1); + return 0; +} +''' diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/run.py b/tools/testing/selftests/drivers/gpu/apple-dcp/run.py index 265dfb9450a80..134b62e97b07e 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/run.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/run.py @@ -57,6 +57,7 @@ def main(): ('display', 'panel-mode-catalog', display.modes, ((),), ()), ('display', 'panel-refresh-range', display.refresh, ((),), ()), ('display', 'panel-adaptive-fallback', display.adaptive, ((),), ()), + ('display', 'panel-adaptive-watchdog', display.watchdog, ((),), ()), ('m4', 'm4-wire-golden', lambda r: m4.golden_check(r, output), (), ()), ('m4', 'm4-protocol', m4.protocol, ((),), ('-Wno-unused-function', '-Wno-unused-const-variable')), ('m4', 'm4-common-plane', m4.plane, ((),), ('-Wno-unused-function', '-Wno-unused-const-variable')), From 30068aecfed9eb6e7f40b1cbd00a221c30dde9ca Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 00:40:32 +1100 Subject: [PATCH 005/184] drm/apple: omit legacy brightness staging from desktop flush Desktop brightness is reserved and encoded after swap_start. Compile the legacy update flag only in the instances that consume it, fixing the unused-variable warning with WERROR enabled. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/gpu/drm/apple/iomfb_template.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 234debba0c7dd..0bddf9fa0aba6 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -3273,7 +3273,10 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru struct DCP_FW_NAME(dcp_swap_submit_req) *req = &DCP_FW_SWAP(dcp); int plane_idx, l; int has_surface = 0; + +#if !DCP_FW_DESKTOP_26(DCP_FW_VER) bool update_brightness; +#endif crtc_state = drm_atomic_get_new_crtc_state(commit, crtc); @@ -3409,16 +3412,16 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru /* These fields should be set together */ req->swap.swap_completed = req->swap.swap_enabled; +#if DCP_FW_DESKTOP_26(DCP_FW_VER) + /* The shared ramp is encoded after swap_start, at submission time. */ + dcp->brightness.update = false; +#else /* update brightness if changed */ update_brightness = dcp_has_panel(dcp) && dcp->brightness.update; #if DCP_FW_H17(DCP_FW_VER) update_brightness = update_brightness && dcp->hw.iomfb_method_profile == DCP_IOMFB_METHODS_H17G; #endif -#if DCP_FW_DESKTOP_26(DCP_FW_VER) - /* The shared ramp is encoded after swap_start, at submission time. */ - dcp->brightness.update = false; -#else if (update_brightness) { #if DCP_FW_LAYOUT_14_7(DCP_FW_VER) req->swap.bl_update = 1; From 7bbacbe6595bb78fcb9a1e932fe456d5d9be387c Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 00:45:32 +1100 Subject: [PATCH 006/184] drm/apple: accept nominal ProMotion rates either side of 120 Hz Keep desktop presentation deadlines for reported rates just above 120 Hz too. The fixed timing remains the rate DCP supplied; this only classifies its ProMotion presentation requirement. Extend the real parser fixture with a slightly higher nominal timing. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/gpu/drm/apple/dcp-refresh.h | 2 +- tools/testing/selftests/drivers/gpu/apple-dcp/display.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp-refresh.h b/drivers/gpu/drm/apple/dcp-refresh.h index 1b26758b6ff7b..06a8d1c703294 100644 --- a/drivers/gpu/drm/apple/dcp-refresh.h +++ b/drivers/gpu/drm/apple/dcp-refresh.h @@ -9,7 +9,7 @@ static inline bool dcp_refresh_is_promotion(u32 rate) { return rate >= (120U << 16) - (120U << 16) / 500 && - rate <= (120U << 16); + rate <= (120U << 16) + (120U << 16) / 500; } /* All rates are unsigned 16.16 Hz, including fractional fixed timings. */ diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/display.py b/tools/testing/selftests/drivers/gpu/apple-dcp/display.py index 8ba1f95206837..68049f6799a52 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/display.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/display.py @@ -134,7 +134,7 @@ def case(elements, expected, ranges=None): supported.append(m) case(supported, (120, 60, 59.94, 50, 48, 47.952), [(24<<16, 120<<16)] + [(0,0)]*5) - for hz in (119.88, 120): + for hz in (119.88, 120, 120.01): m=copy.deepcopy(supported[0]); m['VerticalAttributes']['PreciseSyncRate']=round(hz*65536) case([m], [hz], [(24<<16, round(hz*65536))]) for lo, hi, valid in ((48<<16,120<<16,True), (0,120<<16,False), From e8963b5a2f9e5cea8b361bd0362a858a9cb9d75e Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 00:55:00 +1100 Subject: [PATCH 007/184] drm/apple: initialise backlight state in lifecycle KUnit fixtures The hotplug worker synchronises connector backlight state. Construct that state in the relink fixture before running the real worker and balance its teardown. Initialise the desktop retry work in the announcement fixture before teardown can cancel it. This fixes the uninitialised backlight mutex warning with panic_on_warn=1 in the arm64 KUnit run. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/gpu/drm/apple/dcp_external_announce_test.c | 1 + drivers/gpu/drm/apple/dcp_external_relink_test.c | 2 ++ 2 files changed, 3 insertions(+) diff --git a/drivers/gpu/drm/apple/dcp_external_announce_test.c b/drivers/gpu/drm/apple/dcp_external_announce_test.c index bd0af843698f9..5ac3880f5d0d3 100644 --- a/drivers/gpu/drm/apple/dcp_external_announce_test.c +++ b/drivers/gpu/drm/apple/dcp_external_announce_test.c @@ -113,6 +113,7 @@ static struct announce_test *announce_test_init(struct kunit *test) INIT_DELAYED_WORK(&dcp->hdmi_recheck_wq, announce_test_noop); INIT_DELAYED_WORK(&dcp->bl_fallback_wq, announce_test_noop); INIT_DELAYED_WORK(&dcp->backlight.fade_work, announce_test_noop); + INIT_DELAYED_WORK(&dcp->backlight.retry_work, announce_test_noop); INIT_WORK(&dcp->external_stop_work, announce_test_noop); INIT_DELAYED_WORK(&dcp->external_stop_retry_wq, announce_test_noop); INIT_WORK(&dcp->vblank_wq, announce_test_noop); diff --git a/drivers/gpu/drm/apple/dcp_external_relink_test.c b/drivers/gpu/drm/apple/dcp_external_relink_test.c index b9d7767be80d8..cf3dfa6d911bf 100644 --- a/drivers/gpu/drm/apple/dcp_external_relink_test.c +++ b/drivers/gpu/drm/apple/dcp_external_relink_test.c @@ -222,6 +222,7 @@ static void relink_test_exit(void *data) wait_for_completion_timeout(&t->userspace_done, 10 * HZ); disable_delayed_work_sync(&t->dcp.announce_wq); disable_work_sync(&t->con.hotplug_wq); + apple_connector_backlight_early_unregister(&t->con); drm_atomic_helper_shutdown(&t->drm); drm_framebuffer_put(&t->fb); } @@ -408,6 +409,7 @@ static struct relink_test *relink_test_init(struct kunit *test) spin_lock_init(&dcp->mode_state.lock); INIT_DELAYED_WORK(&dcp->announce_wq, dcp_announce_work); INIT_WORK(&dcp->vblank_wq, relink_test_noop); + apple_connector_backlight_init(&t->con); INIT_WORK(&t->con.hotplug_wq, dcp_hotplug); dcp->test_point = relink_test_point; dcp->typec_cable_connected = true; From d2d0fddc2d2f29c264b31bef69d15fd93e40f518 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 01:11:43 +1100 Subject: [PATCH 008/184] drm/apple: avoid color updates on desktop brightness-only commits Re-present the affected planes for a brightness-only commit without marking color management changed. With a user gamma LUT installed, that flag would otherwise resend the gamma table at every fade step. Keep the legacy brightness trigger for the other firmware paths. Exercise the real atomic backlight helper with an active gamma LUT, blanked and clean states, and allocation failure in the host runner. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/gpu/drm/apple/dcp_backlight.c | 5 +- .../selftests/drivers/gpu/apple-dcp/README | 1 + .../drivers/gpu/apple-dcp/display.py | 57 +++++++++++++++++++ .../selftests/drivers/gpu/apple-dcp/run.py | 1 + 4 files changed, 63 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/dcp_backlight.c b/drivers/gpu/drm/apple/dcp_backlight.c index 475be119789f1..4ed3b573e5101 100644 --- a/drivers/gpu/drm/apple/dcp_backlight.c +++ b/drivers/gpu/drm/apple/dcp_backlight.c @@ -527,7 +527,10 @@ static int drm_crtc_set_brightness(struct apple_dcp *dcp) if (ret) goto out; - crtc_state->color_mgmt_changed |= true; + if (dcp_backlight_desktop(dcp)) + crtc_state->planes_changed = true; + else + crtc_state->color_mgmt_changed = true; ret = drm_atomic_commit(commit); out: diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/README b/tools/testing/selftests/drivers/gpu/apple-dcp/README index f7de1b1179c1d..8a7b42bb7a4fe 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/README +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/README @@ -163,6 +163,7 @@ Internal panel policy --------------------- The display group covers the shared nits ramp on a 600-nit panel, retargeting, blanking, suspend, rejected submits and both submit/completion callback orders. +Brightness-only commits retain the active gamma state without marking it changed. Synthetic TimingElements exercise 120 Hz and fractional lower refresh rates, DCP color/timing identities, invalid geometry and adaptive-sync ranges. The adaptive-sync callback test verifies the desktop parameter record, mode-info diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/display.py b/tools/testing/selftests/drivers/gpu/apple-dcp/display.py index 68049f6799a52..d35feaed549d0 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/display.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/display.py @@ -415,3 +415,60 @@ def watchdog(root): return 0; } ''' + + +def brightness_commit(root): + source=(root/'drivers/gpu/drm/apple/dcp_backlight.c').read_text() + production=block(source,'static int drm_crtc_set_brightness(') + return r''' +#include +#include +#include +#include +#define DRM_MODESET_ACQUIRE_INTERRUPTIBLE 0 +#define IS_ERR(p) false +#define PTR_ERR(p) (-EINVAL) +struct drm_crtc_state {bool active,color_mgmt_changed,planes_changed;void *gamma_lut;}; +struct drm_crtc {void *dev;int mutex;}; +struct drm_modeset_acquire_ctx {int unused;}; +struct drm_atomic_commit {struct drm_modeset_acquire_ctx *acquire_ctx;}; +struct apple_dcp {bool desktop,pending;struct {bool update;} brightness;struct {struct drm_crtc base;} *crtc;}; +static struct drm_atomic_commit commit; +static struct drm_crtc_state state; +static unsigned allocations,commits,planes; +static bool fail_alloc; +static bool dcp_backlight_desktop(struct apple_dcp *d) {return d->desktop;} +static bool dcp_backlight_pending(struct apple_dcp *d) {return d->pending;} +static struct drm_atomic_commit *drm_atomic_commit_alloc(void *dev) +{allocations++;return fail_alloc?NULL:&commit;} +static void drm_modeset_acquire_init(struct drm_modeset_acquire_ctx *c,int flags) {} +static int drm_modeset_lock(int *m,struct drm_modeset_acquire_ctx *c) {assert(!*m);*m=1;return 0;} +static struct drm_crtc_state *drm_atomic_get_crtc_state(struct drm_atomic_commit *a,struct drm_crtc *c) +{assert(c->mutex);return &state;} +static int drm_atomic_add_affected_planes(struct drm_atomic_commit *a,struct drm_crtc *c) +{assert(c->mutex);planes++;return 0;} +static int drm_atomic_commit(struct drm_atomic_commit *a) {commits++;return 0;} +static void drm_atomic_commit_clear(struct drm_atomic_commit *a) {assert(false);} +static int drm_modeset_backoff(struct drm_modeset_acquire_ctx *c) {assert(false);return 0;} +static void drm_atomic_commit_put(struct drm_atomic_commit *a) {} +static void drm_modeset_drop_locks(struct drm_modeset_acquire_ctx *c) {} +static void drm_modeset_acquire_fini(struct drm_modeset_acquire_ctx *c) {} +''' + production + r''' +int main(void) { + struct apple_dcp d={.desktop=true,.pending=true,.brightness={true}}; + __typeof__(*d.crtc) crtc={0};d.crtc=&crtc; + state.active=true;state.gamma_lut=&d; + assert(!drm_crtc_set_brightness(&d)); + assert(commits==1 && planes==1 && state.planes_changed && !state.color_mgmt_changed); + crtc.base.mutex=0;state=(struct drm_crtc_state){.active=true};d.desktop=false; + assert(!drm_crtc_set_brightness(&d)); + assert(commits==2 && planes==2 && state.color_mgmt_changed); + crtc.base.mutex=0;state=(struct drm_crtc_state){.active=false};d.desktop=true; + assert(!drm_crtc_set_brightness(&d) && commits==2); + crtc.base.mutex=0;state.active=true;d.pending=false; + assert(!drm_crtc_set_brightness(&d) && commits==2); + fail_alloc=true;assert(drm_crtc_set_brightness(&d)==-ENOMEM && commits==2); + assert(allocations==5); + return 0; +} +''' diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/run.py b/tools/testing/selftests/drivers/gpu/apple-dcp/run.py index 134b62e97b07e..933c8425c9fbc 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/run.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/run.py @@ -53,6 +53,7 @@ def main(): 'failed-flip-return', 'live-park')) tests = [ ('display', 'panel-backlight-ramp', display.backlight, ((),), ()), + ('display', 'panel-brightness-commit', display.brightness_commit, ((),), ()), ('display', 'panel-brightness-takeover', display.takeover, ((),), ()), ('display', 'panel-mode-catalog', display.modes, ((),), ()), ('display', 'panel-refresh-range', display.refresh, ((),), ()), From eecb5aaa08168237a21778b2d1273dee9b74460a Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 01:40:06 +1100 Subject: [PATCH 009/184] drm/apple: construct parser fixture metadata and notification state Initialise the metadata lock with the mode catalogue, matching probe. The deferred-attribute test also needs an initialised hotplug work item and backlight notification state now that attributes can queue them. Drain those fixture-owned workers before releasing their storage. This fixes the lockdep key report and uninitialised work warning in the strict arm64 parser KUnit run. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/gpu/drm/apple/parser-test.c | 44 ++++++++++++++++++----------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/drivers/gpu/drm/apple/parser-test.c b/drivers/gpu/drm/apple/parser-test.c index 9cf57af1b3c94..46b2f6b40579e 100644 --- a/drivers/gpu/drm/apple/parser-test.c +++ b/drivers/gpu/drm/apple/parser-test.c @@ -22,6 +22,13 @@ static struct apple_dcp parser_dcp = { .dev = &parser_device, }; +/* The probe owns the metadata lock beside the mode catalogue. */ +static void parser_modes_init(struct apple_dcp *dcp) +{ + dcp_modes_init(dcp); + spin_lock_init(&dcp->metadata.lock); +} + struct parser_blob { u8 data[8192]; u32 size; @@ -445,7 +452,7 @@ static void parser_replacement(struct kunit *test) struct drm_display_mode requested; KUNIT_ASSERT_NOT_NULL(test, dcp); - dcp_modes_init(dcp); + parser_modes_init(dcp); blob = make_blob(test, &record, 1); KUNIT_ASSERT_NOT_NULL(test, blob); KUNIT_ASSERT_EQ(test, parse(blob->data, blob->size, &ctx), 0); @@ -493,8 +500,7 @@ static void parser_held_keeps_edid(struct kunit *test) KUNIT_ASSERT_NOT_NULL(test, dcp); KUNIT_ASSERT_NOT_NULL(test, connector); KUNIT_ASSERT_NOT_NULL(test, pipeline); - dcp_modes_init(dcp); - spin_lock_init(&dcp->metadata.lock); + parser_modes_init(dcp); apple_connector_edid_init(connector); apple_connector_set_pipeline(connector, pipeline); dcp->connector = connector; @@ -528,7 +534,7 @@ static void parser_attachment_admission(struct kunit *test) struct drm_display_mode requested; KUNIT_ASSERT_NOT_NULL(test, dcp); - dcp_modes_init(dcp); + parser_modes_init(dcp); spin_lock_init(&dcp->mode_state.lock); blob = make_blob(test, &record, 1); KUNIT_ASSERT_NOT_NULL(test, blob); @@ -575,7 +581,7 @@ static void parser_stale_transfer(struct kunit *test) u64 old_generation, current_generation; KUNIT_ASSERT_NOT_NULL(test, dcp); - dcp_modes_init(dcp); + parser_modes_init(dcp); blob = make_blob(test, &record, 1); KUNIT_ASSERT_NOT_NULL(test, blob); KUNIT_ASSERT_EQ(test, parse(blob->data, blob->size, &ctx), 0); @@ -649,7 +655,7 @@ static void parser_route_catalog_handoff(struct kunit *test) KUNIT_ASSERT_NOT_NULL(test, connector); KUNIT_ASSERT_NOT_NULL(test, other); KUNIT_ASSERT_NOT_NULL(test, pipeline); - dcp_modes_init(dcp); + parser_modes_init(dcp); dcp->dev = &pipeline->dev; connector->dcp = pipeline; observer->dcp = dcp; @@ -770,7 +776,7 @@ static void parser_route_programming_guard(struct kunit *test) KUNIT_ASSERT_NOT_NULL(test, connector->base.state); connector->base.state->crtc = &crtc->base; crtc_state.connector_mask = drm_connector_mask(&connector->base); - dcp_modes_init(dcp); + parser_modes_init(dcp); dcp->modes_admitted = true; dcp->fw_compat = DCP_FIRMWARE_V_13_5; dcp->nr_typec_routes = 1; @@ -856,7 +862,7 @@ static void parser_route_invalid_own_mode(struct kunit *test) KUNIT_ASSERT_NOT_NULL(test, dcp); KUNIT_ASSERT_NOT_NULL(test, crtc); KUNIT_ASSERT_NOT_NULL(test, pdev); - dcp_modes_init(dcp); + parser_modes_init(dcp); dcp->fw_compat = DCP_FIRMWARE_V_13_5; dcp->nr_typec_routes = 1; crtc->dcp = pdev; @@ -875,7 +881,7 @@ static void parser_connector_ownership(struct kunit *test) KUNIT_ASSERT_NOT_NULL(test, dcp); KUNIT_ASSERT_NOT_NULL(test, fixed); KUNIT_ASSERT_NOT_NULL(test, borrowed); - dcp_modes_init(dcp); + parser_modes_init(dcp); mutex_lock(&dcp->modes_lock); dcp->connector = borrowed; dcp->modes_admitted = true; @@ -903,7 +909,7 @@ static void parser_route_retirement(struct kunit *test) KUNIT_ASSERT_NOT_NULL(test, fixed); KUNIT_ASSERT_NOT_NULL(test, route); KUNIT_ASSERT_NOT_NULL(test, other); - dcp_modes_init(dcp); + parser_modes_init(dcp); dcp->connector = fixed; dcp->active_typec_route = route; dcp->modes_admitted = true; @@ -927,7 +933,7 @@ static void parser_rejected_replacement(struct kunit *test) struct parser_blob *blob; KUNIT_ASSERT_NOT_NULL(test, dcp); - dcp_modes_init(dcp); + parser_modes_init(dcp); blob = make_blob(test, &record, 1); KUNIT_ASSERT_NOT_NULL(test, blob); KUNIT_ASSERT_EQ(test, parse(blob->data, blob->size, &ctx), 0); @@ -969,7 +975,7 @@ static void parser_attribute_epoch(struct kunit *test) KUNIT_ASSERT_NOT_NULL(test, dcp); KUNIT_ASSERT_NOT_NULL(test, blob); - dcp_modes_init(dcp); + parser_modes_init(dcp); put_unaligned_le32(0xd3, blob->data); blob->size = 4; blob_tag(blob, 1, 3); @@ -1003,7 +1009,7 @@ static void parser_attributes_malformed(struct kunit *test) KUNIT_ASSERT_NOT_NULL(test, dcp); KUNIT_ASSERT_NOT_NULL(test, blob); - dcp_modes_init(dcp); + parser_modes_init(dcp); dcp->width_mm = 520; dcp->height_mm = 290; dcp->ext_backlight = true; @@ -1040,7 +1046,7 @@ static void parser_optional_flag_extent(struct kunit *test) KUNIT_ASSERT_NOT_NULL(test, dcp); KUNIT_ASSERT_NOT_NULL(test, blob); - dcp_modes_init(dcp); + parser_modes_init(dcp); put_unaligned_le32(0xd3, blob->data); blob->size = 4; blob_tag(blob, 1, 3); @@ -1089,11 +1095,12 @@ static void parser_attribute_deferred(struct kunit *test) KUNIT_ASSERT_NOT_NULL(test, connector); KUNIT_ASSERT_NOT_NULL(test, dev); KUNIT_ASSERT_NOT_NULL(test, blob); - dcp_modes_init(dcp); + parser_modes_init(dcp); observer->dcp = dcp; INIT_WORK(&connector->hotplug_wq, parser_catalog_ready); mutex_init(&dev->mode_config.mutex); connector->base.dev = dev; + apple_connector_backlight_init(connector); dcp->connector = connector; dcp->panel.width_mm = 600; dcp->modes = kmalloc_obj(*dcp->modes); @@ -1118,6 +1125,9 @@ static void parser_attribute_deferred(struct kunit *test) KUNIT_EXPECT_EQ(test, connector->base.display_info.width_mm, 520U); KUNIT_EXPECT_EQ(test, connector->base.display_info.height_mm, 290U); dcp_modes_release(dcp); + cancel_work_sync(&connector->hotplug_wq); + cancel_work_sync(&connector->bl_sync_wq); + apple_connector_backlight_early_unregister(connector); } static void parser_dimensions_lifetime(struct kunit *test) @@ -1129,7 +1139,7 @@ static void parser_dimensions_lifetime(struct kunit *test) KUNIT_ASSERT_NOT_NULL(test, dcp); KUNIT_ASSERT_NOT_NULL(test, connector); KUNIT_ASSERT_NOT_NULL(test, dev); - dcp_modes_init(dcp); + parser_modes_init(dcp); mutex_init(&dev->mode_config.mutex); connector->base.dev = dev; dcp->connector = connector; @@ -1158,7 +1168,7 @@ static void parser_catalog_release(struct kunit *test) struct apple_dcp *dcp = kunit_kzalloc(test, sizeof(*dcp), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, dcp); - dcp_modes_init(dcp); + parser_modes_init(dcp); dcp->modes = kmalloc_obj(*dcp->modes); KUNIT_ASSERT_NOT_NULL(test, dcp->modes); dcp->nr_modes = 1; From 367d76551b46a804a3244bc02c60d75a853c0973 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 02:06:07 +1100 Subject: [PATCH 010/184] drm/apple: initialise the session lock in the route preparation test The strict route preparation path snapshots the tunnel session under hpd_mutex and tb_lock before checking the PHY. Initialise both locks in the fixture, matching the probe, so panic_on_warn can reach the intended missing-PHY result. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/gpu/drm/apple/parser-test.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/apple/parser-test.c b/drivers/gpu/drm/apple/parser-test.c index 46b2f6b40579e..69d7deb3d8357 100644 --- a/drivers/gpu/drm/apple/parser-test.c +++ b/drivers/gpu/drm/apple/parser-test.c @@ -1248,7 +1248,9 @@ static void parser_route_prepare_scope(struct kunit *test) dcp->typec_follow_start = true; dcp->typec_follow_gen = dcp->typec_generation; mutex_init(&dcp->hpd_mutex); + mutex_init(&dcp->tb_lock); KUNIT_EXPECT_EQ(test, dcp_typec_prepare_route(dcp), -ENODEV); + mutex_destroy(&dcp->tb_lock); mutex_destroy(&dcp->hpd_mutex); mutex_destroy(&dcp->modes_lock); } From e940bc6286899f7f9a4c8935f8a2b1d7dc6c4c6c Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 02:36:59 +1100 Subject: [PATCH 011/184] selftests: apple-dcp: keep external rate fixtures scoped after panel VRR Supply the desktop capability boundary for the external H17P rate fixture, which cannot select that firmware, and the refresh rounding helper used by connector range publication. Preserve the firmware lineage and lookup extraction from the current display base. Assisted-by: LLM Signed-off-by: Ryan Murray --- tools/testing/selftests/drivers/gpu/apple-dcp/rate.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py b/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py index 61000228c8b49..db4170e7793e7 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py @@ -681,6 +681,10 @@ def publication(root): #endif /* Nor the desktop 26.6 firmware, the one that withholds VRR modes. */ #define DCP_FIRMWARE_V_26_6_DESKTOP (DCP_FIRMWARE_H17P + 3) +#define DIV_ROUND_UP(n,d) (((n)+(d)-1)/(d)) +/* This fixture selects an external H17P sink, never desktop adaptive sync. */ +static bool dcp_desktop_vrr_supported(struct apple_dcp *d) +{ assert(d->fw_compat != DCP_FIRMWARE_V_26_6_DESKTOP); return false; } '''+firmware_lineage(root)+internal_inline(root,'dcp_fw_is_14_7')+r'''/* Off T6030 machines the direct crossbar output is not clocked separately. */ static int __attribute__((unused)) dcp_direct_crossbar_link(struct apple_dcp *d,bool up) { (void)d;(void)up;return 0; } ''' From 2f6fc9b411ee1636c1c7ce71cb63c898c9c97407 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 03:41:18 +1100 Subject: [PATCH 012/184] selftests: apple-dcp: anchor publication lookup extraction to its definition The desktop VRR admission check calls lookup_mode before its definition. Extract the definition explicitly so the publication fixture does not start in the middle of the admission check or duplicate unrelated helpers. Assisted-by: LLM Signed-off-by: Ryan Murray --- tools/testing/selftests/drivers/gpu/apple-dcp/rate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py b/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py index db4170e7793e7..9176515c38d51 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py @@ -706,7 +706,7 @@ def publication(root): + ''.join(apple_function(root, name) for name in ('dcp_queue_hotplug', 'dcp_modes_for_connector', 'apple_connector_invalidate_edid', 'apple_connector_edid_set_live')) + mode_helpers) - publication = block(iomfb,'static void dcp_clear_connector_edid(')+'\n'+block(iomfb,'static void dcp_consume_edid_invalidation(')+mode_helpers+block(iomfb,'static bool dcp_mode_vrr_supported(')+'\n'+block(iomfb,'int dcp_get_modes(')+block(iomfb,re.search(r'^[^\n;(]*\blookup_mode\(',iomfb,re.M).start()) + publication = block(iomfb,'static void dcp_clear_connector_edid(')+'\n'+block(iomfb,'static void dcp_consume_edid_invalidation(')+mode_helpers+block(iomfb,'static bool dcp_mode_vrr_supported(')+'\n'+block(iomfb,'int dcp_get_modes(')+block(iomfb,'bool lookup_mode(') # Drive malformed replacements through the same chunk/end ingress as firmware. # The dictionary cache is a boundary substitute and takes ownership of the blob. receipt = r''' From 23ff713d4f1dd04f9602cff0fcc3001f1d4d0f95 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 06:10:53 +1100 Subject: [PATCH 013/184] drm/apple: use the internal VRR fallback for zero range fields Desktop firmware may supply both variable-refresh fields as zero placeholders. Treat that pair like an omitted range so the internal ProMotion timing gets its 24 Hz lower bound and native upper bound. Keep partial, duplicate and invalid ranges fixed. Cover the zero pair through the real parser in KUnit and host tests, including rejection of partial zero fields. Assisted-by: LLM Signed-off-by: Ryan Murray --- Documentation/gpu/apple-dcp-t6041-panel.rst | 3 +- drivers/gpu/drm/apple/parser-test.c | 37 ++++++++++++++++++- drivers/gpu/drm/apple/parser.c | 8 +++- .../drivers/gpu/apple-dcp/display.py | 7 +++- 4 files changed, 48 insertions(+), 7 deletions(-) diff --git a/Documentation/gpu/apple-dcp-t6041-panel.rst b/Documentation/gpu/apple-dcp-t6041-panel.rst index c24ebd1e4bfc4..82f5f5445c759 100644 --- a/Documentation/gpu/apple-dcp-t6041-panel.rst +++ b/Documentation/gpu/apple-dcp-t6041-panel.rst @@ -20,7 +20,8 @@ Adaptive sync is experimental and disabled by default. To qualify the internal panel, boot with ``appledrm.t6041_vrr=1``. This enables the connector capability only for the internal main display and a valid adaptive-sync range. The existing internal ProMotion convention supplies 24 Hz to the mode rate when -the firmware omits both range fields; an explicit partial or invalid range +the firmware omits both range fields or reports both as zero placeholders; +a partial, duplicate or invalid range leaves the timing usable at fixed refresh. A rejected adaptive-sync parameter or mode selection gets one retry with diff --git a/drivers/gpu/drm/apple/parser-test.c b/drivers/gpu/drm/apple/parser-test.c index 69d7deb3d8357..749ad582caccd 100644 --- a/drivers/gpu/drm/apple/parser-test.c +++ b/drivers/gpu/drm/apple/parser-test.c @@ -41,6 +41,7 @@ struct parser_record { s64 first_format; bool bad_score_type; bool vrr; + bool vrr_zero; }; enum parser_missing { @@ -150,8 +151,8 @@ static void blob_record(struct parser_blob *blob, const struct parser_record *re record->second_color_missing, DCP_COLOR_FORMAT_RGB); } if (record->vrr) { - blob_int(blob, "MinimumVariableRefreshRate", 48 << 16); - blob_int(blob, "MaximumVariableRefreshRate", 120 << 16); + blob_int(blob, "MinimumVariableRefreshRate", record->vrr_zero ? 0 : 48 << 16); + blob_int(blob, "MaximumVariableRefreshRate", record->vrr_zero ? 0 : 120 << 16); } } @@ -442,6 +443,37 @@ static void parser_notch_vrr(struct kunit *test) kfree(modes); } +static void parser_vrr_empty_range(struct kunit *test) +{ + struct parser_record record = valid_record(7, 10); + struct dcp_display_mode *modes; + struct parser_blob *blob; + unsigned int count; + + /* Desktop firmware can publish both fields as zero placeholders. */ + record.rate = 120 << 16; + record.vrr = true; + record.vrr_zero = true; + blob = make_blob(test, &record, 1); + KUNIT_ASSERT_NOT_NULL(test, blob); + modes = parse_blob(blob, &count, 0, true); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, modes); + KUNIT_ASSERT_EQ(test, count, 1U); + KUNIT_EXPECT_TRUE(test, modes[0].vrr); + KUNIT_EXPECT_EQ(test, modes[0].min_vrr, 24U << 16); + KUNIT_EXPECT_EQ(test, modes[0].max_vrr, 120U << 16); + kfree(modes); + + /* A display without the internal ProMotion policy stays fixed. */ + modes = parse_blob(blob, &count, 0, false); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, modes); + KUNIT_ASSERT_EQ(test, count, 1U); + KUNIT_EXPECT_FALSE(test, modes[0].vrr); + KUNIT_EXPECT_EQ(test, modes[0].min_vrr, 0U); + KUNIT_EXPECT_EQ(test, modes[0].max_vrr, 0U); + kfree(modes); +} + static void parser_replacement(struct kunit *test) { struct parser_record record = valid_record(7, 10); @@ -1264,6 +1296,7 @@ static struct kunit_case parser_cases[] = { KUNIT_CASE(parser_horizontal_rate), KUNIT_CASE(parser_bad_score), KUNIT_CASE(parser_notch_vrr), + KUNIT_CASE(parser_vrr_empty_range), KUNIT_CASE(parser_replacement), KUNIT_CASE(parser_held_keeps_edid), KUNIT_CASE(parser_attachment_admission), diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index ab84a8522ed73..761271339bed1 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -875,10 +875,14 @@ static int parse_mode(struct dcp_parse_ctx *handle, if (is_virtual) return -EINVAL; - /* External H17P keeps fixed timings without adaptive-sync support. */ + /* + * External H17P stays fixed. A pair of zero placeholders is also an + * absent range; a partial or duplicate pair must not enable fallback. + */ if (!external_sdr && !vrr_ambiguous) out->vrr = dcp_refresh_range(vert.precise_sync_rate, min_vrr, - max_vrr, internal && !vrr_seen, + max_vrr, internal && + (!vrr_seen || vrr_seen == 3), &out->min_vrr, &out->max_vrr); out->needs_timestamps = out->vrr || (internal && dcp_refresh_is_promotion(vert.precise_sync_rate)); diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/display.py b/tools/testing/selftests/drivers/gpu/apple-dcp/display.py index d35feaed549d0..c53a9d489e51e 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/display.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/display.py @@ -138,10 +138,13 @@ def case(elements, expected, ranges=None): m=copy.deepcopy(supported[0]); m['VerticalAttributes']['PreciseSyncRate']=round(hz*65536) case([m], [hz], [(24<<16, round(hz*65536))]) for lo, hi, valid in ((48<<16,120<<16,True), (0,120<<16,False), - (60<<16,0,False), (0,0,False), (120<<16,24<<16,False), + (60<<16,0,False), (0,0,True), (120<<16,24<<16,False), (24<<16,144<<16,False), (120<<16,120<<16,False)): m=copy.deepcopy(supported[0]); m['MinimumVariableRefreshRate']=lo; m['MaximumVariableRefreshRate']=hi - case([m],[120],[(lo,hi) if valid else (0,0)]) + case([m],[120],[(lo or 24<<16,hi or 120<<16) if valid else (0,0)]) + for key in ('MinimumVariableRefreshRate', 'MaximumVariableRefreshRate'): + m=copy.deepcopy(supported[0]);m[key]=0 + case([m],[120],[(0,0)]) m=copy.deepcopy(supported[0]);m['MinimumVariableRefreshRate']=24<<16 m=f['ObjectPairs'](list(m.items())+[('MinimumVariableRefreshRate',48<<16)]) # Check duplicates against the parser directly without dict indexing in case(). From 6bc143748fb90e7d8946bcdf58cc80206906b87a Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 06:12:42 +1100 Subject: [PATCH 014/184] drm/apple: keep unknown takeover brightness out of restore policy Report ENODATA for desktop actual_brightness until a loader hint or an accepted, completed brightness present establishes the panel level. A reporting midpoint must not be saved and restored as a measured level by userspace. Keep framebuffer unblank and core suspend notifications from acquiring brightness control while the inherited level is unknown. Accept the first bounded loader hint after registration until a user request takes ownership. Test unknown-level reads, lifecycle notifications, rejected presents and explicit writes in KUnit and host coverage. Assisted-by: LLM Signed-off-by: Ryan Murray --- Documentation/gpu/apple-dcp-t6041-panel.rst | 4 +++ drivers/gpu/drm/apple/dcp_backlight.c | 24 +++++++++++--- drivers/gpu/drm/apple/dcp_backlight.h | 15 +++++++++ drivers/gpu/drm/apple/dcp_backlight_test.c | 32 +++++++++++++++++++ .../drivers/gpu/apple-dcp/display.py | 22 ++++++++++--- 5 files changed, 87 insertions(+), 10 deletions(-) diff --git a/Documentation/gpu/apple-dcp-t6041-panel.rst b/Documentation/gpu/apple-dcp-t6041-panel.rst index 82f5f5445c759..94be14250e8f7 100644 --- a/Documentation/gpu/apple-dcp-t6041-panel.rst +++ b/Documentation/gpu/apple-dcp-t6041-panel.rst @@ -8,6 +8,10 @@ integer nits from zero to the panel ceiling, with the same 250 ms fade used by the H17P panel. The backlight interface reports the requested target and ``actual_brightness`` follows accepted, completed presents. A registration default does not change the brightness inherited from the boot loader. +Until a loader hint or a completed user request supplies a known level, +``actual_brightness`` returns ``ENODATA``. This prevents brightness save/restore +services from treating the registration default as a measured panel level. +Framebuffer unblank and core suspend notifications also leave that level alone. Ambient-light policy can use the normal backlight interface, just as a slider does. Requests made while blanked or suspended preserve the restore target. diff --git a/drivers/gpu/drm/apple/dcp_backlight.c b/drivers/gpu/drm/apple/dcp_backlight.c index 4ed3b573e5101..1d8125f32c4d6 100644 --- a/drivers/gpu/drm/apple/dcp_backlight.c +++ b/drivers/gpu/drm/apple/dcp_backlight.c @@ -128,15 +128,20 @@ int dcp_backlight_takeover(struct apple_dcp *dcp, u32 millinits) if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) { unsigned long flags; + bool seeded = false; - /* Freeze the first hint before publishing the class device. */ + /* Accept the first hint until a user request takes ownership. */ spin_lock_irqsave(&dcp->backlight.lock, flags); - if (!dcp->backlight.state.ready && - !dcp->desktop->inherited_brightness_valid) { + if (!dcp->desktop->inherited_brightness_valid && + !dcp->backlight.state.controlled) { dcp->desktop->inherited_brightness = nits; dcp->desktop->inherited_brightness_valid = true; + if (dcp->backlight.state.ready) + seeded = dcp_bl_seed(&dcp->backlight.state, nits); } spin_unlock_irqrestore(&dcp->backlight.lock, flags); + if (seeded && READ_ONCE(dcp->brightness.bl_dev)) + backlight_force_update(dcp->brightness.bl_dev, BACKLIGHT_UPDATE_HOTKEY); return 0; } @@ -148,14 +153,17 @@ static int dcp_get_brightness(struct backlight_device *bd) { struct apple_dcp *dcp = bl_get_data(bd); unsigned long flags; - u32 actual; + int actual; if (READ_ONCE(dcp->quiescing)) return -ENODEV; if (dcp_backlight_active(dcp) || dcp_backlight_desktop(dcp)) { spin_lock_irqsave(&dcp->backlight.lock, flags); - actual = dcp->backlight.state.actual; + if (dcp_backlight_desktop(dcp)) + actual = dcp_bl_actual(&dcp->backlight.state); + else + actual = dcp->backlight.state.actual; spin_unlock_irqrestore(&dcp->backlight.lock, flags); return actual; } @@ -628,6 +636,12 @@ static bool dcp_backlight_controls_device(struct backlight_device *bd, { struct apple_dcp *dcp = bl_get_data(bd); + /* A framebuffer's first unblank must not apply the reporting default. */ + if (dcp_backlight_desktop(dcp) && + !READ_ONCE(dcp->backlight.state.level_known) && + !READ_ONCE(dcp->backlight.state.controlled)) + return false; + return !display_dev || display_dev == dcp->dev || (dcp->crtc && display_dev == dcp->crtc->base.dev->dev); } diff --git a/drivers/gpu/drm/apple/dcp_backlight.h b/drivers/gpu/drm/apple/dcp_backlight.h index 6585e58436e2a..b130a5ff0437e 100644 --- a/drivers/gpu/drm/apple/dcp_backlight.h +++ b/drivers/gpu/drm/apple/dcp_backlight.h @@ -107,6 +107,14 @@ static inline bool dcp_bl_seed(struct dcp_backlight_state *state, u32 nits) return true; } +/* Do not let userspace save or restore an unmeasured registration default. */ +static inline int dcp_bl_actual(const struct dcp_backlight_state *state) +{ + if (!state->ready || !state->level_known) + return -ENODATA; + return state->actual; +} + static inline bool dcp_bl_lit(const struct dcp_backlight_state *state) { return !state->dpms_off && !state->core_blank && !state->suspended; @@ -139,6 +147,13 @@ static inline int dcp_bl_request(struct dcp_backlight_state *state, u32 nits, return -ENODATA; if (nits > state->maximum) return -ERANGE; + /* Core blank/suspend notifications do not establish a panel level. */ + if (!state->controlled && !state->level_known && state->target == nits && + (state->core_blank != core_blank || state->suspended != suspended)) { + state->core_blank = core_blank; + state->suspended = suspended; + return 0; + } state->controlled = true; state->retries = 0; /* diff --git a/drivers/gpu/drm/apple/dcp_backlight_test.c b/drivers/gpu/drm/apple/dcp_backlight_test.c index 61cf775c683fc..74a2b33801542 100644 --- a/drivers/gpu/drm/apple/dcp_backlight_test.c +++ b/drivers/gpu/drm/apple/dcp_backlight_test.c @@ -292,6 +292,37 @@ static void backlight_fallback_test(struct kunit *test) KUNIT_EXPECT_EQ(test, present.nits, 262U); } +static void backlight_unknown_takeover_test(struct kunit *test) +{ + struct dcp_backlight_state state; + struct dcp_backlight_present present; + + KUNIT_ASSERT_EQ(test, dcp_bl_init(&state, 600, false, 0, true, 300), 0); + KUNIT_EXPECT_EQ(test, dcp_bl_actual(&state), -ENODATA); + /* Core suspend/blank and resume do not turn the guess into a write. */ + KUNIT_ASSERT_EQ(test, dcp_bl_request(&state, 300, true, true), 0); + KUNIT_ASSERT_EQ(test, dcp_bl_request(&state, 300, false, false), 0); + KUNIT_EXPECT_FALSE(test, state.controlled); + KUNIT_EXPECT_FALSE(test, state.dirty); + KUNIT_EXPECT_EQ(test, dcp_bl_prepare(&state, true, &present), -EALREADY); + KUNIT_EXPECT_EQ(test, dcp_bl_actual(&state), -ENODATA); + + /* A late loader hint supplies the inherited level without a present. */ + KUNIT_EXPECT_TRUE(test, dcp_bl_seed(&state, 250)); + KUNIT_EXPECT_EQ(test, dcp_bl_actual(&state), 250); + KUNIT_EXPECT_FALSE(test, state.dirty); + + /* An explicit write, even of the default, takes ownership. */ + KUNIT_ASSERT_EQ(test, dcp_bl_init(&state, 600, false, 0, true, 300), 0); + KUNIT_ASSERT_EQ(test, dcp_bl_request(&state, 300, false, false), 0); + KUNIT_ASSERT_EQ(test, dcp_bl_prepare(&state, true, &present), 0); + KUNIT_EXPECT_TRUE(test, dcp_bl_complete(&state, present.sequence, false)); + KUNIT_EXPECT_EQ(test, dcp_bl_actual(&state), -ENODATA); + KUNIT_ASSERT_EQ(test, dcp_bl_prepare(&state, true, &present), 0); + KUNIT_EXPECT_TRUE(test, dcp_bl_complete(&state, present.sequence, true)); + KUNIT_EXPECT_EQ(test, dcp_bl_actual(&state), 300); +} + static void backlight_fallback_same_level_test(struct kunit *test) { struct dcp_backlight_state state; @@ -548,6 +579,7 @@ static struct kunit_case backlight_cases[] = { KUNIT_CASE(backlight_inflight_dpms_test), KUNIT_CASE(backlight_resend_test), KUNIT_CASE(backlight_fallback_test), + KUNIT_CASE(backlight_unknown_takeover_test), KUNIT_CASE(backlight_fallback_same_level_test), KUNIT_CASE(backlight_seed_known_test), KUNIT_CASE(backlight_desktop_swap_test), diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/display.py b/tools/testing/selftests/drivers/gpu/apple-dcp/display.py index c53a9d489e51e..6dce2e1149ff1 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/display.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/display.py @@ -47,14 +47,17 @@ def backlight(root): assert(!dcp_bl_takeover_nits(600,600000,&nits) && nits==600); /* A registration default must never change the loader's visible level. */ assert(!dcp_bl_init(&s,600,false,0,true,250)); s.fade=true; - assert(!s.dirty && !s.level_known); + assert(!s.dirty && !s.level_known && dcp_bl_actual(&s)==-ENODATA); + assert(!dcp_bl_request(&s,250,true,true)); + assert(!dcp_bl_request(&s,250,false,false)); + assert(!s.controlled && !s.dirty && dcp_bl_actual(&s)==-ENODATA); assert(dcp_bl_swap_prepare(&s,&p,id,true)==-EALREADY); assert(!dcp_bl_request(&s,250,false,false)); assert(!s.fading && s.dirty); assert(dcp_bl_swap_prepare(&s,&p,id,false)==-ENODATA); assert(!dcp_bl_swap_prepare(&s,&p,id,true)); completed(&s,&p,id++,true); - assert(s.actual==250 && s.level_known); + assert(s.actual==250 && s.level_known && dcp_bl_actual(&s)==250); assert(!dcp_bl_seed(&s,300)); /* Same state as a slider request or an ALS retarget through backlight. */ assert(!dcp_bl_request(&s,600,false,false) && s.fading); @@ -309,11 +312,14 @@ def takeover(root): #define spin_unlock_irqrestore(p,f) do {assert(*(p));*(p)=false;(void)(f);} while(0) struct apple_dcp { int fw_compat; - struct {u32 maximum;} brightness; + struct {u32 maximum;void *bl_dev;} brightness; struct {bool lock;struct dcp_backlight_state state;} backlight; struct {bool inherited_brightness_valid;u32 inherited_brightness;} *desktop; }; -static unsigned legacy; +#define BACKLIGHT_UPDATE_HOTKEY 1 +static unsigned legacy,notifications; +static void backlight_force_update(void *bd,int reason) +{assert(bd && reason==BACKLIGHT_UPDATE_HOTKEY);notifications++;} bool dcp_backlight_active(struct apple_dcp *d) {return d->fw_compat==1 && d->backlight.state.ready;} static int iomfb_configure_backlight_h17p(struct apple_dcp *d,u32 max,bool valid,u32 nits,bool fallback,u32 guessed) {assert(max==600 && valid && nits==100 && !fallback && !guessed);legacy++;return 0;} @@ -327,7 +333,13 @@ def takeover(root): saved.inherited_brightness_valid=false; assert(dcp_backlight_takeover(&d,1300000)==-ERANGE && !saved.inherited_brightness_valid); assert(!dcp_backlight_takeover(&d,700000) && saved.inherited_brightness==600); - d.backlight.state.ready=true; saved.inherited_brightness_valid=false; + assert(!dcp_bl_init(&d.backlight.state,600,false,0,true,300)); + d.brightness.bl_dev=&d;saved.inherited_brightness_valid=false; + assert(!dcp_backlight_takeover(&d,100000) && saved.inherited_brightness_valid); + assert(dcp_bl_actual(&d.backlight.state)==100 && !d.backlight.state.dirty && notifications==1); + assert(!dcp_backlight_takeover(&d,200000) && d.backlight.state.target==100 && notifications==1); + saved.inherited_brightness_valid=false; + assert(!dcp_bl_request(&d.backlight.state,250,false,false)); assert(!dcp_backlight_takeover(&d,100000) && !saved.inherited_brightness_valid); d.fw_compat=1;d.backlight.state.ready=false; assert(!dcp_backlight_takeover(&d,100000) && legacy==1); From e3bb614fcc56131d3b098d257999b9d244a6c953 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 06:18:44 +1100 Subject: [PATCH 015/184] drm/apple: preserve the panel level when clients echo the initial target Brightness restore clients can read brightness and write the same value back before a loader hint arrives. On the desktop panel that value is a reporting default, so leave the inherited panel level alone. A hint or a different target establishes brightness control. Keep this policy specific to desktop takeover. Add KUnit and host coverage for an echoed midpoint and for a new target becoming known only after its accepted present completes. Assisted-by: LLM Signed-off-by: Ryan Murray --- Documentation/gpu/apple-dcp-t6041-panel.rst | 8 +++++--- drivers/gpu/drm/apple/dcp_backlight.c | 1 + drivers/gpu/drm/apple/dcp_backlight.h | 9 +++++++++ drivers/gpu/drm/apple/dcp_backlight_test.c | 10 ++++++++-- .../testing/selftests/drivers/gpu/apple-dcp/display.py | 6 +++--- 5 files changed, 26 insertions(+), 8 deletions(-) diff --git a/Documentation/gpu/apple-dcp-t6041-panel.rst b/Documentation/gpu/apple-dcp-t6041-panel.rst index 94be14250e8f7..5bbe4ef6872ca 100644 --- a/Documentation/gpu/apple-dcp-t6041-panel.rst +++ b/Documentation/gpu/apple-dcp-t6041-panel.rst @@ -9,9 +9,11 @@ by the H17P panel. The backlight interface reports the requested target and ``actual_brightness`` follows accepted, completed presents. A registration default does not change the brightness inherited from the boot loader. Until a loader hint or a completed user request supplies a known level, -``actual_brightness`` returns ``ENODATA``. This prevents brightness save/restore -services from treating the registration default as a measured panel level. -Framebuffer unblank and core suspend notifications also leave that level alone. +``actual_brightness`` returns ``ENODATA``. Echoing the unchanged registration +value also leaves the panel alone while its level is unknown. A request for a +different target takes control. Once a hint or completed request establishes +the level, all targets are usable. Framebuffer unblank and core suspend +notifications also preserve an unknown inherited level. Ambient-light policy can use the normal backlight interface, just as a slider does. Requests made while blanked or suspended preserve the restore target. diff --git a/drivers/gpu/drm/apple/dcp_backlight.c b/drivers/gpu/drm/apple/dcp_backlight.c index 1d8125f32c4d6..042711030eb4b 100644 --- a/drivers/gpu/drm/apple/dcp_backlight.c +++ b/drivers/gpu/drm/apple/dcp_backlight.c @@ -704,6 +704,7 @@ int dcp_backlight_register(struct apple_dcp *dcp) return ret; } dcp->backlight.state.fade = true; + dcp->backlight.state.preserve_inherited = true; dcp->backlight.kick = dcp_backlight_desktop_kick; } props.brightness = dcp->backlight.state.target; diff --git a/drivers/gpu/drm/apple/dcp_backlight.h b/drivers/gpu/drm/apple/dcp_backlight.h index b130a5ff0437e..7683883137224 100644 --- a/drivers/gpu/drm/apple/dcp_backlight.h +++ b/drivers/gpu/drm/apple/dcp_backlight.h @@ -26,6 +26,8 @@ struct dcp_backlight_state { u8 retries; bool ready; bool controlled; + /* An unchanged registration default cannot take over an unknown level. */ + bool preserve_inherited; bool dpms_off; bool core_blank; bool suspended; @@ -147,6 +149,13 @@ static inline int dcp_bl_request(struct dcp_backlight_state *state, u32 nits, return -ENODATA; if (nits > state->maximum) return -ERANGE; + /* Save/restore clients may echo the reporting default without a change. */ + if (state->preserve_inherited && !state->controlled && !state->level_known && + state->target == nits) { + state->core_blank = core_blank; + state->suspended = suspended; + return 0; + } /* Core blank/suspend notifications do not establish a panel level. */ if (!state->controlled && !state->level_known && state->target == nits && (state->core_blank != core_blank || state->suspended != suspended)) { diff --git a/drivers/gpu/drm/apple/dcp_backlight_test.c b/drivers/gpu/drm/apple/dcp_backlight_test.c index 74a2b33801542..48959efd2665a 100644 --- a/drivers/gpu/drm/apple/dcp_backlight_test.c +++ b/drivers/gpu/drm/apple/dcp_backlight_test.c @@ -298,6 +298,7 @@ static void backlight_unknown_takeover_test(struct kunit *test) struct dcp_backlight_present present; KUNIT_ASSERT_EQ(test, dcp_bl_init(&state, 600, false, 0, true, 300), 0); + state.preserve_inherited = true; KUNIT_EXPECT_EQ(test, dcp_bl_actual(&state), -ENODATA); /* Core suspend/blank and resume do not turn the guess into a write. */ KUNIT_ASSERT_EQ(test, dcp_bl_request(&state, 300, true, true), 0); @@ -312,15 +313,20 @@ static void backlight_unknown_takeover_test(struct kunit *test) KUNIT_EXPECT_EQ(test, dcp_bl_actual(&state), 250); KUNIT_EXPECT_FALSE(test, state.dirty); - /* An explicit write, even of the default, takes ownership. */ + /* Echoing the unknown registration value leaves the panel alone. */ KUNIT_ASSERT_EQ(test, dcp_bl_init(&state, 600, false, 0, true, 300), 0); + state.preserve_inherited = true; KUNIT_ASSERT_EQ(test, dcp_bl_request(&state, 300, false, false), 0); + KUNIT_EXPECT_FALSE(test, state.controlled); + KUNIT_EXPECT_EQ(test, dcp_bl_prepare(&state, true, &present), -EALREADY); + /* A new target takes ownership; rejection still leaves actual unknown. */ + KUNIT_ASSERT_EQ(test, dcp_bl_request(&state, 250, false, false), 0); KUNIT_ASSERT_EQ(test, dcp_bl_prepare(&state, true, &present), 0); KUNIT_EXPECT_TRUE(test, dcp_bl_complete(&state, present.sequence, false)); KUNIT_EXPECT_EQ(test, dcp_bl_actual(&state), -ENODATA); KUNIT_ASSERT_EQ(test, dcp_bl_prepare(&state, true, &present), 0); KUNIT_EXPECT_TRUE(test, dcp_bl_complete(&state, present.sequence, true)); - KUNIT_EXPECT_EQ(test, dcp_bl_actual(&state), 300); + KUNIT_EXPECT_EQ(test, dcp_bl_actual(&state), 250); } static void backlight_fallback_same_level_test(struct kunit *test) diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/display.py b/tools/testing/selftests/drivers/gpu/apple-dcp/display.py index 6dce2e1149ff1..85d12dea91ab2 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/display.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/display.py @@ -46,10 +46,10 @@ def backlight(root): u32 id=1, nits; assert(!dcp_bl_takeover_nits(600,600000,&nits) && nits==600); /* A registration default must never change the loader's visible level. */ - assert(!dcp_bl_init(&s,600,false,0,true,250)); s.fade=true; + assert(!dcp_bl_init(&s,600,false,0,true,300)); s.fade=true;s.preserve_inherited=true; assert(!s.dirty && !s.level_known && dcp_bl_actual(&s)==-ENODATA); - assert(!dcp_bl_request(&s,250,true,true)); - assert(!dcp_bl_request(&s,250,false,false)); + assert(!dcp_bl_request(&s,300,true,true)); + assert(!dcp_bl_request(&s,300,false,false)); assert(!s.controlled && !s.dirty && dcp_bl_actual(&s)==-ENODATA); assert(dcp_bl_swap_prepare(&s,&p,id,true)==-EALREADY); assert(!dcp_bl_request(&s,250,false,false)); From 0c677232749fa9b310d1b43d14889927b98bb0ef Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 06:20:46 +1100 Subject: [PATCH 016/184] selftests: apple-dcp: exercise desktop backlight registration and restore Run the actual registration, get_brightness, controls_device and update_status functions with the backlight-core boundary mocked. Check that echoing the midpoint and core suspend notifications preserve an unknown panel level, while a different target takes control and only its completed present publishes actual_brightness. Assisted-by: LLM Signed-off-by: Ryan Murray --- .../drivers/gpu/apple-dcp/desktop.py | 107 ++++++++++++++++++ .../selftests/drivers/gpu/apple-dcp/run.py | 2 + 2 files changed, 109 insertions(+) create mode 100644 tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py new file mode 100644 index 0000000000000..79d9750a7e316 --- /dev/null +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py @@ -0,0 +1,107 @@ +# SPDX-License-Identifier: GPL-2.0-only OR MIT +# Copyright 2026 Ryan Murray +"""Desktop modeset callbacks through the production dispatcher and DMA policy.""" +from common import block + + +def brightness_takeover(root): + import display + source = (root / 'drivers/gpu/drm/apple/dcp_backlight.c').read_text() + prefix = display.backlight(root).split('static void completed(')[0] + prefix = prefix.replace('static inline ', 'static inline __attribute__((unused)) ') + functions = '\n'.join(block(source, marker) for marker in ( + 'static bool dcp_backlight_desktop(', 'static int dcp_get_brightness(', + 'int dcp_backlight_register(', 'static int dcp_set_brightness(', + 'static bool dcp_backlight_controls_device(')) + return prefix + r''' +#include +#define READ_ONCE(x) (x) +#define DCP_FIRMWARE_V_26_6_DESKTOP 5 +#define DCP_FIRMWARE_H17P 1 +#define DCP_IOMFB_METHODS_H17G 7 +#define MAX_BRIGHTNESS_PART2 510U +#define BACKLIGHT_PLATFORM 1 +#define BACKLIGHT_SCALE_LINEAR 1 +#define BL_CORE_SUSPENDED 1 +#define BL_CORE_FBBLANK 2 +#define DCP_BL_FADE_STEP_MS 16 +#define system_dfl_wq NULL +#define IS_ERR(p) false +#define PTR_ERR(p) (-EINVAL) +#define msecs_to_jiffies(n) (n) +#define spin_lock_irqsave(p,f) do {assert(!*(p));*(p)=true;(f)=0;} while(0) +#define spin_unlock_irqrestore(p,f) do {assert(*(p));*(p)=false;(void)(f);} while(0) +struct device {int unused;}; +struct backlight_properties {int type,brightness,max_brightness,scale,state;}; +struct backlight_device {struct backlight_properties props;void *data;}; +struct apple_dcp { + int fw_compat;bool quiescing; + struct {int iomfb_method_profile;} hw; + struct {u32 maximum,nits,dac;bool update;struct backlight_device *bl_dev;} brightness; + struct {bool lock;struct dcp_backlight_state state;unsigned long fade_start; + int fade_work;void (*kick)(struct apple_dcp *);} backlight; + struct {bool inherited_brightness_valid;u32 inherited_brightness;} *desktop; + struct {struct {struct {struct device *dev;} *dev;int mutex;} base;} *crtc; + struct device *dev; +}; +static unsigned long jiffies; +static struct backlight_device registered; +static const int dcp_backlight_ops; +static void *bl_get_data(struct backlight_device *bd) {return bd->data;} +static bool iomfb_is_m4(struct apple_dcp *d) {(void)d;return false;} +bool dcp_backlight_active(struct apple_dcp *d) {(void)d;return false;} +static void dcp_backlight_desktop_kick(struct apple_dcp *d) {(void)d;} +static void dcp_backlight_kick(struct apple_dcp *d) {(void)d;} +static unsigned fades; +static void queue_delayed_work(void *q,int *work,unsigned delay) +{assert(!q && delay==16);(void)work;fades++;} +static struct backlight_device *devm_backlight_device_register(struct device *dev, + const char *name,struct device *parent,void *data,const int *ops, + const struct backlight_properties *props) +{(void)dev;(void)name;(void)parent;assert(ops==&dcp_backlight_ops); + registered=(struct backlight_device){.props=*props,.data=data};return ®istered;} +static int backlight_get_brightness(struct backlight_device *bd) +{return bd->props.state?0:bd->props.brightness;} +static bool backlight_is_blank(struct backlight_device *bd) {return !!bd->props.state;} +static u32 calculate_dac(struct apple_dcp *d,int nits) {(void)d;(void)nits;assert(false);return 0;} +static int drm_modeset_lock_single_interruptible(int *p) {(void)p;assert(false);return 0;} +static void drm_modeset_unlock(int *p) {(void)p;assert(false);} +static int dcp_backlight_update(struct apple_dcp *d) {(void)d;assert(false);return 0;} +''' + functions + r''' +int main(void) +{ + struct apple_dcp d={.fw_compat=5,.brightness={.maximum=600}}; + __typeof__(*d.desktop) saved={0};d.desktop=&saved; + assert(!dcp_backlight_register(&d)); + assert(registered.props.brightness==300 && registered.props.max_brightness==600); + assert(dcp_get_brightness(®istered)==-ENODATA); + assert(!dcp_backlight_controls_device(®istered,NULL)); + /* A save/restore client echoes the exposed value, not a new target. */ + assert(!dcp_set_brightness(®istered)); + assert(!d.backlight.state.controlled && !d.backlight.state.dirty); + registered.props.state=BL_CORE_SUSPENDED|BL_CORE_FBBLANK; + assert(!dcp_set_brightness(®istered)); + registered.props.state=0;assert(!dcp_set_brightness(®istered)); + assert(!d.backlight.state.controlled && !d.backlight.state.dirty); + assert(dcp_get_brightness(®istered)==-ENODATA); + /* A different explicit target takes over the unknown inherited level. */ + registered.props.brightness=250;assert(!dcp_set_brightness(®istered)); + assert(d.backlight.state.controlled && d.backlight.state.dirty && !fades); + assert(dcp_backlight_controls_device(®istered,NULL)); + struct dcp_backlight_present present; + assert(!dcp_bl_prepare(&d.backlight.state,true,&present) && present.nits==250); + assert(dcp_get_brightness(®istered)==-ENODATA); + assert(dcp_bl_complete(&d.backlight.state,present.sequence,true)); + assert(dcp_get_brightness(®istered)==250); + /* Even the former midpoint is an ordinary faded target after takeover. */ + registered.props.brightness=300;assert(!dcp_set_brightness(®istered)); + assert(d.backlight.state.fading && fades==1 && d.backlight.state.target==300); + /* A real loader hint seeds registration without needing a present. */ + d.brightness.bl_dev=NULL;d.backlight.state=(struct dcp_backlight_state){0}; + saved.inherited_brightness_valid=true;saved.inherited_brightness=180; + assert(!dcp_backlight_register(&d) && registered.props.brightness==180); + assert(dcp_get_brightness(®istered)==180); + assert(dcp_backlight_controls_device(®istered,NULL)); + return 0; +} +''' diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/run.py b/tools/testing/selftests/drivers/gpu/apple-dcp/run.py index 933c8425c9fbc..9f08c6ec53499 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/run.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/run.py @@ -30,6 +30,7 @@ import boot import m4 import display +import desktop def parking_unit(root): @@ -53,6 +54,7 @@ def main(): 'failed-flip-return', 'live-park')) tests = [ ('display', 'panel-backlight-ramp', display.backlight, ((),), ()), + ('display', 'panel-takeover-registration', desktop.brightness_takeover, ((),), ()), ('display', 'panel-brightness-commit', display.brightness_commit, ((),), ()), ('display', 'panel-brightness-takeover', display.takeover, ((),), ()), ('display', 'panel-mode-catalog', display.modes, ((),), ()), From 42a2a2e123dcbccb77b49148fa11b28af348e10f Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 07:07:34 +1100 Subject: [PATCH 017/184] drm/apple: keep late loader hints from replacing a user's target A loader hint can arrive after the backlight property snapshot or while a user writes a new target. Synchronize its reporting value under the backlight ops lock and the panel state lock, without the unconditional actual-to-target copy performed by backlight_force_update(). Reconcile a hint that arrived during device registration too. The host registration test covers both that boundary and a user write racing the hint notification. Neither path submits a brightness change on its own. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/gpu/drm/apple/dcp_backlight.c | 24 +++++++++++++++-- .../drivers/gpu/apple-dcp/desktop.py | 26 ++++++++++++++++--- .../drivers/gpu/apple-dcp/display.py | 5 ++-- 3 files changed, 47 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp_backlight.c b/drivers/gpu/drm/apple/dcp_backlight.c index 042711030eb4b..381bdedee65b1 100644 --- a/drivers/gpu/drm/apple/dcp_backlight.c +++ b/drivers/gpu/drm/apple/dcp_backlight.c @@ -113,6 +113,23 @@ static void dcp_backlight_desktop_kick(struct apple_dcp *dcp) schedule_work(&dcp->bl_update_wq); } +/* A loader hint describes the inherited level, not a new brightness request. */ +static void dcp_backlight_sync_hint(struct apple_dcp *dcp) +{ + struct backlight_device *bd = READ_ONCE(dcp->brightness.bl_dev); + unsigned long flags; + + if (!bd) + return; + /* Match update_status's lock order and never replace a user's target. */ + mutex_lock(&bd->ops_lock); + spin_lock_irqsave(&dcp->backlight.lock, flags); + if (!dcp->backlight.state.controlled && dcp->backlight.state.level_known) + bd->props.brightness = dcp->backlight.state.target; + spin_unlock_irqrestore(&dcp->backlight.lock, flags); + mutex_unlock(&bd->ops_lock); +} + int dcp_backlight_takeover(struct apple_dcp *dcp, u32 millinits) { u32 maximum, nits; @@ -140,8 +157,8 @@ int dcp_backlight_takeover(struct apple_dcp *dcp, u32 millinits) seeded = dcp_bl_seed(&dcp->backlight.state, nits); } spin_unlock_irqrestore(&dcp->backlight.lock, flags); - if (seeded && READ_ONCE(dcp->brightness.bl_dev)) - backlight_force_update(dcp->brightness.bl_dev, BACKLIGHT_UPDATE_HOTKEY); + if (seeded) + dcp_backlight_sync_hint(dcp); return 0; } @@ -717,6 +734,9 @@ int dcp_backlight_register(struct apple_dcp *dcp) return PTR_ERR(bl_dev); dcp->brightness.bl_dev = bl_dev; + /* A hint may have arrived between the property snapshot and registration. */ + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) + dcp_backlight_sync_hint(dcp); if (!dcp_backlight_active(dcp) && dcp->fw_compat != DCP_FIRMWARE_V_26_6_DESKTOP) dcp->brightness.dac = calculate_dac(dcp, dcp->brightness.nits); diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py index 79d9750a7e316..00b42ac0f8ec0 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py @@ -11,6 +11,7 @@ def brightness_takeover(root): prefix = prefix.replace('static inline ', 'static inline __attribute__((unused)) ') functions = '\n'.join(block(source, marker) for marker in ( 'static bool dcp_backlight_desktop(', 'static int dcp_get_brightness(', + 'static void dcp_backlight_sync_hint(', 'int dcp_backlight_register(', 'static int dcp_set_brightness(', 'static bool dcp_backlight_controls_device(')) return prefix + r''' @@ -33,7 +34,7 @@ def brightness_takeover(root): #define spin_unlock_irqrestore(p,f) do {assert(*(p));*(p)=false;(void)(f);} while(0) struct device {int unused;}; struct backlight_properties {int type,brightness,max_brightness,scale,state;}; -struct backlight_device {struct backlight_properties props;void *data;}; +struct backlight_device {struct backlight_properties props;void *data;bool ops_lock;}; struct apple_dcp { int fw_compat;bool quiescing; struct {int iomfb_method_profile;} hw; @@ -45,6 +46,19 @@ def brightness_takeover(root): struct device *dev; }; static unsigned long jiffies; +static bool inject_hint,inject_user; +static void mutex_lock(bool *p) +{ + assert(!*p);*p=true; + if(inject_user) { + inject_user=false; + struct backlight_device *bd=(void *)((char *)p - offsetof(struct backlight_device,ops_lock)); + struct apple_dcp *d=bd->data; + bd->props.brightness=450; + assert(!dcp_bl_request(&d->backlight.state,450,false,false)); + } +} +static void mutex_unlock(bool *p) {assert(*p);*p=false;} static struct backlight_device registered; static const int dcp_backlight_ops; static void *bl_get_data(struct backlight_device *bd) {return bd->data;} @@ -59,7 +73,9 @@ def brightness_takeover(root): const char *name,struct device *parent,void *data,const int *ops, const struct backlight_properties *props) {(void)dev;(void)name;(void)parent;assert(ops==&dcp_backlight_ops); - registered=(struct backlight_device){.props=*props,.data=data};return ®istered;} + registered=(struct backlight_device){.props=*props,.data=data}; + if(inject_hint) {inject_hint=false;struct apple_dcp *d=data;assert(dcp_bl_seed(&d->backlight.state,180));} + return ®istered;} static int backlight_get_brightness(struct backlight_device *bd) {return bd->props.state?0:bd->props.brightness;} static bool backlight_is_blank(struct backlight_device *bd) {return !!bd->props.state;} @@ -98,10 +114,14 @@ def brightness_takeover(root): assert(d.backlight.state.fading && fades==1 && d.backlight.state.target==300); /* A real loader hint seeds registration without needing a present. */ d.brightness.bl_dev=NULL;d.backlight.state=(struct dcp_backlight_state){0}; - saved.inherited_brightness_valid=true;saved.inherited_brightness=180; + inject_hint=true; assert(!dcp_backlight_register(&d) && registered.props.brightness==180); assert(dcp_get_brightness(®istered)==180); assert(dcp_backlight_controls_device(®istered,NULL)); + /* A user write racing the late-hint notification keeps its target. */ + inject_user=true;dcp_backlight_sync_hint(&d); + assert(d.backlight.state.controlled && d.backlight.state.target==450); + assert(registered.props.brightness==450 && !registered.ops_lock); return 0; } ''' diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/display.py b/tools/testing/selftests/drivers/gpu/apple-dcp/display.py index 85d12dea91ab2..41aaa8b28fe51 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/display.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/display.py @@ -316,10 +316,9 @@ def takeover(root): struct {bool lock;struct dcp_backlight_state state;} backlight; struct {bool inherited_brightness_valid;u32 inherited_brightness;} *desktop; }; -#define BACKLIGHT_UPDATE_HOTKEY 1 static unsigned legacy,notifications; -static void backlight_force_update(void *bd,int reason) -{assert(bd && reason==BACKLIGHT_UPDATE_HOTKEY);notifications++;} +static void dcp_backlight_sync_hint(struct apple_dcp *d) +{assert(d->brightness.bl_dev);notifications++;} bool dcp_backlight_active(struct apple_dcp *d) {return d->fw_compat==1 && d->backlight.state.ready;} static int iomfb_configure_backlight_h17p(struct apple_dcp *d,u32 max,bool valid,u32 nits,bool fallback,u32 guessed) {assert(max==600 && valid && nits==100 && !fallback && !guessed);legacy++;return 0;} From 99766390022d64c236312387a4c945953d01c882 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 07:17:30 +1100 Subject: [PATCH 018/184] drm/apple: scope zero VRR placeholders to desktop firmware A pair of zero range fields is missing metadata on desktop 26.6, but older panel paths may use it as an explicit fixed-refresh range. Carry the firmware convention in the parse context instead of applying it to every internal ProMotion panel. Exercise the actual catalog replacement path in KUnit for desktop and legacy firmware, and retain host coverage for zero placeholders, absent fields, partial ranges and duplicates. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/gpu/drm/apple/iomfb.c | 1 + drivers/gpu/drm/apple/parser-test.c | 29 ++++++++++++++++--- drivers/gpu/drm/apple/parser.c | 8 ++--- drivers/gpu/drm/apple/parser.h | 2 ++ .../drivers/gpu/apple-dcp/display.py | 6 ++-- 5 files changed, 36 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 6d5e91fa3fe35..1575f57c783bb 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -1630,6 +1630,7 @@ int dcp_modes_replace(struct apple_dcp *dcp, struct dcp_parse_ctx *ctx, if (ret) return ret; } + ctx->zero_vrr_range_fallback = dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP; modes = enumerate_modes(ctx, &nr_modes, dcp->width_mm, dcp->height_mm, dcp->notch_height, dcp->fixed_connector_type == DRM_MODE_CONNECTOR_eDP, diff --git a/drivers/gpu/drm/apple/parser-test.c b/drivers/gpu/drm/apple/parser-test.c index 749ad582caccd..05648a14cf8e6 100644 --- a/drivers/gpu/drm/apple/parser-test.c +++ b/drivers/gpu/drm/apple/parser-test.c @@ -448,7 +448,8 @@ static void parser_vrr_empty_range(struct kunit *test) struct parser_record record = valid_record(7, 10); struct dcp_display_mode *modes; struct parser_blob *blob; - unsigned int count; + unsigned int count, desktop; + struct dcp_parse_ctx ctx; /* Desktop firmware can publish both fields as zero placeholders. */ record.rate = 120 << 16; @@ -459,9 +460,9 @@ static void parser_vrr_empty_range(struct kunit *test) modes = parse_blob(blob, &count, 0, true); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, modes); KUNIT_ASSERT_EQ(test, count, 1U); - KUNIT_EXPECT_TRUE(test, modes[0].vrr); - KUNIT_EXPECT_EQ(test, modes[0].min_vrr, 24U << 16); - KUNIT_EXPECT_EQ(test, modes[0].max_vrr, 120U << 16); + KUNIT_EXPECT_FALSE(test, modes[0].vrr); + KUNIT_EXPECT_EQ(test, modes[0].min_vrr, 0U); + KUNIT_EXPECT_EQ(test, modes[0].max_vrr, 0U); kfree(modes); /* A display without the internal ProMotion policy stays fixed. */ @@ -472,6 +473,26 @@ static void parser_vrr_empty_range(struct kunit *test) KUNIT_EXPECT_EQ(test, modes[0].min_vrr, 0U); KUNIT_EXPECT_EQ(test, modes[0].max_vrr, 0U); kfree(modes); + /* The catalog owner enables this convention only for desktop firmware. */ + for (desktop = 0; desktop < 2; desktop++) { + struct apple_dcp *dcp = kunit_kzalloc(test, sizeof(*dcp), GFP_KERNEL); + u64 generation; + + KUNIT_ASSERT_NOT_NULL(test, dcp); + parser_modes_init(dcp); + dcp->fw_compat = desktop ? DCP_FIRMWARE_V_26_6_DESKTOP : + DCP_FIRMWARE_V_13_5; + dcp->fixed_connector_type = DRM_MODE_CONNECTOR_eDP; + KUNIT_ASSERT_EQ(test, parse(blob->data, blob->size, &ctx), 0); + ctx.dcp = &parser_dcp; + generation = dcp_modes_transfer_begin(dcp); + KUNIT_ASSERT_EQ(test, dcp_modes_replace(dcp, &ctx, generation), 0); + KUNIT_ASSERT_EQ(test, dcp->nr_modes, 1U); + KUNIT_EXPECT_EQ(test, dcp->modes[0].vrr, !!desktop); + KUNIT_EXPECT_EQ(test, dcp->modes[0].min_vrr, desktop ? 24U << 16 : 0U); + KUNIT_EXPECT_EQ(test, dcp->modes[0].max_vrr, desktop ? 120U << 16 : 0U); + kfree(dcp->modes); + } } static void parser_replacement(struct kunit *test) diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index 761271339bed1..25a2562f34807 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -724,7 +724,7 @@ static int parse_mode(struct dcp_parse_ctx *handle, struct timing_color_restrictions limits = {}; struct dcp_color_mode unsafe_rgb8 = { .score = -1 }; bool have_horiz = false, have_vert = false, have_virtual = false; - bool duplicate_scope = false, vrr_ambiguous = false; + bool duplicate_scope = false, vrr_ambiguous = false, vrr_fallback; unsigned int vrr_seen = 0; s64 min_vrr = 0, max_vrr = 0; u64 clock; @@ -876,13 +876,13 @@ static int parse_mode(struct dcp_parse_ctx *handle, return -EINVAL; /* - * External H17P stays fixed. A pair of zero placeholders is also an + * External H17P stays fixed. Desktop zero placeholders are also an * absent range; a partial or duplicate pair must not enable fallback. */ + vrr_fallback = !vrr_seen || (vrr_seen == 3 && handle->zero_vrr_range_fallback); if (!external_sdr && !vrr_ambiguous) out->vrr = dcp_refresh_range(vert.precise_sync_rate, min_vrr, - max_vrr, internal && - (!vrr_seen || vrr_seen == 3), + max_vrr, internal && vrr_fallback, &out->min_vrr, &out->max_vrr); out->needs_timestamps = out->vrr || (internal && dcp_refresh_is_promotion(vert.precise_sync_rate)); diff --git a/drivers/gpu/drm/apple/parser.h b/drivers/gpu/drm/apple/parser.h index ef9420c1109f9..135124680ec6a 100644 --- a/drivers/gpu/drm/apple/parser.h +++ b/drivers/gpu/drm/apple/parser.h @@ -21,6 +21,8 @@ struct dcp_parse_ctx { /* External H17P candidates require separate publication/lookup proof. */ bool automatic_1080p180_rgb8; bool ambiguous_180hz; + /* Desktop 26.6 uses zero placeholders for an unreported VRR range. */ + bool zero_vrr_range_fallback; #if IS_ENABLED(CONFIG_DRM_APPLE_DCP_T6030_AUDIO) /* * T6030 external processor audio elements (DCP firmware 14.7, external diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/display.py b/tools/testing/selftests/drivers/gpu/apple-dcp/display.py index 41aaa8b28fe51..e91202924f264 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/display.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/display.py @@ -103,14 +103,14 @@ def modes(root): checks = [] blobs = [] - def case(elements, expected, ranges=None): + def case(elements, expected, ranges=None, desktop=True): raw = bytearray(struct.pack(' Date: Mon, 26 Oct 2026 08:35:28 +1100 Subject: [PATCH 019/184] drm/apple: report takeover brightness and fade the first change Keep the inherited panel level untouched until an explicit target changes. Report the target while no loader hint or completed present has supplied a measured level, then report the completed level. An unchanged reporting default still cannot acquire brightness control. Use the reporting level as the first fade's origin when the loader report is absent. A valid hint remains the preferred origin. Cover readback, unchanged restore, the first fade and its completion in KUnit and the host backlight fixture. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/gpu/drm/apple/dcp_backlight.h | 9 ++--- drivers/gpu/drm/apple/dcp_backlight_test.c | 36 ++++++++++++++++--- .../drivers/gpu/apple-dcp/desktop.py | 11 +++--- .../drivers/gpu/apple-dcp/display.py | 8 +++-- 4 files changed, 48 insertions(+), 16 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp_backlight.h b/drivers/gpu/drm/apple/dcp_backlight.h index 7683883137224..704e985f49a78 100644 --- a/drivers/gpu/drm/apple/dcp_backlight.h +++ b/drivers/gpu/drm/apple/dcp_backlight.h @@ -109,12 +109,12 @@ static inline bool dcp_bl_seed(struct dcp_backlight_state *state, u32 nits) return true; } -/* Do not let userspace save or restore an unmeasured registration default. */ +/* Report a usable target until a hint or completed present gives the level. */ static inline int dcp_bl_actual(const struct dcp_backlight_state *state) { - if (!state->ready || !state->level_known) + if (!state->ready) return -ENODATA; - return state->actual; + return state->level_known ? state->actual : state->target; } static inline bool dcp_bl_lit(const struct dcp_backlight_state *state) @@ -174,7 +174,8 @@ static inline int dcp_bl_request(struct dcp_backlight_state *state, u32 nits, state->target = nits; state->core_blank = core_blank; state->suspended = suspended; - if (state->fade && state->level_known && was_lit && + /* With no hint, the reporting target is the first fade's origin. */ + if (state->fade && (state->level_known || state->preserve_inherited) && was_lit && dcp_bl_lit(state) && state->level != nits) { state->fade_from = state->level; state->fading = true; diff --git a/drivers/gpu/drm/apple/dcp_backlight_test.c b/drivers/gpu/drm/apple/dcp_backlight_test.c index 48959efd2665a..57029cecb2f23 100644 --- a/drivers/gpu/drm/apple/dcp_backlight_test.c +++ b/drivers/gpu/drm/apple/dcp_backlight_test.c @@ -299,14 +299,14 @@ static void backlight_unknown_takeover_test(struct kunit *test) KUNIT_ASSERT_EQ(test, dcp_bl_init(&state, 600, false, 0, true, 300), 0); state.preserve_inherited = true; - KUNIT_EXPECT_EQ(test, dcp_bl_actual(&state), -ENODATA); + KUNIT_EXPECT_EQ(test, dcp_bl_actual(&state), 300); /* Core suspend/blank and resume do not turn the guess into a write. */ KUNIT_ASSERT_EQ(test, dcp_bl_request(&state, 300, true, true), 0); KUNIT_ASSERT_EQ(test, dcp_bl_request(&state, 300, false, false), 0); KUNIT_EXPECT_FALSE(test, state.controlled); KUNIT_EXPECT_FALSE(test, state.dirty); KUNIT_EXPECT_EQ(test, dcp_bl_prepare(&state, true, &present), -EALREADY); - KUNIT_EXPECT_EQ(test, dcp_bl_actual(&state), -ENODATA); + KUNIT_EXPECT_EQ(test, dcp_bl_actual(&state), 300); /* A late loader hint supplies the inherited level without a present. */ KUNIT_EXPECT_TRUE(test, dcp_bl_seed(&state, 250)); @@ -319,16 +319,43 @@ static void backlight_unknown_takeover_test(struct kunit *test) KUNIT_ASSERT_EQ(test, dcp_bl_request(&state, 300, false, false), 0); KUNIT_EXPECT_FALSE(test, state.controlled); KUNIT_EXPECT_EQ(test, dcp_bl_prepare(&state, true, &present), -EALREADY); - /* A new target takes ownership; rejection still leaves actual unknown. */ + /* A new target is reported even before its present completes. */ KUNIT_ASSERT_EQ(test, dcp_bl_request(&state, 250, false, false), 0); KUNIT_ASSERT_EQ(test, dcp_bl_prepare(&state, true, &present), 0); KUNIT_EXPECT_TRUE(test, dcp_bl_complete(&state, present.sequence, false)); - KUNIT_EXPECT_EQ(test, dcp_bl_actual(&state), -ENODATA); + KUNIT_EXPECT_EQ(test, dcp_bl_actual(&state), 250); KUNIT_ASSERT_EQ(test, dcp_bl_prepare(&state, true, &present), 0); KUNIT_EXPECT_TRUE(test, dcp_bl_complete(&state, present.sequence, true)); KUNIT_EXPECT_EQ(test, dcp_bl_actual(&state), 250); } +static void backlight_first_change_fades_test(struct kunit *test) +{ + struct dcp_backlight_state state; + struct dcp_backlight_present present; + + KUNIT_ASSERT_EQ(test, dcp_bl_init(&state, 600, false, 0, true, 300), 0); + state.preserve_inherited = true; + state.fade = true; + KUNIT_EXPECT_EQ(test, dcp_bl_actual(&state), 300); + KUNIT_ASSERT_EQ(test, dcp_bl_request(&state, 300, false, false), 0); + KUNIT_EXPECT_FALSE(test, state.dirty); + KUNIT_EXPECT_FALSE(test, state.controlled); + KUNIT_ASSERT_EQ(test, dcp_bl_request(&state, 100, false, false), 0); + KUNIT_EXPECT_TRUE(test, state.fading); + KUNIT_EXPECT_FALSE(test, state.level_known); + KUNIT_EXPECT_FALSE(test, state.dirty); + KUNIT_EXPECT_EQ(test, state.fade_from, 300U); + KUNIT_EXPECT_EQ(test, dcp_bl_actual(&state), 100); + KUNIT_EXPECT_TRUE(test, dcp_bl_fade_step(&state, 125, 250)); + KUNIT_EXPECT_EQ(test, state.level, 200U); + KUNIT_ASSERT_EQ(test, dcp_bl_prepare(&state, true, &present), 0); + KUNIT_EXPECT_TRUE(test, dcp_bl_complete(&state, present.sequence, true)); + KUNIT_EXPECT_EQ(test, dcp_bl_actual(&state), 200); + KUNIT_EXPECT_FALSE(test, dcp_bl_fade_step(&state, 250, 250)); + KUNIT_EXPECT_EQ(test, state.level, 100U); +} + static void backlight_fallback_same_level_test(struct kunit *test) { struct dcp_backlight_state state; @@ -586,6 +613,7 @@ static struct kunit_case backlight_cases[] = { KUNIT_CASE(backlight_resend_test), KUNIT_CASE(backlight_fallback_test), KUNIT_CASE(backlight_unknown_takeover_test), + KUNIT_CASE(backlight_first_change_fades_test), KUNIT_CASE(backlight_fallback_same_level_test), KUNIT_CASE(backlight_seed_known_test), KUNIT_CASE(backlight_desktop_swap_test), diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py index 00b42ac0f8ec0..f6d0cd4de7c64 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py @@ -90,7 +90,7 @@ def brightness_takeover(root): __typeof__(*d.desktop) saved={0};d.desktop=&saved; assert(!dcp_backlight_register(&d)); assert(registered.props.brightness==300 && registered.props.max_brightness==600); - assert(dcp_get_brightness(®istered)==-ENODATA); + assert(dcp_get_brightness(®istered)==300); assert(!dcp_backlight_controls_device(®istered,NULL)); /* A save/restore client echoes the exposed value, not a new target. */ assert(!dcp_set_brightness(®istered)); @@ -99,19 +99,20 @@ def brightness_takeover(root): assert(!dcp_set_brightness(®istered)); registered.props.state=0;assert(!dcp_set_brightness(®istered)); assert(!d.backlight.state.controlled && !d.backlight.state.dirty); - assert(dcp_get_brightness(®istered)==-ENODATA); + assert(dcp_get_brightness(®istered)==300); /* A different explicit target takes over the unknown inherited level. */ registered.props.brightness=250;assert(!dcp_set_brightness(®istered)); - assert(d.backlight.state.controlled && d.backlight.state.dirty && !fades); + assert(d.backlight.state.controlled && d.backlight.state.fading && !d.backlight.state.dirty && fades==1); + assert(!dcp_bl_fade_step(&d.backlight.state,250,250)); assert(dcp_backlight_controls_device(®istered,NULL)); struct dcp_backlight_present present; assert(!dcp_bl_prepare(&d.backlight.state,true,&present) && present.nits==250); - assert(dcp_get_brightness(®istered)==-ENODATA); + assert(dcp_get_brightness(®istered)==250); assert(dcp_bl_complete(&d.backlight.state,present.sequence,true)); assert(dcp_get_brightness(®istered)==250); /* Even the former midpoint is an ordinary faded target after takeover. */ registered.props.brightness=300;assert(!dcp_set_brightness(®istered)); - assert(d.backlight.state.fading && fades==1 && d.backlight.state.target==300); + assert(d.backlight.state.fading && fades==2 && d.backlight.state.target==300); /* A real loader hint seeds registration without needing a present. */ d.brightness.bl_dev=NULL;d.backlight.state=(struct dcp_backlight_state){0}; inject_hint=true; diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/display.py b/tools/testing/selftests/drivers/gpu/apple-dcp/display.py index e91202924f264..e5793a84e0bb0 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/display.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/display.py @@ -47,13 +47,15 @@ def backlight(root): assert(!dcp_bl_takeover_nits(600,600000,&nits) && nits==600); /* A registration default must never change the loader's visible level. */ assert(!dcp_bl_init(&s,600,false,0,true,300)); s.fade=true;s.preserve_inherited=true; - assert(!s.dirty && !s.level_known && dcp_bl_actual(&s)==-ENODATA); + assert(!s.dirty && !s.level_known && dcp_bl_actual(&s)==300); assert(!dcp_bl_request(&s,300,true,true)); assert(!dcp_bl_request(&s,300,false,false)); - assert(!s.controlled && !s.dirty && dcp_bl_actual(&s)==-ENODATA); + assert(!s.controlled && !s.dirty && dcp_bl_actual(&s)==300); assert(dcp_bl_swap_prepare(&s,&p,id,true)==-EALREADY); assert(!dcp_bl_request(&s,250,false,false)); - assert(!s.fading && s.dirty); + assert(s.fading && !s.dirty); + assert(dcp_bl_fade_step(&s,125,250) && s.level==275); + assert(!dcp_bl_fade_step(&s,250,250) && s.level==250); assert(dcp_bl_swap_prepare(&s,&p,id,false)==-ENODATA); assert(!dcp_bl_swap_prepare(&s,&p,id,true)); completed(&s,&p,id++,true); From 1a20d7709805ea69cd246daf2056fcf4692eb428 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 08:58:52 +1100 Subject: [PATCH 020/184] drm/apple: bind the remaining desktop memory and resize callbacks The shared template pairs descriptor allocation and release with D452 physical mapping. The public native 26.6 backend also handles D588 as an empty resize notification. Bind both in the desktop table after its D202/D454 adapters, preserving the shared display-register bounds and resource ownership checks. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/gpu/drm/apple/iomfb_v26_6_desktop.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/apple/iomfb_v26_6_desktop.c b/drivers/gpu/drm/apple/iomfb_v26_6_desktop.c index 05417d2c2b2cb..4a398d4db33f0 100644 --- a/drivers/gpu/drm/apple/iomfb_v26_6_desktop.c +++ b/drivers/gpu/drm/apple/iomfb_v26_6_desktop.c @@ -178,6 +178,7 @@ static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = { [414] = trampoline_sr_set_property_int, [415] = trampoline_true, /* sr_set_property_bool */ [451] = trampoline_allocate_buffer, + [452] = trampoline_map_physical, [454] = trampoline_release_mem_desc_v26_6, [552] = trampoline_true, /* set_property_dict_0 */ [561] = trampoline_true, /* set_property_dict */ @@ -190,6 +191,7 @@ static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = { [576] = trampoline_nop, [581] = trampoline_nop, [582] = trampoline_true, /* create_default_fb_surface */ + [588] = trampoline_nop, /* resize_default_fb_surface_gated */ [590] = trampoline_swap_complete, [592] = trampoline_swap_complete_intent_gated, [593] = trampoline_abort_swap_ap_gated, From b81e397b79468fd4bf872dd32fbc528ef65b86b0 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 08:59:39 +1100 Subject: [PATCH 021/184] drm/apple: drain unsupported desktop modesets before reporting failure Bound callback envelopes and descriptor records before decoding. An unknown callback during a mode set gets an unsupported zero reply and records an error. Drain the real enclosing RPC ACK, stop the continuation and return EOPNOTSUPP. Signal an error on the accepted atomic operation out-fence. A timed-out command retains ownership of its busy channel. Exercise the contributor-provided desktop D202/D454 adapters and their normal allocation lifecycle. Inject an unknown callback during A413, drain the ACK, fail that set and complete a later set. Check every mode-selection continuation boundary. Keep the panel mode list from DCP now that the known mode-change callbacks are handled. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/gpu/drm/apple/apple_drv.c | 7 +- drivers/gpu/drm/apple/dcp-internal.h | 3 + drivers/gpu/drm/apple/iomfb.c | 23 +- drivers/gpu/drm/apple/iomfb_template.c | 28 +++ drivers/gpu/drm/apple/iomfb_v26_6_desktop.c | 18 ++ drivers/gpu/drm/apple/iomfb_v26_6_desktop.h | 2 + .../selftests/drivers/gpu/apple-dcp/Makefile | 2 +- .../drivers/gpu/apple-dcp/desktop.py | 231 ++++++++++++++++++ .../drivers/gpu/apple-dcp/display.py | 15 +- .../drivers/gpu/apple-dcp/lifecycle.py | 8 +- .../selftests/drivers/gpu/apple-dcp/run.py | 4 +- 11 files changed, 330 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 2ce9fb420f2f4..8fc8cb7f7a6ab 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -268,8 +268,11 @@ static void apple_crtc_atomic_enable(struct drm_crtc *crtc, /* A cable generation can change while the CRTC stays active. */ dcp_external_recover(to_apple_crtc(crtc)->dcp); } - if (crtc_state->active) - dcp_crtc_atomic_modeset(crtc, commit); + if (crtc_state->active) { + ret = dcp_crtc_atomic_modeset(crtc, commit); + if (ret) + dcp_drm_crtc_error(apple_crtc, crtc_state, ret); + } dcp_typec_follow_done(locked); return; diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index f3c8ec3cc2fa5..9aa05a5b3b257 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -243,6 +243,9 @@ struct dcp_desktop_state { * element before the real one is selected. */ bool mode_live; + /* RX records an unsupported callback until the modeset RPC has drained. */ + bool modeset_active; + bool modeset_callback_error; /* An opt-in adaptive-sync failure stays fixed until the next boot. */ bool vrr_failed; bool vrr_requested; diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 1575f57c783bb..6cc67676fb9db 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -1299,7 +1299,8 @@ static void dcpep_handle_cb(struct apple_dcp *dcp, enum dcp_context_id context, u8 depth; bool handled, zero_reply; - if (dcp->fw_compat == DCP_FIRMWARE_H17P && + if ((dcp->fw_compat == DCP_FIRMWARE_H17P || + dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) && (length < sizeof(*hdr) || !ch || ch->depth >= DCP_MAX_CALL_DEPTH || offset >= 0x8000 || length > 0x8000 - offset)) { dev_err(dev, "invalid IOMFB callback envelope\n"); @@ -1309,6 +1310,14 @@ static void dcpep_handle_cb(struct apple_dcp *dcp, enum dcp_context_id context, tag = dcp_parse_tag(hdr->tag); + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && + (!iomfb_check_callback_v26_6_desktop(tag, hdr->in_len, hdr->out_len) || + (u64)sizeof(*hdr) + hdr->in_len + hdr->out_len != length)) { + dev_err(dev, "invalid desktop IOMFB callback %d (%u, %u)\n", + tag, hdr->in_len, hdr->out_len); + dcp->crashed = true; + return; + } handled = tag >= 0 && tag < IOMFB_MAX_CB && dcp->cb_handlers && dcp->cb_handlers[tag]; @@ -1344,11 +1353,15 @@ static void dcpep_handle_cb(struct apple_dcp *dcp, enum dcp_context_id context, return; } /* - * Leaving a callback unanswered wedges the coprocessor: it - * waits for the ack forever and the outer call never returns. - * On H17P answer it with a zeroed output instead. + * Drain an unsupported desktop callback too. The outer modeset + * must see an error even if firmware accepts the zero reply; + * never pop its command before its real ACK arrives. */ - if (dcp->fw_compat == DCP_FIRMWARE_H17P) { + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && + READ_ONCE(dcp->desktop->modeset_active)) + WRITE_ONCE(dcp->desktop->modeset_callback_error, true); + if (dcp->fw_compat == DCP_FIRMWARE_H17P || + dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) { if (hdr->out_len) memset(out, 0, hdr->out_len); depth = dcp_push_depth(&ch->depth); diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 0bddf9fa0aba6..abb1eb8bd6dac 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -2857,6 +2857,15 @@ struct dcp_modeset_cookie { u32 min_vrr; }; +/* Finish only after the RPC which contained the unknown callback has replied. */ +static bool dcp_modeset_callback_failed(struct apple_dcp *dcp, void *cookie) +{ + if (!READ_ONCE(dcp->desktop->modeset_callback_error)) + return false; + complete_set_digital_out_mode(dcp, NULL, cookie); + return true; +} + /* * get_mode_info (A364) fills the firmware's mode record for a timing and * color pair before set_digital_out_mode (A413) selects it. The request @@ -2899,6 +2908,8 @@ static void dcp_modeset_actual_done(struct apple_dcp *dcp, void *data, { struct dcp_modeset_cookie *modeset = cookie; + if (dcp_modeset_callback_failed(dcp, cookie)) + return; if (data && get_unaligned_le32(data) && modeset->vrr) { dcp_modeset_fixed_fallback(dcp, modeset); return; @@ -2911,6 +2922,8 @@ static void dcp_modeset_after_actual_info(struct apple_dcp *dcp, void *data, { struct dcp_modeset_cookie *modeset = cookie; + if (dcp_modeset_callback_failed(dcp, cookie)) + return; if (!data) { complete_set_digital_out_mode(dcp, NULL, cookie); return; @@ -2924,6 +2937,8 @@ static void dcp_modeset_after_bootstrap(struct apple_dcp *dcp, void *data, { struct dcp_modeset_cookie *modeset = cookie; + if (dcp_modeset_callback_failed(dcp, cookie)) + return; if (!data || get_unaligned_le32(data)) { dcp_modeset_actual_done(dcp, data, cookie); return; @@ -2937,6 +2952,8 @@ static void dcp_modeset_after_bootstrap_info(struct apple_dcp *dcp, void *data, { struct dcp_modeset_cookie *modeset = cookie; + if (dcp_modeset_callback_failed(dcp, cookie)) + return; if (!data) { complete_set_digital_out_mode(dcp, NULL, cookie); return; @@ -2963,6 +2980,8 @@ static void dcp_modeset_sync_done(struct apple_dcp *dcp, void *data, { struct dcp_modeset_cookie *modeset = cookie; + if (dcp_modeset_callback_failed(dcp, cookie)) + return; if (!data || get_unaligned_le32(data)) { if (data && modeset->vrr) { dcp_modeset_fixed_fallback(dcp, modeset); @@ -2999,6 +3018,9 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, if (dcp->crashed) return -ENODEV; + /* A timed-out command still owns its channel until firmware replies. */ + if (READ_ONCE(dcp->ch_cmd.depth)) + return -EBUSY; if (!lookup_mode(dcp, &crtc_state->mode, mode)) { dev_err(dcp->dev, "no match for " DRM_MODE_FMT "\n", @@ -3077,6 +3099,8 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, modeset->bootstrap.timing_mode_id); } + WRITE_ONCE(dcp->desktop->modeset_callback_error, false); + WRITE_ONCE(dcp->desktop->modeset_active, true); if (modeset->program_sync) dcp_modeset_sync(dcp, cookie); else @@ -3097,6 +3121,9 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, dev_err(dcp->dev, "waiting on set_digital_out_mode failed:%d\n", ret); ret = -EIO; + } else if (READ_ONCE(dcp->desktop->modeset_callback_error)) { + dev_err(dcp->dev, "unsupported callback during mode set\n"); + ret = -EOPNOTSUPP; } else if (!READ_ONCE(cookie->replied)) { dev_err(dcp->dev, "set_digital_out_mode got no reply\n"); ret = -EIO; @@ -3110,6 +3137,7 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, jiffies_to_msecs(ret)); ret = 0; } + WRITE_ONCE(dcp->desktop->modeset_active, false); dcp->vrr_enabled = !ret && modeset->vrr; if (modeset->notify_vrr_failure && dcp->connector) dcp_queue_hotplug(dcp->connector); diff --git a/drivers/gpu/drm/apple/iomfb_v26_6_desktop.c b/drivers/gpu/drm/apple/iomfb_v26_6_desktop.c index 4a398d4db33f0..ed7f5faadf0f6 100644 --- a/drivers/gpu/drm/apple/iomfb_v26_6_desktop.c +++ b/drivers/gpu/drm/apple/iomfb_v26_6_desktop.c @@ -91,6 +91,24 @@ static bool trampoline_passthrough_dict_v26_6(struct apple_dcp *dcp, int tag, return true; } +bool iomfb_check_callback_v26_6_desktop(int tag, u32 in_len, u32 out_len) +{ + switch (tag) { + case 201: + return in_len == 12 && out_len == 16; + case 202: + return in_len == 24 && !out_len; + case 451: + return in_len == 20 && out_len == 28; + case 452: + return in_len == 24 && out_len == 20; + case 454: + return in_len == 4 && out_len == 4; + default: + return true; + } +} + /* * unmap_piodma (D202): the buffer id, the id again, the DVA (unaligned) and a * trailing word. The older firmware sends the DVA after a virtual address. diff --git a/drivers/gpu/drm/apple/iomfb_v26_6_desktop.h b/drivers/gpu/drm/apple/iomfb_v26_6_desktop.h index 697edc47fde3b..6f61493858e8b 100644 --- a/drivers/gpu/drm/apple/iomfb_v26_6_desktop.h +++ b/drivers/gpu/drm/apple/iomfb_v26_6_desktop.h @@ -49,4 +49,6 @@ struct dcp_set_frame_sync_props_resp_v26_6_desktop { u8 props[0x50]; } __packed; +bool iomfb_check_callback_v26_6_desktop(int tag, u32 in_len, u32 out_len); + #endif /* __APPLE_IOMFB_V26_6_DESKTOP_H__ */ diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/Makefile b/tools/testing/selftests/drivers/gpu/apple-dcp/Makefile index af6d27b20f062..2c25d757aca5e 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/Makefile +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/Makefile @@ -1,4 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only OR MIT TEST_PROGS := run.sh m4-error-paths.py plane-layout.py -TEST_FILES := run.py common.py edid.py recovery.py rate.py phy.py lifecycle.py pm.py idle.py boot.py quiesce.py transaction.py accounting.py m4.py m4-fixtures native-m4-kms.c run-native-m4-kms.py dwc3 README +TEST_FILES := run.py common.py desktop.py display.py edid.py recovery.py rate.py phy.py lifecycle.py pm.py idle.py boot.py quiesce.py transaction.py accounting.py m4.py m4-fixtures native-m4-kms.c run-native-m4-kms.py dwc3 README include ../../../lib.mk diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py index f6d0cd4de7c64..1ccc513a96f35 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py @@ -2,6 +2,195 @@ # Copyright 2026 Ryan Murray """Desktop modeset callbacks through the production dispatcher and DMA policy.""" from common import block +import lifecycle + + +def modeset_unmap(root): + base = root / 'drivers/gpu/drm/apple' + iomfb = (base / 'iomfb.c').read_text() + template = (base / 'iomfb_template.c').read_text() + desktop = (base / 'iomfb_v26_6_desktop.c').read_text() + header = (base / 'iomfb_v26_6_desktop.h').read_text() + # Reuse the transport boundary mocks, retaining the real callback dispatcher. + prefix = lifecycle.dispatcher(root).split('static bool dcp_external_zero_reply(')[0] + prefix = prefix.replace('struct dcp_channel { unsigned depth; void *output[8]; unsigned end[8]; };', r''' +struct apple_dcp; +typedef void (*dcp_callback_t)(struct apple_dcp *, void *, void *); +struct dcp_channel { + unsigned depth; void *output[8], *cookies[8]; unsigned end[8]; + dcp_callback_t callbacks[8]; struct dcp_packet_header header[8]; +}; +struct sg_table {void *sgl;}; +struct dcp_mem_descriptor {void *buf;u64 dva,reg;size_t size;bool piodma_mapped;struct sg_table map;}; +''') + prefix = prefix.replace(' struct dcp_channel channel;', r''' + struct dcp_channel channel, command; + struct dcp_mem_descriptor memdesc[4]; + unsigned long memdesc_map[1];void *iommu_dom; + struct resource {u64 start,end;} *disp_registers[1];int nr_disp_registers; +''') + prefix = prefix.replace('DCP_CONTEXT_CB = 0, DCP_CONTEXT_ASYNC = 3', + 'DCP_CONTEXT_CB = 0, DCP_CONTEXT_CMD = 1, DCP_CONTEXT_ASYNC = 3') + prefix = prefix.replace('(void)c; return &d->channel;', + 'return c==DCP_CONTEXT_CMD?&d->command:&d->channel;') + prefix = prefix.replace('((void)dev)', '((void)dcp->dev)') + prefix += r''' +#include +typedef u64 __le64;typedef u32 __le32; +#define __packed __attribute__((packed)) +typedef u64 dma_addr_t; +#define DCP_FW_NAME(n) n +#define DCP_FW_H17(v) 0 +#define DCP_FW_DESKTOP_26(v) 1 +#define SZ_16K 16384U +#define ARRAY_SIZE(a) (sizeof(a)/sizeof((a)[0])) +#define DCP_MAX_MAPPINGS 4 +#undef static_assert +#define static_assert(c) _Static_assert(c,#c) +#define trace_iomfb_callback(...) ((void)0) +#define dev_warn_dma(...) ((void)0) +struct dcp_map_physical_req {u64 paddr,size;u32 flags;u8 dva_null,dva_size_null,padding[2];} __attribute__((packed)); +struct dcp_map_physical_resp {u64 dva,dva_size;u32 mem_desc_id;} __attribute__((packed)); +struct dcp_map_buf_req {u64 buffer;}; +struct dcp_map_buf_resp {u64 buffer,dva;}; +struct dcp_unmap_buf_resp {u64 buffer,vaddr,dva;u8 unk,buf_null;}; +struct dcp_wait_cookie {unsigned status;bool replied;int done,refcount;}; +#define dev_warn_release(...) ((void)0) +#define dev_err_release(...) ((void)0) +#define DMA_BIDIRECTIONAL 0 +#define round_down(x,y) ((x) & ~((__typeof__(x))(y)-1)) +#define check_add_overflow(a,b,p) __builtin_add_overflow(a,b,p) +static unsigned physical_maps; +static unsigned find_first_zero_bit(unsigned long *map,unsigned count) +{for(unsigned i=0;isgl);} +static void dma_free_coherent(struct device *dev,size_t size,void *buf,u64 dva) +{assert(dev && size==SZ_16K && buf && dva==0x8000);frees++;} +static void dma_unmap_resource(struct device *dev,u64 dva,size_t size,int dir,int flags) +{assert(dev && dva==0x8000 && size==4096 && !dir && !flags);resource_unmaps++;} +static void clear_bit(unsigned b,unsigned long *p) {*p &= ~(1UL<sgl && flags==3);maps++;return SZ_16K;} +static size_t iommu_unmap(void *domain,u64 dva,size_t size) +{assert(domain && dva==0x8000 && size==SZ_16K);unmaps++;return size;} +''' + # dev logging in the DMA helper has no local 'dev', unlike the dispatcher. + unmap = block(template, 'static bool dcpep_unmap_piodma(') + unmap = unmap.replace('dev_warn(', 'dev_warn_dma(').replace('dev_err(', 'dev_warn_dma(') + handlers = block(desktop, 'static const iomfb_cb_handler cb_handlers[') + binding = '\n'.join(line for line in handlers.splitlines() if '[202]' in line or '[454]' in line) + mapping = block(template, 'static bool dcpep_map_piodma(').replace('dev_err(', 'dev_warn_dma(') + release = block(template, 'static u8 dcpep_cb_release_mem_desc(').replace('dev_warn(', 'dev_warn_release(').replace('dev_err(', 'dev_err_release(') + physical = block(template, 'static bool is_disp_register(') + '\n' + block(template, 'static struct dcp_map_physical_resp\ndcpep_cb_map_physical(').replace('dev_err(', 'dev_err_release(').replace('dev_warn(', 'dev_warn_release(') + physical += r""" +static bool trampoline_map_physical(struct apple_dcp *dcp,int tag,void *out,void *in) +{struct dcp_map_physical_resp r=dcpep_cb_map_physical(dcp,in);(void)tag;memcpy(out,&r,sizeof(r));return true;} +""" + # Assert the table still binds the shared production mapper. + assert '[452] = trampoline_map_physical,' in handlers + binding += '\n[452] = trampoline_map_physical,' + wire = block(desktop, 'struct dcp_unmap_buf_req_v26_6 {')+';\n' + production = (wire + '\n' + physical + '\n' + release + '\n' + block(desktop, 'static bool trampoline_release_mem_desc_v26_6(') + '\n' + mapping + '\n' + unmap + '\n' + block(desktop, 'static bool trampoline_unmap_piodma_v26_6(') + + '\n' + block(iomfb, 'static bool dcp_external_zero_reply(') + + '\n' + block(iomfb, 'static void dcpep_handle_cb(').replace('struct device *dev =', 'struct device *dev __attribute__((unused)) =') + + '\n' + block(iomfb, 'static void dcpep_handle_ack(') + + '\n' + block(template, 'static void complete_set_digital_out_mode(')) + return prefix + production + '\n' + r''' +int main(void) +{ + struct device_node node={0};struct device dev={&node}; + bool (*handlers[IOMFB_MAX_CB])(struct apple_dcp *,int,void *,void *)={ +''' + binding + r''' + }; + struct apple_dcp d={.dev=&dev,.fw_compat=DCP_FIRMWARE_V_26_6_DESKTOP, + .cb_handlers=handlers,.memdesc_map={2},.iommu_dom=&d}; + union {u64 align;u8 data[sizeof(struct dcp_packet_header)+24];} packet; + struct dcp_packet_header *h=(void *)packet.data; + struct {struct dcp_packet_header h;u32 input[2];u32 status;} command={ + .h={.in_len=8,.out_len=4},.status=0}; + struct dcp_wait_cookie wait={.status=~0U,.refcount=2}; + /* The firmware pauses an A413 modeset until its D202 is acknowledged. */ + __typeof__(*d.desktop) desktop={0};d.desktop=&desktop; + d.command.depth=1;d.command.header[0]=command.h; + d.command.callbacks[0]=complete_set_digital_out_mode; + d.command.cookies[0]=&wait;d.command.output[0]=&command.status; + d.memdesc[1]=(struct dcp_mem_descriptor){.buf=&d,.dva=0x8000,.size=4096,.map={.sgl=&d}}; + struct dcp_map_buf_req map_request={.buffer=1};struct dcp_map_buf_resp map_reply; + assert(dcpep_map_piodma(&d,&map_request,&map_reply)); + assert(maps==1 && map_reply.buffer==1 && map_reply.dva==0x8000); + memset(&packet,0,sizeof(packet));h->in_len=24; + struct dcp_unmap_buf_req_v26_6 fields={.buffer=1,.id=1,.dva=0x8000};memcpy(packet.data+sizeof(*h),&fields,24); + case_tag=202;acks=0; + dcpep_handle_cb(&d,DCP_CONTEXT_CB,h,sizeof(*h)+24,0); + assert(acks==1 && !d.crashed && !d.channel.depth && unmaps==1); + assert(!d.memdesc[1].piodma_mapped && d.command.depth==1 && !wait.done); + /* No firmware response can arrive until the empty callback ACK above. */ + dcpep_handle_ack(&d,DCP_CONTEXT_CMD); + assert(wait.done && wait.replied && !wait.status && wait.refcount==1 && !d.command.depth); + /* A later mode can reuse a descriptor after the firmware unmaps it. */ + assert(dcpep_map_piodma(&d,&map_request,&map_reply) && maps==2); + /* Descriptor release handles both RAM and mapped resources, once. */ + fields.buffer=1;fields.dva=0x8000;memcpy(packet.data+sizeof(*h),&fields,24); + dcpep_handle_cb(&d,DCP_CONTEXT_CB,h,sizeof(*h)+24,0); + assert(unmaps==2); + case_tag=454;h->in_len=4;h->out_len=4; + put_unaligned_le32(1,packet.data+sizeof(*h)); + memset(packet.data+sizeof(*h)+4,0xa5,4); + dcpep_handle_cb(&d,DCP_CONTEXT_CB,h,sizeof(*h)+8,0); + assert(get_unaligned_le32(packet.data+sizeof(*h)+4)==1); + assert(frees==1 && !d.memdesc_map[0] && !d.memdesc[1].buf && !d.memdesc[1].size); + dcpep_handle_cb(&d,DCP_CONTEXT_CB,h,sizeof(*h)+8,0); + assert(!get_unaligned_le32(packet.data+sizeof(*h)+4) && frees==1); + d.memdesc_map[0]=1; + struct resource reg={.start=0x123400,.end=0x1243ff};d.disp_registers[0]=®d.nr_disp_registers=1; + struct dcp_map_physical_req request={.paddr=0x123400,.size=4096}; + struct {struct dcp_packet_header h;struct dcp_map_physical_req in;struct dcp_map_physical_resp out;} __attribute__((packed)) phys={.h={.in_len=24,.out_len=20},.in=request}; + case_tag=452; + dcpep_handle_cb(&d,DCP_CONTEXT_CB,&phys,sizeof(phys),0); + assert(physical_maps==1 && phys.out.mem_desc_id==1 && phys.out.dva==0x8000 && phys.out.dva_size==4096); + assert(d.memdesc_map[0]==3 && d.memdesc[1].reg==0x123400); + case_tag=454;put_unaligned_le32(1,packet.data+sizeof(*h)); + dcpep_handle_cb(&d,DCP_CONTEXT_CB,h,sizeof(*h)+8,0); + assert(get_unaligned_le32(packet.data+sizeof(*h)+4)==1 && resource_unmaps==1 && d.memdesc_map[0]==1); + for(u32 id=4;id<8;id++) { + put_unaligned_le32(id,packet.data+sizeof(*h)); + dcpep_handle_cb(&d,DCP_CONTEXT_CB,h,sizeof(*h)+8,0); + assert(!get_unaligned_le32(packet.data+sizeof(*h)+4) && frees==1); + } + /* Out-of-range and overflowing physical regions receive no mapping. */ + case_tag=452;phys.in.paddr=0x124000; + dcpep_handle_cb(&d,DCP_CONTEXT_CB,&phys,sizeof(phys),0); + assert(!phys.out.dva_size && physical_maps==1); + phys.in.paddr=~0ULL;phys.in.size=8192; + dcpep_handle_cb(&d,DCP_CONTEXT_CB,&phys,sizeof(phys),0); + assert(!phys.out.dva_size && physical_maps==1); + unsigned before=acks; + assert(!iomfb_check_callback_v26_6_desktop(454,4,1)); + assert(!iomfb_check_callback_v26_6_desktop(452,23,20)); + /* The declared desktop shape is exact, with no guessed reply bytes. */ + assert(!iomfb_check_callback_v26_6_desktop(202,26,0)); + assert(!iomfb_check_callback_v26_6_desktop(202,24,4)); + case_tag=202;h->in_len=23;h->out_len=0; + dcpep_handle_cb(&d,DCP_CONTEXT_CB,h,sizeof(*h)+24,0); + assert(d.crashed && acks==before && unmaps==2); + return 0; +} +''' def brightness_takeover(root): @@ -126,3 +315,45 @@ def brightness_takeover(root): return 0; } ''' + + +def modeset_unknown(root): + template = (root / 'drivers/gpu/drm/apple/iomfb_template.c').read_text() + unit = modeset_unmap(root) + helper = block(template, 'static bool dcp_modeset_callback_failed(') + # Keep the real modeset completion and its callback-error check. The + # adaptive retry boundary must never be reached after an unknown callback. + helper += r''' +struct dcp_modeset_cookie {struct dcp_wait_cookie wait;bool vrr;}; +static void dcp_modeset_fixed_fallback(struct apple_dcp *d,struct dcp_modeset_cookie *m) +{(void)d;(void)m;assert(false);} +''' + block(template, 'static void dcp_modeset_actual_done(') + unit = unit.replace('int main(void)', helper + '\nint main(void)') + tests = r''' + /* A fresh channel has an A413 outstanding; inject a valid but unknown D. */ + d.crashed=false;desktop.modeset_active=true;desktop.modeset_callback_error=false; + struct dcp_modeset_cookie mode={.wait={.status=~0U,.refcount=2},.vrr=true}; + d.command.depth=1;d.command.header[0]=command.h; + d.command.callbacks[0]=dcp_modeset_actual_done; + d.command.cookies[0]=&mode;d.command.output[0]=&command.status; + case_tag=499;h->in_len=4;h->out_len=4; + memset(packet.data+sizeof(*h)+4,0xa5,4);before=acks; + dcpep_handle_cb(&d,DCP_CONTEXT_CB,h,sizeof(*h)+8,0); + assert(acks==before+1 && !d.crashed && !d.channel.depth); + assert(!get_unaligned_le32(packet.data+sizeof(*h)+4)); + assert(desktop.modeset_callback_error && d.command.depth==1 && !mode.wait.done); + /* Draining the real ACK fails the set, even if firmware reports success. */ + dcpep_handle_ack(&d,DCP_CONTEXT_CMD); + assert(mode.wait.done && !mode.wait.replied && mode.wait.status==~0U); + assert(mode.wait.refcount==1 && !d.command.depth); + /* The next fixed modeset is independent and completes normally. */ + desktop.modeset_active=false;desktop.modeset_callback_error=false; + mode=(struct dcp_modeset_cookie){.wait={.status=~0U,.refcount=2}}; + d.command.depth=1;d.command.header[0]=command.h; + d.command.callbacks[0]=dcp_modeset_actual_done; + d.command.cookies[0]=&mode;d.command.output[0]=&command.status; + dcpep_handle_ack(&d,DCP_CONTEXT_CMD); + assert(mode.wait.done && mode.wait.replied && !mode.wait.status); + assert(!d.crashed && !d.command.depth && !d.channel.depth); +''' + return unit.replace(' return 0;\n}', tests + ' return 0;\n}') diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/display.py b/tools/testing/selftests/drivers/gpu/apple-dcp/display.py index e5793a84e0bb0..e64e4f9a16473 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/display.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/display.py @@ -197,6 +197,7 @@ def refresh(root): def adaptive(root): source=(root/'drivers/gpu/drm/apple/iomfb_template.c').read_text() funcs='\n'.join(block(source, marker) for marker in ( + 'static bool dcp_modeset_callback_failed(', 'static void dcp_modeset_fixed_fallback(', 'static void dcp_modeset_actual_done(', 'static void dcp_modeset_after_actual_info(', @@ -224,7 +225,7 @@ def adaptive(root): struct dcp_wait_cookie { u32 status; bool done,replied; }; struct dcp_set_digital_out_mode_req {u32 color_mode_id,timing_mode_id;}; struct dcp_set_parameter_dcp { u32 param; u64 value[4]; u32 count; }; -struct apple_dcp {void *dev;int fw_compat;bool main_display,panel;struct {bool vrr_failed;} *desktop;}; +struct apple_dcp {void *dev;int fw_compat;bool main_display,panel;struct {bool vrr_failed,modeset_callback_error;} *desktop;}; static bool t6041_vrr; struct drm_display_mode {int id;}; struct drm_crtc_state {bool vrr_enabled;struct drm_display_mode mode;}; @@ -298,6 +299,18 @@ def adaptive(root): dcp_modeset_sync(&d,&m);reply(&d,1,&zero); assert(requested.color_mode_id==13 && requested.timing_mode_id==12); reply(&d,2,&zero);reply(&d,3,&zero);selected(&d,&m); + /* Unsupported callbacks at every chained RPC stop after its real ACK. */ + for(unsigned phase=0;phase<5;phase++) { + desktop.vrr_failed=false;desktop.modeset_callback_error=false; + initialize(&m);m.use_bootstrap=true;dcp_modeset_sync(&d,&m); + if(phase>0)reply(&d,1,&zero); + if(phase>1)reply(&d,2,&zero); + if(phase>2)reply(&d,3,&zero); + if(phase>3)reply(&d,2,&zero); + desktop.modeset_callback_error=true; + reply(&d,kind,&zero); + assert(m.wait.done && !m.wait.replied && !pending && !desktop.vrr_failed); + } assert(parameters && infos && modes); return 0; } diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py b/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py index 582156af8ca4d..cc82ade333fb3 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py @@ -1840,7 +1840,10 @@ def dispatcher(root): typedef uint16_t u16; typedef uint32_t u32; typedef uint64_t u64; +#define READ_ONCE(x) (x) +#define WRITE_ONCE(x,v) ((x)=(v)) #define DCP_FIRMWARE_H17P 1 +#define DCP_FIRMWARE_V_26_6_DESKTOP 5 #define DCP_IOMFB_METHODS_H17P_EXTERNAL 2 #define DCP_IOMFB_METHODS_H17G 3 #define DCP_MAX_CALL_DEPTH 8 @@ -1863,6 +1866,7 @@ def dispatcher(root): bool (**cb_handlers)(struct apple_dcp *, int, void *, void *); unsigned long unknown_callbacks[16]; int start_done; + struct {bool modeset_active,modeset_callback_error;} *desktop; struct dcp_channel channel; }; static bool machine; @@ -1922,4 +1926,6 @@ def dispatcher(root): } """ - return prefix + source[start:end] + suffix + desktop = (root / "drivers/gpu/drm/apple/iomfb_v26_6_desktop.c").read_text() + checker = block(desktop, 'bool iomfb_check_callback_v26_6_desktop(') + return prefix + checker + source[start:end] + suffix diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/run.py b/tools/testing/selftests/drivers/gpu/apple-dcp/run.py index 9f08c6ec53499..1adcfe62035f5 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/run.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/run.py @@ -26,11 +26,11 @@ import transaction import accounting import idle +import desktop import rate import boot import m4 import display -import desktop def parking_unit(root): @@ -53,6 +53,8 @@ def main(): 'orientation-faults', 'backend-change', 'safe-first', 'failed-flip-return', 'live-park')) tests = [ + ('lifecycle', 'desktop-modeset-unmap', desktop.modeset_unmap, ((),), ()), + ('lifecycle', 'desktop-unknown-modeset-recovery', desktop.modeset_unknown, ((),), ()), ('display', 'panel-backlight-ramp', display.backlight, ((),), ()), ('display', 'panel-takeover-registration', desktop.brightness_takeover, ((),), ()), ('display', 'panel-brightness-commit', display.brightness_commit, ((),), ()), From 0618cae05907a6ad923b442cba5254960723ec42 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 09:04:16 +1100 Subject: [PATCH 022/184] drm/apple: publish the desktop panel identity as a valid EDID The internal desktop panel has no external EDID service. Its product identity was also excluded from the firmware-derived EDID path, leaving userspace without monitor identity strings. Parse the panel ProductAttributes independently of optional color fields and publish an identity-only EDID base block. Keep DCP TimingElements as the authoritative mode list, including timings which EDID cannot encode. Use the panel dimensions already supplied by the device tree, and retain the firmware vendor, product and serial without embedding device data. Protect replacement and allocation with an identity generation check. Cover the real parser, EDID checksum and identity bytes, metadata replacement and other-profile exclusion in host tests, plus KUnit. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/gpu/drm/apple/dcp-display-metadata.c | 58 +++++++++++++++ drivers/gpu/drm/apple/dcp-display-metadata.h | 3 + drivers/gpu/drm/apple/dcp-internal.h | 8 ++ drivers/gpu/drm/apple/dcp.c | 54 ++++++++++++++ drivers/gpu/drm/apple/iomfb.c | 8 +- drivers/gpu/drm/apple/iomfb_template.c | 6 ++ drivers/gpu/drm/apple/parser-test.c | 35 +++++++++ drivers/gpu/drm/apple/parser.c | 74 +++++++++++++++++-- drivers/gpu/drm/apple/parser.h | 1 + .../drivers/gpu/apple-dcp/desktop.py | 64 ++++++++++++++++ .../selftests/drivers/gpu/apple-dcp/rate.py | 4 + .../selftests/drivers/gpu/apple-dcp/run.py | 1 + 12 files changed, 308 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp-display-metadata.c b/drivers/gpu/drm/apple/dcp-display-metadata.c index cc4c69736d098..5bc60abd95e4b 100644 --- a/drivers/gpu/drm/apple/dcp-display-metadata.c +++ b/drivers/gpu/drm/apple/dcp-display-metadata.c @@ -202,3 +202,61 @@ const struct drm_edid *dcp_metadata_edid(const struct dcp_display_metadata *meta } return edid; } + +/* + * Internal modes come from TimingElements. Supply identity without inventing + * an EDID timing, chromaticities or gamma when the panel omits those fields. + */ +const struct drm_edid *dcp_panel_edid(const struct dcp_display_metadata *metadata, + u32 width_mm, u32 height_mm) +{ + struct edid block = {}; + const struct drm_edid *edid; + char serial[14]; + u16 manufacturer = 0; + u8 sum = 0; + int ret; + + if (strnlen(metadata->manufacturer, 4) != 3 || metadata->product > U16_MAX || + !width_mm || width_mm > 2550 || !height_mm || height_mm > 2550) + return ERR_PTR(-EINVAL); + for (unsigned int i = 0; i < 3; i++) { + if (metadata->manufacturer[i] < 'A' || metadata->manufacturer[i] > 'Z') + return ERR_PTR(-EINVAL); + manufacturer = manufacturer << 5 | (metadata->manufacturer[i] - '@'); + } + memcpy(block.header, "\x00\xff\xff\xff\xff\xff\xff\x00", 8); + block.product_id.manufacturer_name = cpu_to_be16(manufacturer); + block.product_id.product_code = cpu_to_le16(metadata->product); + block.product_id.serial_number = cpu_to_le32(metadata->serial); + block.version = 1; + block.revision = 3; + block.input = DRM_EDID_INPUT_DIGITAL; + block.width_cm = DIV_ROUND_CLOSEST(width_mm, 10); + block.height_cm = DIV_ROUND_CLOSEST(height_mm, 10); + block.gamma = 0xff; /* Unspecified. */ + memset(block.standard_timings, 1, sizeof(block.standard_timings)); + for (unsigned int i = 0; i < ARRAY_SIZE(block.detailed_timings); i++) + block.detailed_timings[i].data.other_data.type = 0x10 /* Dummy descriptor. */; + ret = dcp_metadata_text(&block.detailed_timings[0], EDID_DETAIL_MONITOR_NAME, + metadata->name); + if (ret) + return ERR_PTR(ret); + /* A zero numeric serial is a valid unspecified EDID serial, not NULL. */ + snprintf(serial, sizeof(serial), "%08x", metadata->serial); + ret = dcp_metadata_text(&block.detailed_timings[1], EDID_DETAIL_MONITOR_SERIAL, + metadata->serial_text[0] ? metadata->serial_text : serial); + if (ret) + return ERR_PTR(ret); + for (unsigned int i = 0; i < sizeof(block) - 1; i++) + sum += ((const u8 *)&block)[i]; + block.checksum = -sum; + edid = drm_edid_alloc(&block, sizeof(block)); + if (!edid) + return ERR_PTR(-ENOMEM); + if (!drm_edid_valid(edid)) { + drm_edid_free(edid); + return ERR_PTR(-EINVAL); + } + return edid; +} diff --git a/drivers/gpu/drm/apple/dcp-display-metadata.h b/drivers/gpu/drm/apple/dcp-display-metadata.h index 537994420ebd6..b7ec1505d5728 100644 --- a/drivers/gpu/drm/apple/dcp-display-metadata.h +++ b/drivers/gpu/drm/apple/dcp-display-metadata.h @@ -21,4 +21,7 @@ struct drm_display_mode; const struct drm_edid *dcp_metadata_edid(const struct dcp_display_metadata *metadata, const struct drm_display_mode *mode); +const struct drm_edid *dcp_panel_edid(const struct dcp_display_metadata *metadata, + u32 width_mm, u32 height_mm); + #endif diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 9aa05a5b3b257..ef8cc5b093573 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -243,6 +243,10 @@ struct dcp_desktop_state { * element before the real one is selected. */ bool mode_live; + /* Identity supplied by this fixed panel, independent of optional color data. */ + struct dcp_display_metadata panel_metadata; + u64 panel_metadata_generation; + bool panel_metadata_valid; /* RX records an unsupported callback until the modeset RPC has drained. */ bool modeset_active; bool modeset_callback_error; @@ -301,6 +305,10 @@ struct dcpext_scanout; struct dcp_hdmi; bool dcp_desktop_present_complete(struct apple_dcp *dcp, u32 id); +void dcp_panel_metadata_replace(struct apple_dcp *dcp, + const struct dcp_display_metadata *metadata); +const struct drm_edid *dcp_panel_edid_snapshot(struct apple_dcp *dcp, u64 *generation); +bool dcp_panel_metadata_current(struct apple_dcp *dcp, u64 generation); bool dcp_desktop_vrr_supported(struct apple_dcp *dcp); int dcp_desktop_vrr_check(struct apple_dcp *dcp, struct drm_crtc_state *state); diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 67f8aba5a5acd..dbfb03fa2a040 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -776,6 +776,60 @@ const struct drm_edid *dcp_firmware_edid(struct apple_dcp *dcp, u64 *generation) return edid; } +/* Internal panel identity has no detachable Type-C service or EDID transport. */ +void dcp_panel_metadata_replace(struct apple_dcp *dcp, + const struct dcp_display_metadata *metadata) +{ + unsigned long flags; + + if (dcp->fw_compat != DCP_FIRMWARE_V_26_6_DESKTOP || !dcp_has_panel(dcp)) + return; + spin_lock_irqsave(&dcp->metadata.lock, flags); + dcp->desktop->panel_metadata_generation++; + dcp->desktop->panel_metadata_valid = !!metadata; + if (metadata) + dcp->desktop->panel_metadata = *metadata; + spin_unlock_irqrestore(&dcp->metadata.lock, flags); + if (dcp->connector) + dcp_queue_hotplug(dcp->connector); +} + +bool dcp_panel_metadata_current(struct apple_dcp *dcp, u64 generation) +{ + unsigned long flags; + bool valid; + + spin_lock_irqsave(&dcp->metadata.lock, flags); + valid = dcp->desktop->panel_metadata_valid && + generation == dcp->desktop->panel_metadata_generation; + spin_unlock_irqrestore(&dcp->metadata.lock, flags); + return valid; +} + +const struct drm_edid *dcp_panel_edid_snapshot(struct apple_dcp *dcp, u64 *generation) +{ + struct dcp_display_metadata metadata; + const struct drm_edid *edid; + unsigned long flags; + bool valid; + + spin_lock_irqsave(&dcp->metadata.lock, flags); + valid = dcp->desktop->panel_metadata_valid; + if (valid) { + metadata = dcp->desktop->panel_metadata; + *generation = dcp->desktop->panel_metadata_generation; + } + spin_unlock_irqrestore(&dcp->metadata.lock, flags); + if (!valid) + return ERR_PTR(-ENODATA); + edid = dcp_panel_edid(&metadata, dcp->panel.width_mm, dcp->panel.height_mm); + if (!IS_ERR(edid) && !dcp_panel_metadata_current(dcp, *generation)) { + drm_edid_free(edid); + return ERR_PTR(-ESTALE); + } + return edid; +} + void dcp_set_dimensions(struct apple_dcp *dcp, u64 generation) { struct apple_connector *apple_connector = READ_ONCE(dcp->connector); diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 6cc67676fb9db..2a48c8b8df962 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -1944,8 +1944,11 @@ int dcp_get_modes(struct drm_connector *connector) } } else { u64 generation = 0; + bool panel = dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && + dcp_has_panel(dcp); const struct drm_edid *derived = nr_modes ? - dcp_firmware_edid(dcp, &generation) : ERR_PTR(-ENODATA); + (panel ? dcp_panel_edid_snapshot(dcp, &generation) : + dcp_firmware_edid(dcp, &generation)) : ERR_PTR(-ENODATA); if (!IS_ERR(derived)) { bool log = !apple_connector->drm_edid_firmware_derived || @@ -1956,7 +1959,8 @@ int dcp_get_modes(struct drm_connector *connector) apple_connector->drm_edid_generation = generation; update = drm_edid_connector_update(connector, derived); drm_edid_free(derived); - if (update || !dcp_firmware_metadata_current(dcp, generation)) { + if (update || !(panel ? dcp_panel_metadata_current(dcp, generation) : + dcp_firmware_metadata_current(dcp, generation))) { drm_edid_connector_update(connector, NULL); apple_connector->drm_edid_firmware_derived = false; } else if (log) { diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index abb1eb8bd6dac..0a735158ceda8 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -1320,6 +1320,12 @@ static bool dcpep_process_chunks(struct apple_dcp *dcp, ret = parse_display_metadata(&metadata_ctx, &metadata); dcp_firmware_metadata_attributes(dcp, ret ? NULL : &metadata); + } else if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && + dcp_has_panel(dcp)) { + struct dcp_display_metadata metadata; + + ret = parse_panel_metadata(&metadata_ctx, &metadata); + dcp_panel_metadata_replace(dcp, ret ? NULL : &metadata); } } diff --git a/drivers/gpu/drm/apple/parser-test.c b/drivers/gpu/drm/apple/parser-test.c index 05648a14cf8e6..9a7426d1f7016 100644 --- a/drivers/gpu/drm/apple/parser-test.c +++ b/drivers/gpu/drm/apple/parser-test.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include "dcp.h" @@ -1308,7 +1309,41 @@ static void parser_route_prepare_scope(struct kunit *test) mutex_destroy(&dcp->modes_lock); } +static void parser_panel_identity(struct kunit *test) +{ + struct parser_blob *blob = kunit_kzalloc(test, sizeof(*blob), GFP_KERNEL); + struct dcp_display_metadata metadata; + struct dcp_parse_ctx ctx; + const struct drm_edid *edid; + const struct edid *raw; + + KUNIT_ASSERT_NOT_NULL(test, blob); + put_unaligned_le32(0xd3, blob->data); + blob->size = 4; + blob_tag(blob, 1, 1); + blob_key(blob, "ProductAttributes"); + blob_tag(blob, 1, 4); + blob_key(blob, "ManufacturerID"); + blob_key(blob, "TST"); + blob_key(blob, "ProductName"); + blob_key(blob, "Synthetic Internal Panel"); + blob_int(blob, "ProductID", 42); + blob_int(blob, "SerialNumber", 0x12345678); + KUNIT_ASSERT_EQ(test, parse(blob->data, blob->size, &ctx), 0); + KUNIT_ASSERT_EQ(test, parse_panel_metadata(&ctx, &metadata), 0); + KUNIT_EXPECT_STREQ(test, metadata.name, "Synthetic Int"); + edid = dcp_panel_edid(&metadata, 345, 223); + KUNIT_ASSERT_FALSE(test, IS_ERR(edid)); + KUNIT_EXPECT_TRUE(test, drm_edid_valid(edid)); + raw = drm_edid_raw(edid); + KUNIT_EXPECT_EQ(test, le16_to_cpu(raw->product_id.product_code), 42); + KUNIT_EXPECT_EQ(test, le32_to_cpu(raw->product_id.serial_number), 0x12345678U); + KUNIT_EXPECT_EQ(test, raw->gamma, 0xff); + drm_edid_free(edid); +} + static struct kunit_case parser_cases[] = { + KUNIT_CASE(parser_panel_identity), KUNIT_CASE(parser_score_presence), KUNIT_CASE(parser_legacy_ties), KUNIT_CASE(parser_color_presence), diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index 25a2562f34807..1de2fa263a865 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -1319,7 +1319,33 @@ static int parse_metadata_chromaticity(struct dcp_parse_ctx *handle, return seen == 7 ? 0 : -EINVAL; } -static int parse_metadata_product(struct dcp_parse_ctx *handle, struct dcp_display_metadata *out) +/* Internal panel labels can be longer than an EDID descriptor. */ +static int parse_panel_label(struct dcp_parse_ctx *handle, char out[14]) +{ + const struct dcp_parse_tag *tag = parse_tag_of_type(handle, DCP_TYPE_STRING); + const u8 *value; + unsigned int length; + + if (IS_ERR(tag)) + return PTR_ERR(tag); + if (!tag->size || tag->size > 255) + return -EINVAL; + value = parse_bytes(handle, tag->size); + if (IS_ERR(value)) + return PTR_ERR(value); + for (unsigned int i = 0; i < tag->size; i++) + if (value[i] < 0x20 || value[i] > 0x7e) + return -EINVAL; + length = tag->size; + if (length > 13) + length = 13; + memcpy(out, value, length); + out[length] = 0; + return 0; +} + +static int parse_metadata_product(struct dcp_parse_ctx *handle, + struct dcp_display_metadata *out, bool panel) { struct iterator it; unsigned int seen = 0; @@ -1344,7 +1370,8 @@ static int parse_metadata_product(struct dcp_parse_ctx *handle, struct dcp_displ if (seen & 2) return -EINVAL; seen |= 2; - ret = parse_metadata_string(handle, out->name, sizeof(out->name)); + ret = panel ? parse_panel_label(handle, out->name) : + parse_metadata_string(handle, out->name, sizeof(out->name)); } else if (!strcmp(key, "ProductID")) { if (seen & 4) return -EINVAL; @@ -1369,8 +1396,9 @@ static int parse_metadata_product(struct dcp_parse_ctx *handle, struct dcp_displ if (seen & 128) return -EINVAL; seen |= 128; - ret = parse_metadata_string(handle, out->serial_text, - sizeof(out->serial_text)); + ret = panel ? parse_panel_label(handle, out->serial_text) : + parse_metadata_string(handle, out->serial_text, + sizeof(out->serial_text)); } else if (!strcmp(key, "LegacyManufacturerID")) { if (seen & 32) return -EINVAL; @@ -1382,7 +1410,8 @@ static int parse_metadata_product(struct dcp_parse_ctx *handle, struct dcp_displ if (ret) return ret; } - if ((seen & 31) != 31 || strlen(out->manufacturer) != 3) + if ((seen & (panel ? 7 : 31)) != (panel ? 7 : 31) || + strlen(out->manufacturer) != 3) return -EINVAL; for (unsigned int i = 0; i < 3; i++) if (out->manufacturer[i] < 'A' || out->manufacturer[i] > 'Z') @@ -1433,7 +1462,7 @@ int parse_display_metadata(struct dcp_parse_ctx *handle, struct dcp_display_meta if (seen & 16) return -EINVAL; seen |= 16; - ret = parse_metadata_product(handle, &parsed); + ret = parse_metadata_product(handle, &parsed, false); } else { ret = skip(handle); } @@ -1446,6 +1475,39 @@ int parse_display_metadata(struct dcp_parse_ctx *handle, struct dcp_display_meta return 0; } +/* Identity does not depend on optional colorimetry or EDID-compatible timing. */ +int parse_panel_metadata(struct dcp_parse_ctx *handle, struct dcp_display_metadata *out) +{ + struct dcp_display_metadata parsed = {}; + struct iterator it; + bool product = false; + int ret = iterator_begin(handle, &it, true); + + if (ret) + return ret; + for (; it.idx < it.len; it.idx++) { + char key[64]; + + ret = parse_metadata_string(handle, key, sizeof(key)); + if (ret) + return ret; + if (!strcmp(key, "ProductAttributes")) { + if (product) + return -EINVAL; + product = true; + ret = parse_metadata_product(handle, &parsed, true); + } else { + ret = skip(handle); + } + if (ret) + return ret; + } + if (!product) + return -ENODATA; + *out = parsed; + return 0; +} + int parse_epic_service_init(struct dcp_parse_ctx *handle, const char **name, const char **class, s64 *unit, bool h17p_keys) { diff --git a/drivers/gpu/drm/apple/parser.h b/drivers/gpu/drm/apple/parser.h index 135124680ec6a..c88f4dd21b855 100644 --- a/drivers/gpu/drm/apple/parser.h +++ b/drivers/gpu/drm/apple/parser.h @@ -196,6 +196,7 @@ int replace_modes(struct dcp_parse_ctx *handle, struct dcp_display_mode **modes, unsigned int *count, int width_mm, int height_mm, unsigned int notch_height, bool internal); +int parse_panel_metadata(struct dcp_parse_ctx *handle, struct dcp_display_metadata *out); int parse_display_attributes(struct dcp_parse_ctx *handle, int *width_mm, int *height_mm, bool *backlight_control); int parse_epic_service_init(struct dcp_parse_ctx *handle, const char **name, diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py index 1ccc513a96f35..28507cbba031d 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py @@ -357,3 +357,67 @@ def modeset_unknown(root): assert(!d.crashed && !d.command.depth && !d.channel.depth); ''' return unit.replace(' return 0;\n}', tests + ' return 0;\n}') + + +def panel_identity(root): + import struct + import rate + f = rate.parser_fixture(root) + attributes = {'MaxHorizontalImageSize': 0, 'MaxVerticalImageSize': 0, + 'ProductAttributes': {'ManufacturerID': 'TST', 'ProductID': 42, + 'ProductName': 'Synthetic Internal Panel', + 'SerialNumber': 0x12345678}} + raw = bytearray(struct.pack('has_panel;} +static void dcp_queue_hotplug(void *con) {assert(con);notifications++;} +''' + helpers + '\nstatic const u8 attributes[]={'+','.join(map(str,raw))+'};\n' + r''' +int main(void) +{ + struct dcp_parse_ctx ctx; + struct dcp_display_metadata metadata; + assert(!parse(attributes,sizeof(attributes),&ctx)); + assert(!parse_panel_metadata(&ctx,&metadata)); + assert(!strcmp(metadata.manufacturer,"TST") && metadata.product==42 && metadata.serial==0x12345678); + assert(!strcmp(metadata.name,"Synthetic Int")); + /* No color fields, date or EDID-representable timing is required. */ + struct apple_dcp d={.fw_compat=5,.has_panel=true,.panel={345,223},.connector=&d}; + __typeof__(*d.desktop) desktop={0};d.desktop=&desktop; + u64 generation=99; + assert(IS_ERR(dcp_panel_edid_snapshot(&d,&generation))); + dcp_panel_metadata_replace(&d,&metadata); + assert(notifications==1 && desktop.panel_metadata_valid); + const struct drm_edid *edid=dcp_panel_edid_snapshot(&d,&generation); + assert(!IS_ERR(edid) && drm_edid_valid(edid) && dcp_panel_metadata_current(&d,generation)); + const u8 *bytes=(const void *)&edid->raw; + assert(bytes[8]==0x52 && bytes[9]==0x74 && bytes[10]==42 && !bytes[11]); + assert(bytes[12]==0x78 && bytes[13]==0x56 && bytes[14]==0x34 && bytes[15]==0x12); + assert(bytes[18]==1 && bytes[19]==3 && bytes[21]==35 && bytes[22]==22 && bytes[23]==0xff); + assert(bytes[54+3]==EDID_DETAIL_MONITOR_NAME && bytes[72+3]==EDID_DETAIL_MONITOR_SERIAL); + assert(!memcmp(bytes+72+5,"12345678\n",9)); + drm_edid_free(edid); + dcp_panel_metadata_replace(&d,NULL); + assert(!dcp_panel_metadata_current(&d,generation)); + assert(IS_ERR(dcp_panel_edid_snapshot(&d,&generation))); + /* Another firmware/profile cannot publish a desktop panel identity. */ + d.fw_compat=2;dcp_panel_metadata_replace(&d,&metadata); + assert(!desktop.panel_metadata_valid); + metadata.manufacturer[0]='?';assert(IS_ERR(dcp_panel_edid(&metadata,345,223))); + return 0; +} +''' diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py b/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py index 9176515c38d51..db5930b71215b 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py @@ -638,6 +638,10 @@ def publication(root): } static const struct drm_edid *dcp_firmware_edid(struct apple_dcp *d,u64 *generation) { (void)d; (void)generation; return ERR_PTR(-ENODATA); } static bool dcp_firmware_metadata_current(struct apple_dcp *d,u64 generation) { (void)d; (void)generation; return false; } +static const struct drm_edid *dcp_panel_edid_snapshot(struct apple_dcp *d,u64 *generation) { (void)d;(void)generation;assert(!"panel identity in external fixture");return ERR_PTR(-ENODATA); } +static bool dcp_panel_metadata_current(struct apple_dcp *d,u64 generation) { (void)d;(void)generation;assert(!"panel identity in external fixture");return false; } +static void dcp_panel_metadata_replace(struct apple_dcp *d,const struct dcp_display_metadata *m) { (void)d;(void)m;assert(!"panel identity in external receipt"); } + static struct drm_display_mode *drm_mode_duplicate(struct drm_device *dev,const struct drm_display_mode *m) { assert(dev->mode_config.mutex);struct drm_display_mode *copy=malloc(sizeof(*copy));assert(copy);*copy=*m; if(invalidate_during_duplicate) { invalidate_during_duplicate=false;dcp_180hz_invalidate(probing); } diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/run.py b/tools/testing/selftests/drivers/gpu/apple-dcp/run.py index 1adcfe62035f5..73eef60687984 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/run.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/run.py @@ -60,6 +60,7 @@ def main(): ('display', 'panel-brightness-commit', display.brightness_commit, ((),), ()), ('display', 'panel-brightness-takeover', display.takeover, ((),), ()), ('display', 'panel-mode-catalog', display.modes, ((),), ()), + ('display', 'desktop-panel-identity', desktop.panel_identity, ((),), ()), ('display', 'panel-refresh-range', display.refresh, ((),), ()), ('display', 'panel-adaptive-fallback', display.adaptive, ((),), ()), ('display', 'panel-adaptive-watchdog', display.watchdog, ((),), ()), From 0c76139a04d6f5a374956c156d626e773f4fa992 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 09:04:16 +1100 Subject: [PATCH 023/184] drm/apple: preserve serialized powerlog reports on compact EPIC The compact report dispatcher removed an eight-byte command descriptor from every standard-service report. A bare serialized dictionary loses its header there, so systemep rejects it before it can decode the mNits event or establish a takeover hint. Recognize the bounded serialization header and dictionary tag before removing a descriptor. Keep the existing descriptor-wrapped report path. Do not scan arbitrary offsets or manufacture a brightness sample. The host test sends both report forms through the production compact receiver, systemep handler and dictionary parser, and verifies the decoded hint. It also reproduces the old header-loss failure and rejects truncation. KUnit covers the exact header boundary. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/gpu/drm/apple/afk-parser-test.c | 15 ++++ drivers/gpu/drm/apple/afk.c | 12 ++- .../drivers/gpu/apple-dcp/desktop.py | 81 +++++++++++++++++++ .../selftests/drivers/gpu/apple-dcp/run.py | 1 + 4 files changed, 107 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/apple/afk-parser-test.c b/drivers/gpu/drm/apple/afk-parser-test.c index fd8c5cb0c26e5..8b93e67fd3d5b 100644 --- a/drivers/gpu/drm/apple/afk-parser-test.c +++ b/drivers/gpu/drm/apple/afk-parser-test.c @@ -89,7 +89,22 @@ static void afk_reply_index_test(struct kunit *test) KUNIT_EXPECT_EQ(test, pending->retcode, 7U); } +static void afk_serialized_report_test(struct kunit *test) +{ + u8 dictionary[8] = {}; + unsigned int length; + + put_unaligned_le32(0xd3, dictionary); + put_unaligned_le32(1U << 24, dictionary + 4); + for (length = 0; length < sizeof(dictionary); length++) + KUNIT_EXPECT_FALSE(test, afk_compact_report_dictionary(dictionary, length)); + KUNIT_EXPECT_TRUE(test, afk_compact_report_dictionary(dictionary, sizeof(dictionary))); + put_unaligned_le32(0, dictionary); + KUNIT_EXPECT_FALSE(test, afk_compact_report_dictionary(dictionary, sizeof(dictionary))); +} + static struct kunit_case afk_parser_cases[] = { + KUNIT_CASE(afk_serialized_report_test), KUNIT_CASE(afk_compact_header_test), KUNIT_CASE(afk_short_epic_test), KUNIT_CASE(afk_short_reply_test), diff --git a/drivers/gpu/drm/apple/afk.c b/drivers/gpu/drm/apple/afk.c index 248ca42640b40..688b2d2f5c622 100644 --- a/drivers/gpu/drm/apple/afk.c +++ b/drivers/gpu/drm/apple/afk.c @@ -1024,6 +1024,13 @@ static void afk_recv_handle_compact_call(struct apple_dcp_afkep *ep, queue_work(ep->tx_wq, &r->work); } +/* A serialized report starts at its serialization header, without a command descriptor. */ +static bool afk_compact_report_dictionary(const void *payload, size_t size) +{ + return size >= 2 * sizeof(u32) && get_unaligned_le32(payload) == 0xd3 && + ((get_unaligned_le32(payload + sizeof(u32)) >> 24) & 0x1f) == 1; +} + static void afk_recv_compact_message(struct apple_dcp_afkep *ep, u16 intf_id, const void *body, size_t size) { @@ -1046,9 +1053,10 @@ static void afk_recv_compact_message(struct apple_dcp_afkep *ep, u16 intf_id, return; } if (sub->category == 0) { - /* A standard-service report carries a descriptor first. */ + /* Preserve bare dictionaries; command-style reports have a descriptor. */ if (sub->type == EPIC_SUBTYPE_STD_SERVICE && - payload_size >= sizeof(struct epic_compact_desc)) { + payload_size >= sizeof(struct epic_compact_desc) && + !afk_compact_report_dictionary(payload, payload_size)) { payload += sizeof(struct epic_compact_desc); payload_size -= sizeof(struct epic_compact_desc); } diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py index 28507cbba031d..bde59e98e0270 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py @@ -421,3 +421,84 @@ def panel_identity(root): return 0; } ''' + + +def powerlog_framing(root): + import rate + import struct + f = rate.parser_fixture(root) + # Use only the existing integer/string parser; no captured device data. + raw = bytearray(struct.pack('service->channel==id);return e->service;} +static struct apple_epic_service *afk_service_get(struct apple_epic_service *s) {s->users++;return s;} +static void afk_service_put(struct apple_epic_service *s) {assert(s->users);s->users--;} +static void afk_recv_handle_compact_init(struct apple_dcp_afkep *e,u16 id,const void *p,size_t n) +{(void)e;(void)id;(void)p;(void)n;assert(false);} +static void afk_recv_handle_compact_call(struct apple_dcp_afkep *e,u16 id,const void *p,size_t n) +{(void)e;(void)id;(void)p;(void)n;assert(false);} +static void afk_recv_handle_compact_reply(struct apple_dcp_afkep *e,u16 id,u8 t,const void *p,size_t n) +{(void)e;(void)id;(void)t;(void)p;(void)n;assert(false);} +''' + functions = '\n'.join(block(source, name) for source, name in ( + (system, 'static int powerlog_report('), + (afk, 'static bool afk_compact_report_dictionary('), + (afk, 'static void afk_recv_compact_message('))) + return prefix + '\n' + functions + '\nstatic const u8 report[]={'+','.join(map(str,raw))+'};\n' + r''' +int main(void) +{ + struct apple_dcp d={.fw_compat=5}; + struct apple_dcp_afkep ep={.dcp=&d,.endpoint=0x20}; + struct apple_epic_service_ops ops={.report=powerlog_report}; + struct apple_epic_service service={.ep=&ep,.ops=&ops,.channel=3};ep.service=&service; + u8 wire[16+8+sizeof(report)]={0}; + struct epic_compact_sub_hdr *sub=(void *)(wire+8);sub->category=0;sub->type=0xc0; + memcpy(wire+16,report,sizeof(report)); + afk_recv_compact_message(&ep,3,wire,16+sizeof(report)); + assert(hints==1 && hint==123000 && !service.users); + /* Descriptor-wrapped reports keep their existing transport behavior. */ + memset(wire+16,0,8);memcpy(wire+24,report,sizeof(report)); + afk_recv_compact_message(&ep,3,wire,sizeof(wire)); + assert(hints==2 && hint==123000 && !service.users); + /* The old unconditional descriptor removal loses the serialization header. */ + struct dcp_parse_ctx ctx;assert(parse(report+8,sizeof(report)-8,&ctx)==-EINVAL); + /* Truncated data never creates a takeover hint. */ + memcpy(wire+16,report,8); + afk_recv_compact_message(&ep,3,wire,23); + assert(hints==2 && !service.users); + return 0; +} +''' diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/run.py b/tools/testing/selftests/drivers/gpu/apple-dcp/run.py index 73eef60687984..bad70b2fd8f6f 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/run.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/run.py @@ -61,6 +61,7 @@ def main(): ('display', 'panel-brightness-takeover', display.takeover, ((),), ()), ('display', 'panel-mode-catalog', display.modes, ((),), ()), ('display', 'desktop-panel-identity', desktop.panel_identity, ((),), ()), + ('display', 'desktop-powerlog-framing', desktop.powerlog_framing, ((),), ()), ('display', 'panel-refresh-range', display.refresh, ((),), ()), ('display', 'panel-adaptive-fallback', display.adaptive, ((),), ()), ('display', 'panel-adaptive-watchdog', display.watchdog, ((),), ()), From e29ee9a18eb04daee1254d256ecfb508d52ae6c1 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 09:07:04 +1100 Subject: [PATCH 024/184] drm/apple: keep firmware brightness reports separate from user targets Use the first bounded report only to establish an unknown takeover level. Once Linux has a level or a user target, retain firmware observations separately from the target, fade position and accepted present. A delayed or non-monotonic report must not replace a request, mark it dirty, restart a fade or generate a new write. Continue decoding reports after backlight registration so they can confirm output without driving policy. Preserve the backlight target under its existing state lock and synchronize only the initial takeover hint with the registered device. Add a host regression with rapid increasing and decreasing key repeats, interleaved old and new reports, both completion/reply orders and late reports after settling. Assert monotonic output and convergence to the last user target. Add KUnit for report ownership through a fade and retarget. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/gpu/drm/apple/dcp_backlight.c | 22 ++++++- drivers/gpu/drm/apple/dcp_backlight.h | 21 ++++++ drivers/gpu/drm/apple/dcp_backlight_test.c | 34 ++++++++++ drivers/gpu/drm/apple/systemep.c | 4 -- .../drivers/gpu/apple-dcp/desktop.py | 64 ++++++++++++++++++- .../drivers/gpu/apple-dcp/display.py | 1 + .../selftests/drivers/gpu/apple-dcp/run.py | 1 + 7 files changed, 139 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp_backlight.c b/drivers/gpu/drm/apple/dcp_backlight.c index 381bdedee65b1..7d884e062f363 100644 --- a/drivers/gpu/drm/apple/dcp_backlight.c +++ b/drivers/gpu/drm/apple/dcp_backlight.c @@ -135,14 +135,30 @@ int dcp_backlight_takeover(struct apple_dcp *dcp, u32 millinits) u32 maximum, nits; int ret; - if (dcp_backlight_active(dcp)) - return 0; /* H17P takes nits up to the panel ceiling, not the older DAC table's. */ maximum = dcp->brightness.maximum; ret = dcp_bl_takeover_nits(maximum, millinits, &nits); if (ret) return ret; + if (dcp_backlight_active(dcp) || + (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && + READ_ONCE(dcp->backlight.state.ready))) { + unsigned long flags; + bool seeded; + + spin_lock_irqsave(&dcp->backlight.lock, flags); + seeded = dcp_bl_report(&dcp->backlight.state, nits); + if (seeded && dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) { + dcp->desktop->inherited_brightness = nits; + dcp->desktop->inherited_brightness_valid = true; + } + spin_unlock_irqrestore(&dcp->backlight.lock, flags); + if (seeded) + dcp_backlight_sync_hint(dcp); + return 0; + } + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) { unsigned long flags; bool seeded = false; @@ -154,7 +170,7 @@ int dcp_backlight_takeover(struct apple_dcp *dcp, u32 millinits) dcp->desktop->inherited_brightness = nits; dcp->desktop->inherited_brightness_valid = true; if (dcp->backlight.state.ready) - seeded = dcp_bl_seed(&dcp->backlight.state, nits); + seeded = dcp_bl_report(&dcp->backlight.state, nits); } spin_unlock_irqrestore(&dcp->backlight.lock, flags); if (seeded) diff --git a/drivers/gpu/drm/apple/dcp_backlight.h b/drivers/gpu/drm/apple/dcp_backlight.h index 704e985f49a78..ad8ca6d5623fc 100644 --- a/drivers/gpu/drm/apple/dcp_backlight.h +++ b/drivers/gpu/drm/apple/dcp_backlight.h @@ -22,6 +22,8 @@ struct dcp_backlight_state { u32 fade_from; u32 actual; u32 sent_nits; + /* Firmware observations confirm output; they never become user requests. */ + u32 reported; u64 sequence; u8 retries; bool ready; @@ -43,6 +45,7 @@ struct dcp_backlight_state { * without a loader report is not known until a present carries a level. */ bool level_known; + bool report_valid; }; struct dcp_backlight_present { @@ -88,8 +91,10 @@ static inline int dcp_bl_init(struct dcp_backlight_state *state, u32 maximum, .target = nits, .level = nits, .actual = nits, + .reported = nits, .ready = true, .level_known = inherited_valid, + .report_valid = inherited_valid, }; return 0; } @@ -109,6 +114,22 @@ static inline bool dcp_bl_seed(struct dcp_backlight_state *state, u32 nits) return true; } +/* + * A report can establish takeover once. After Linux has a level or target, + * keep the observation separate from target, fade progress and completion. + * In particular, delayed reports cannot restart a fade or request a resend. + */ +static inline bool dcp_bl_report(struct dcp_backlight_state *state, u32 nits) +{ + if (!state->ready || nits > state->maximum) + return false; + state->reported = nits; + state->report_valid = true; + if (state->controlled || state->level_known) + return false; + return dcp_bl_seed(state, nits); +} + /* Report a usable target until a hint or completed present gives the level. */ static inline int dcp_bl_actual(const struct dcp_backlight_state *state) { diff --git a/drivers/gpu/drm/apple/dcp_backlight_test.c b/drivers/gpu/drm/apple/dcp_backlight_test.c index 57029cecb2f23..c8a4d31bde2af 100644 --- a/drivers/gpu/drm/apple/dcp_backlight_test.c +++ b/drivers/gpu/drm/apple/dcp_backlight_test.c @@ -600,7 +600,41 @@ static void backlight_desktop_swap_test(struct kunit *test) KUNIT_EXPECT_EQ(test, state.level, 0U); } +static void backlight_reports_keep_target_test(struct kunit *test) +{ + struct dcp_backlight_state state; + struct dcp_backlight_present present; + + KUNIT_ASSERT_EQ(test, dcp_bl_init(&state, 600, false, 0, true, 300), 0); + state.fade = true; + KUNIT_EXPECT_TRUE(test, dcp_bl_report(&state, 100)); + KUNIT_EXPECT_EQ(test, state.actual, 100U); + KUNIT_ASSERT_EQ(test, dcp_bl_request(&state, 600, false, false), 0); + KUNIT_EXPECT_TRUE(test, dcp_bl_fade_step(&state, 125, 250)); + KUNIT_EXPECT_FALSE(test, dcp_bl_report(&state, 50)); + KUNIT_EXPECT_EQ(test, state.target, 600U); + KUNIT_EXPECT_EQ(test, state.level, 350U); + KUNIT_EXPECT_EQ(test, state.actual, 100U); + KUNIT_ASSERT_EQ(test, dcp_bl_prepare(&state, true, &present), 0); + KUNIT_EXPECT_FALSE(test, dcp_bl_report(&state, 600)); + KUNIT_EXPECT_TRUE(test, dcp_bl_complete(&state, present.sequence, true)); + KUNIT_EXPECT_EQ(test, state.actual, 350U); + KUNIT_ASSERT_EQ(test, dcp_bl_request(&state, 500, false, false), 0); + KUNIT_EXPECT_FALSE(test, dcp_bl_report(&state, 100)); + KUNIT_EXPECT_TRUE(test, dcp_bl_fade_step(&state, 125, 250)); + KUNIT_EXPECT_EQ(test, state.level, 425U); + KUNIT_EXPECT_FALSE(test, dcp_bl_fade_step(&state, 250, 250)); + KUNIT_ASSERT_EQ(test, dcp_bl_prepare(&state, true, &present), 0); + KUNIT_EXPECT_TRUE(test, dcp_bl_complete(&state, present.sequence, true)); + KUNIT_EXPECT_FALSE(test, dcp_bl_report(&state, 200)); + KUNIT_EXPECT_EQ(test, state.reported, 200U); + KUNIT_EXPECT_EQ(test, state.target, 500U); + KUNIT_EXPECT_EQ(test, state.actual, 500U); + KUNIT_EXPECT_FALSE(test, state.dirty); +} + static struct kunit_case backlight_cases[] = { + KUNIT_CASE(backlight_reports_keep_target_test), KUNIT_CASE(backlight_millinits_takeover_test), KUNIT_CASE(backlight_takeover_test), KUNIT_CASE(backlight_range_test), diff --git a/drivers/gpu/drm/apple/systemep.c b/drivers/gpu/drm/apple/systemep.c index b8576aec5c6f5..31e4060679bb2 100644 --- a/drivers/gpu/drm/apple/systemep.c +++ b/drivers/gpu/drm/apple/systemep.c @@ -75,10 +75,6 @@ static int powerlog_report(struct apple_epic_service *service, enum epic_subtype struct apple_dcp *dcp = service->ep->dcp; int ret; - /* Later reports must not overwrite a latched hint or Linux's target. */ - if (dcp_backlight_active(dcp)) - return 0; - dev_dbg(dcp->dev, "systemep[ch:%u]: report type:%02x len:%zu\n", service->channel, type, data_size); diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py index bde59e98e0270..ac6798f250e1e 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py @@ -458,7 +458,7 @@ def powerlog_framing(root): struct apple_epic_service_ops {int (*report)(struct apple_epic_service *,enum epic_subtype,const void *,size_t);}; struct apple_epic_service {struct apple_dcp_afkep *ep;struct apple_epic_service_ops *ops;unsigned channel,users;}; static unsigned hints;static u32 hint; -static bool dcp_backlight_active(struct apple_dcp *d) {(void)d;return false;} +static bool __attribute__((unused)) dcp_backlight_active(struct apple_dcp *d) {(void)d;return false;} static bool dcp_has_panel(struct apple_dcp *d) {(void)d;return true;} static int dcp_backlight_takeover(struct apple_dcp *d,u32 mnits) {(void)d;hints++;hint=mnits;return 0;} static u32 get_unaligned_le32(const void *p) {u32 v;memcpy(&v,p,4);return v;} @@ -502,3 +502,65 @@ def powerlog_framing(root): return 0; } ''' + + +def keys_and_reports(root): + import display + prefix = display.backlight(root).split('static void completed(')[0] + prefix = prefix.replace('static inline ', 'static inline __attribute__((unused)) ') + return prefix + r''' +static void report(struct dcp_backlight_state *s,u32 nits) +{ + struct dcp_backlight_state before=*s; + assert(!dcp_bl_report(s,nits)); + assert(s->target==before.target && s->level==before.level && s->actual==before.actual); + assert(s->fade_from==before.fade_from && s->fading==before.fading); + assert(s->dirty==before.dirty && s->in_flight==before.in_flight && s->sequence==before.sequence); + assert(s->report_valid && s->reported==nits); +} +static void step(struct dcp_backlight_state *s,u32 elapsed,u32 *id,u32 *previous,bool up) +{ + struct dcp_backlight_swap p; + dcp_bl_fade_step(s,elapsed,250); + report(s,(*id & 1)?50:550); + if(!s->dirty)return; + assert(!dcp_bl_swap_prepare(s,&p,(*id)++,true)); + report(s,(*id & 1)?0:600); + if(*id & 1) { + assert(!dcp_bl_swap_finish(s,&p,p.id,true,true)); + report(s,250); + assert(dcp_bl_swap_finish(s,&p,p.id,false,true)); + } else { + assert(!dcp_bl_swap_finish(s,&p,p.id,false,true)); + report(s,160); + assert(dcp_bl_swap_finish(s,&p,p.id,true,true)); + } + assert(up?s->actual>=*previous:s->actual<=*previous); + *previous=s->actual; +} +int main(void) +{ + for(unsigned direction=0;direction<2;direction++) { + bool up=!direction;u32 initial=up?100:600,previous=initial,id=1; + struct dcp_backlight_state s; + assert(!dcp_bl_init(&s,600,false,0,true,300)); + s.fade=true;s.preserve_inherited=true; + assert(dcp_bl_report(&s,initial) && s.actual==initial && !s.dirty); + report(&s,up?550:50); /* Only the first hint can establish takeover. */ + for(unsigned key=1;key<=10;key++) { + u32 target=up?100+key*50:600-key*50; + assert(!dcp_bl_request(&s,target,false,false)); + report(&s,up?50:550); + /* Key repeat retargets before any fade can finish. */ + step(&s,16,&id,&previous,up);step(&s,32,&id,&previous,up); + assert(s.target==target); + } + u32 last=up?600:100; + for(u32 time=48;time<=256;time+=16)step(&s,time,&id,&previous,up); + assert(s.target==last && s.actual==last && s.level==last && !s.fading && !s.dirty); + for(unsigned late=0;late<64;late++)report(&s,late&1?0:600); + assert(s.target==last && s.actual==last && !s.dirty); + } + return 0; +} +''' diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/display.py b/tools/testing/selftests/drivers/gpu/apple-dcp/display.py index e64e4f9a16473..5ee71a0b20993 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/display.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/display.py @@ -11,6 +11,7 @@ def backlight(root): header = (root / 'drivers/gpu/drm/apple/dcp_backlight.h').read_text() header = '\n'.join(line for line in header.splitlines() if not line.startswith('#include')) + header = header.replace('static inline ', 'static inline __attribute__((unused)) ') return r''' #include #include diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/run.py b/tools/testing/selftests/drivers/gpu/apple-dcp/run.py index bad70b2fd8f6f..c5a2bed8483c2 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/run.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/run.py @@ -62,6 +62,7 @@ def main(): ('display', 'panel-mode-catalog', display.modes, ((),), ()), ('display', 'desktop-panel-identity', desktop.panel_identity, ((),), ()), ('display', 'desktop-powerlog-framing', desktop.powerlog_framing, ((),), ()), + ('display', 'desktop-brightness-keys-reports', desktop.keys_and_reports, ((),), ()), ('display', 'panel-refresh-range', display.refresh, ((),), ()), ('display', 'panel-adaptive-fallback', display.adaptive, ((),), ()), ('display', 'panel-adaptive-watchdog', display.watchdog, ((),), ()), From ad97d44a15a12901c4b71224b6770ae0989e6e8d Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 09:07:53 +1100 Subject: [PATCH 025/184] selftests: apple-dcp: retain the panel EDID for independent decoding Retain the synthetic panel identity EDID with host artifacts so a userspace parser can independently validate its monitor strings. Document panel identity, report framing and brightness ownership alongside the existing ramp and adaptive-sync policy. Assisted-by: LLM Signed-off-by: Ryan Murray --- Documentation/gpu/apple-dcp-t6041-panel.rst | 28 ++++++++++++++++++- drivers/gpu/drm/apple/dcp-display-metadata.c | 2 +- drivers/gpu/drm/apple/dcp-display-metadata.h | 2 +- .../selftests/drivers/gpu/apple-dcp/README | 9 ++++++ .../drivers/gpu/apple-dcp/desktop.py | 9 ++++-- .../selftests/drivers/gpu/apple-dcp/run.py | 2 +- 6 files changed, 45 insertions(+), 7 deletions(-) diff --git a/Documentation/gpu/apple-dcp-t6041-panel.rst b/Documentation/gpu/apple-dcp-t6041-panel.rst index 5bbe4ef6872ca..8512296f29fd8 100644 --- a/Documentation/gpu/apple-dcp-t6041-panel.rst +++ b/Documentation/gpu/apple-dcp-t6041-panel.rst @@ -9,11 +9,15 @@ by the H17P panel. The backlight interface reports the requested target and ``actual_brightness`` follows accepted, completed presents. A registration default does not change the brightness inherited from the boot loader. Until a loader hint or a completed user request supplies a known level, -``actual_brightness`` returns ``ENODATA``. Echoing the unchanged registration +``actual_brightness`` reports the target. Echoing the unchanged registration value also leaves the panel alone while its level is unknown. A request for a different target takes control. Once a hint or completed request establishes the level, all targets are usable. Framebuffer unblank and core suspend notifications also preserve an unknown inherited level. +The first explicit change fades from a known inherited level, or from the +reporting target when no hint is available. Firmware reports are observations, +not requests. After takeover they cannot replace the target, restart a fade or +cause a brightness write. Actual readback follows accepted, completed presents. Ambient-light policy can use the normal backlight interface, just as a slider does. Requests made while blanked or suspended preserve the restore target. @@ -36,3 +40,25 @@ swap watchdog and re-applies the active CRTC at fixed refresh. Either failure latches adaptive sync off until reboot. A transport that stops replying cannot be recovered by issuing another command on its busy channel; it still requires a reboot. Use the previous kernel entry if a panel fails qualification. + +Panel identity +-------------- + +The desktop internal panel has no external EDID service. Its ProductAttributes +supply the monitor identity, independently of optional colorimetry or timing +fields. The driver publishes an identity-only EDID using that product data and +the panel dimensions from the device tree. The firmware timing list remains +authoritative; the EDID adds no synthetic modes or color capabilities. + +Compact powerlog reports may be bare serialized dictionaries or carry a command +descriptor. The receiver preserves the serialization header before handing the +report to systemep. Only the first valid report before Linux control can seed +takeover; later reports confirm output without changing the requested target. + +Desktop mode changes use the D202 and D454 adapters for this firmware's records. +The callback table also includes physical mapping and the empty resize +notification. An unknown callback during a modeset gets a bounded unsupported +reply. Its enclosing command must ACK before the set fails, so a subsequent +mode change can reuse the drained channel. Accepted atomic modeset errors are +reported through the out-fence. A timed-out command retains its channel until +the firmware replies; another set cannot overwrite its stack. diff --git a/drivers/gpu/drm/apple/dcp-display-metadata.c b/drivers/gpu/drm/apple/dcp-display-metadata.c index 5bc60abd95e4b..fd5e0cd6ccdd2 100644 --- a/drivers/gpu/drm/apple/dcp-display-metadata.c +++ b/drivers/gpu/drm/apple/dcp-display-metadata.c @@ -208,7 +208,7 @@ const struct drm_edid *dcp_metadata_edid(const struct dcp_display_metadata *meta * an EDID timing, chromaticities or gamma when the panel omits those fields. */ const struct drm_edid *dcp_panel_edid(const struct dcp_display_metadata *metadata, - u32 width_mm, u32 height_mm) + u32 width_mm, u32 height_mm) { struct edid block = {}; const struct drm_edid *edid; diff --git a/drivers/gpu/drm/apple/dcp-display-metadata.h b/drivers/gpu/drm/apple/dcp-display-metadata.h index b7ec1505d5728..226b778f40138 100644 --- a/drivers/gpu/drm/apple/dcp-display-metadata.h +++ b/drivers/gpu/drm/apple/dcp-display-metadata.h @@ -22,6 +22,6 @@ const struct drm_edid *dcp_metadata_edid(const struct dcp_display_metadata *meta const struct drm_display_mode *mode); const struct drm_edid *dcp_panel_edid(const struct dcp_display_metadata *metadata, - u32 width_mm, u32 height_mm); + u32 width_mm, u32 height_mm); #endif diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/README b/tools/testing/selftests/drivers/gpu/apple-dcp/README index 8a7b42bb7a4fe..dfbdaaab3f367 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/README +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/README @@ -172,3 +172,12 @@ watchdog test checks fixed-refresh recovery, retained-buffer arming and late completion isolation. These tests do not establish which timings a particular panel reports or prove refresh rate, flicker, brightness smoothness or adaptive-sync on hardware. + +Desktop panel brightness regressions also cover identity-only EDID parsing and +publication from ProductAttributes without optional color data. Both bare and +descriptor-wrapped powerlog reports pass through the production compact receiver, +systemep handler and mNits parser. Rapid key repeats with interleaved stale and +new reports must converge monotonically to the last user target in both +directions. Report observations cannot restart a fade or request another write. +The callback cases exercise the desktop D202/D454 wire adapters, physical resource +mapping and release, and unknown-callback failure followed by a successful set. diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py index ac6798f250e1e..0c1e7c5dd7400 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py @@ -359,7 +359,7 @@ def modeset_unknown(root): return unit.replace(' return 0;\n}', tests + ' return 0;\n}') -def panel_identity(root): +def panel_identity(root, output=None): import struct import rate f = rate.parser_fixture(root) @@ -374,7 +374,7 @@ def panel_identity(root): helpers = '\n'.join(block(source, name) for name in ( 'void dcp_panel_metadata_replace(', 'bool dcp_panel_metadata_current(', 'const struct drm_edid *dcp_panel_edid_snapshot(')) - return code + r''' + unit = code + r''' #define DCP_FIRMWARE_V_26_6_DESKTOP 5 #define spin_lock_irqsave(p,f) do {assert(!*(p));*(p)=true;(f)=0;} while(0) #define spin_unlock_irqrestore(p,f) do {assert(*(p));*(p)=false;(void)(f);} while(0) @@ -387,7 +387,7 @@ def panel_identity(root): static bool dcp_has_panel(struct apple_dcp *d) {return d->has_panel;} static void dcp_queue_hotplug(void *con) {assert(con);notifications++;} ''' + helpers + '\nstatic const u8 attributes[]={'+','.join(map(str,raw))+'};\n' + r''' -int main(void) +int main(int argc, char **argv) { struct dcp_parse_ctx ctx; struct dcp_display_metadata metadata; @@ -410,6 +410,7 @@ def panel_identity(root): assert(bytes[18]==1 && bytes[19]==3 && bytes[21]==35 && bytes[22]==22 && bytes[23]==0xff); assert(bytes[54+3]==EDID_DETAIL_MONITOR_NAME && bytes[72+3]==EDID_DETAIL_MONITOR_SERIAL); assert(!memcmp(bytes+72+5,"12345678\n",9)); + if(argc==2) {FILE *file=fopen(argv[1],"wb");assert(file);assert(fwrite(bytes,128,1,file)==1);fclose(file);} drm_edid_free(edid); dcp_panel_metadata_replace(&d,NULL); assert(!dcp_panel_metadata_current(&d,generation)); @@ -422,6 +423,8 @@ def panel_identity(root): } ''' + return (unit, ((str(output / "desktop-panel-identity.edid"),),)) if output else unit + def powerlog_framing(root): import rate diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/run.py b/tools/testing/selftests/drivers/gpu/apple-dcp/run.py index c5a2bed8483c2..1f6e1d845f44f 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/run.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/run.py @@ -60,7 +60,7 @@ def main(): ('display', 'panel-brightness-commit', display.brightness_commit, ((),), ()), ('display', 'panel-brightness-takeover', display.takeover, ((),), ()), ('display', 'panel-mode-catalog', display.modes, ((),), ()), - ('display', 'desktop-panel-identity', desktop.panel_identity, ((),), ()), + ('display', 'desktop-panel-identity', lambda r: desktop.panel_identity(r, output), ((),), ()), ('display', 'desktop-powerlog-framing', desktop.powerlog_framing, ((),), ()), ('display', 'desktop-brightness-keys-reports', desktop.keys_and_reports, ((),), ()), ('display', 'panel-refresh-range', display.refresh, ((),), ()), From 505d49459e2eed947772c36cff4e29a5528938f8 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 09:19:53 +1100 Subject: [PATCH 026/184] drm/apple: include DCP timing and range limits in the panel EDID Provide a representable preferred timing and catalog limits for a conformant panel EDID. Select a base-block timing from the actual DCP catalog, including when the preferred high-refresh clock exceeds the base EDID format. Round only the EDID clock to its 10 kHz units; the DRM mode catalog stays unchanged. Derive vertical limits from the validated adaptive range or fixed mode rates, and horizontal and clock limits from the same retained catalog being published. Use EDID 1.4 frequency offsets above 255 kHz. This also preserves the panel adaptive-sync range when DRM reads the new EDID. Extend the host and KUnit cases with a high-clock 120 Hz mode, a representable 60 Hz mode with a fractional clock, and 24-120 Hz adaptive limits. The retained synthetic EDID passes edid-decode conformance validation. Assisted-by: LLM Signed-off-by: Ryan Murray --- Documentation/gpu/apple-dcp-t6041-panel.rst | 7 +- drivers/gpu/drm/apple/dcp-display-metadata.c | 97 ++++++++++++++++--- drivers/gpu/drm/apple/dcp-display-metadata.h | 2 + drivers/gpu/drm/apple/dcp-internal.h | 4 +- drivers/gpu/drm/apple/dcp.c | 8 +- drivers/gpu/drm/apple/iomfb.c | 2 +- drivers/gpu/drm/apple/parser-test.c | 19 +++- .../selftests/drivers/gpu/apple-dcp/README | 2 +- .../drivers/gpu/apple-dcp/desktop.py | 27 ++++-- .../selftests/drivers/gpu/apple-dcp/rate.py | 11 ++- 10 files changed, 148 insertions(+), 31 deletions(-) diff --git a/Documentation/gpu/apple-dcp-t6041-panel.rst b/Documentation/gpu/apple-dcp-t6041-panel.rst index 8512296f29fd8..a32407077a1c2 100644 --- a/Documentation/gpu/apple-dcp-t6041-panel.rst +++ b/Documentation/gpu/apple-dcp-t6041-panel.rst @@ -46,9 +46,10 @@ Panel identity The desktop internal panel has no external EDID service. Its ProductAttributes supply the monitor identity, independently of optional colorimetry or timing -fields. The driver publishes an identity-only EDID using that product data and -the panel dimensions from the device tree. The firmware timing list remains -authoritative; the EDID adds no synthetic modes or color capabilities. +fields. The driver publishes an EDID using that product data, the panel dimensions +from the device tree, and a representable DCP timing plus catalog limits. +The EDID clock rounds to its required 10 kHz units. DRM still uses the exact +firmware timing list; the EDID adds no synthetic modes or color capabilities. Compact powerlog reports may be bare serialized dictionaries or carry a command descriptor. The receiver preserves the serialization header before handing the diff --git a/drivers/gpu/drm/apple/dcp-display-metadata.c b/drivers/gpu/drm/apple/dcp-display-metadata.c index fd5e0cd6ccdd2..8819f65cb8655 100644 --- a/drivers/gpu/drm/apple/dcp-display-metadata.c +++ b/drivers/gpu/drm/apple/dcp-display-metadata.c @@ -1,11 +1,13 @@ // SPDX-License-Identifier: GPL-2.0-only OR MIT #include #include +#include #include #include #include #include "dcp-display-metadata.h" +#include "parser.h" static int dcp_metadata_timing(struct detailed_timing *timing, const struct drm_display_mode *mode, @@ -203,51 +205,120 @@ const struct drm_edid *dcp_metadata_edid(const struct dcp_display_metadata *meta return edid; } +/* EDID 1.4 range offsets extend each one-byte limit by 255. */ +static int dcp_panel_range_byte(u32 limit, u8 *out, u8 *offsets, u8 flag) +{ + if (!limit || limit > 510) + return -ERANGE; + if (limit > 255) { + limit -= 255; + *offsets |= flag; + } + *out = limit; + return 0; +} + /* - * Internal modes come from TimingElements. Supply identity without inventing - * an EDID timing, chromaticities or gamma when the panel omits those fields. + * Supply one representable DCP timing and its catalog limits for EDID + * conformity. DRM still enumerates TimingElements, never these rounded bytes. */ const struct drm_edid *dcp_panel_edid(const struct dcp_display_metadata *metadata, + const struct dcp_display_mode *modes, unsigned int count, u32 width_mm, u32 height_mm) { struct edid block = {}; + struct dcp_display_metadata dimensions = *metadata; + struct detailed_non_pixel *limits = &block.detailed_timings[3].data.other_data; + struct detailed_data_monitor_range *range = &limits->data.range; const struct drm_edid *edid; char serial[14]; + u32 min_v = U32_MAX, max_v = 0, min_h = U32_MAX, max_h = 0, clock = 0; u16 manufacturer = 0; u8 sum = 0; + bool timing = false, preferred = false; int ret; - if (strnlen(metadata->manufacturer, 4) != 3 || metadata->product > U16_MAX || - !width_mm || width_mm > 2550 || !height_mm || height_mm > 2550) + if (!modes || !count || strnlen(metadata->manufacturer, 4) != 3 || + metadata->product > U16_MAX || !width_mm || width_mm > 2550 || + !height_mm || height_mm > 2550) return ERR_PTR(-EINVAL); for (unsigned int i = 0; i < 3; i++) { if (metadata->manufacturer[i] < 'A' || metadata->manufacturer[i] > 'Z') return ERR_PTR(-EINVAL); manufacturer = manufacturer << 5 | (metadata->manufacturer[i] - '@'); } + dimensions.width_cm = DIV_ROUND_CLOSEST(width_mm, 10); + dimensions.height_cm = DIV_ROUND_CLOSEST(height_mm, 10); + for (unsigned int i = 0; i < count; i++) { + const struct dcp_display_mode *entry = &modes[i]; + struct drm_display_mode mode = entry->mode; + struct detailed_timing candidate = {}; + u32 low, high; + u32 pixels; + + if (mode.clock <= 0 || mode.clock > 2550000 || mode.htotal <= 0 || + mode.htotal > 65535 || mode.vtotal <= 0 || mode.vtotal > 65535) + return ERR_PTR(-EINVAL); + pixels = (u32)mode.htotal * mode.vtotal; + low = div_u64((u64)mode.clock * 1000, pixels); + high = DIV_ROUND_UP_ULL((u64)mode.clock * 1000, pixels); + if (entry->vrr && entry->min_vrr && entry->min_vrr <= entry->max_vrr) { + low = entry->min_vrr >> 16; + high = DIV_ROUND_UP_ULL(entry->max_vrr, 1U << 16); + } + min_v = min(min_v, low); + max_v = max(max_v, high); + min_h = min(min_h, (u32)mode.clock / mode.htotal); + max_h = max(max_h, DIV_ROUND_UP_ULL((u32)mode.clock, mode.htotal)); + clock = max_t(u32, clock, mode.clock); + /* The EDID base timing clock has units of 10 kHz. */ + mode.clock = DIV_ROUND_CLOSEST(mode.clock, 10) * 10; + ret = dcp_metadata_timing(&candidate, &mode, &dimensions); + if (!ret && (!timing || (!preferred && (mode.type & DRM_MODE_TYPE_PREFERRED)))) { + block.detailed_timings[0] = candidate; + timing = true; + preferred = mode.type & DRM_MODE_TYPE_PREFERRED; + } + } + if (!timing || clock > 2550000) + return ERR_PTR(-ERANGE); + memcpy(block.header, "\x00\xff\xff\xff\xff\xff\xff\x00", 8); block.product_id.manufacturer_name = cpu_to_be16(manufacturer); block.product_id.product_code = cpu_to_le16(metadata->product); block.product_id.serial_number = cpu_to_le32(metadata->serial); block.version = 1; - block.revision = 3; + block.revision = 4; block.input = DRM_EDID_INPUT_DIGITAL; - block.width_cm = DIV_ROUND_CLOSEST(width_mm, 10); - block.height_cm = DIV_ROUND_CLOSEST(height_mm, 10); - block.gamma = 0xff; /* Unspecified. */ + block.width_cm = dimensions.width_cm; + block.height_cm = dimensions.height_cm; + block.gamma = 0xff; + block.features = DRM_EDID_FEATURE_PREFERRED_TIMING; memset(block.standard_timings, 1, sizeof(block.standard_timings)); - for (unsigned int i = 0; i < ARRAY_SIZE(block.detailed_timings); i++) - block.detailed_timings[i].data.other_data.type = 0x10 /* Dummy descriptor. */; - ret = dcp_metadata_text(&block.detailed_timings[0], EDID_DETAIL_MONITOR_NAME, + ret = dcp_metadata_text(&block.detailed_timings[1], EDID_DETAIL_MONITOR_NAME, metadata->name); if (ret) return ERR_PTR(ret); - /* A zero numeric serial is a valid unspecified EDID serial, not NULL. */ snprintf(serial, sizeof(serial), "%08x", metadata->serial); - ret = dcp_metadata_text(&block.detailed_timings[1], EDID_DETAIL_MONITOR_SERIAL, + ret = dcp_metadata_text(&block.detailed_timings[2], EDID_DETAIL_MONITOR_SERIAL, metadata->serial_text[0] ? metadata->serial_text : serial); if (ret) return ERR_PTR(ret); + limits->type = EDID_DETAIL_MONITOR_RANGE; + ret = dcp_panel_range_byte(min_v, &range->min_vfreq, &limits->pad2, + DRM_EDID_RANGE_OFFSET_MIN_VFREQ); + ret = ret ?: dcp_panel_range_byte(max_v, &range->max_vfreq, &limits->pad2, + DRM_EDID_RANGE_OFFSET_MAX_VFREQ); + ret = ret ?: dcp_panel_range_byte(min_h, &range->min_hfreq_khz, &limits->pad2, + DRM_EDID_RANGE_OFFSET_MIN_HFREQ); + ret = ret ?: dcp_panel_range_byte(max_h, &range->max_hfreq_khz, &limits->pad2, + DRM_EDID_RANGE_OFFSET_MAX_HFREQ); + if (ret) + return ERR_PTR(ret); + range->pixel_clock_mhz = DIV_ROUND_UP_ULL(clock, 10000); + range->flags = DRM_EDID_RANGE_LIMITS_ONLY_FLAG; + memset(&range->formula, ' ', sizeof(range->formula)); + *((u8 *)&range->formula) = '\n'; for (unsigned int i = 0; i < sizeof(block) - 1; i++) sum += ((const u8 *)&block)[i]; block.checksum = -sum; diff --git a/drivers/gpu/drm/apple/dcp-display-metadata.h b/drivers/gpu/drm/apple/dcp-display-metadata.h index 226b778f40138..d97b090af699d 100644 --- a/drivers/gpu/drm/apple/dcp-display-metadata.h +++ b/drivers/gpu/drm/apple/dcp-display-metadata.h @@ -18,10 +18,12 @@ struct dcp_display_metadata { struct drm_edid; struct drm_display_mode; +struct dcp_display_mode; const struct drm_edid *dcp_metadata_edid(const struct dcp_display_metadata *metadata, const struct drm_display_mode *mode); const struct drm_edid *dcp_panel_edid(const struct dcp_display_metadata *metadata, + const struct dcp_display_mode *modes, unsigned int count, u32 width_mm, u32 height_mm); #endif diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index ef8cc5b093573..f8cbb94b6951e 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -307,7 +307,9 @@ struct dcp_hdmi; bool dcp_desktop_present_complete(struct apple_dcp *dcp, u32 id); void dcp_panel_metadata_replace(struct apple_dcp *dcp, const struct dcp_display_metadata *metadata); -const struct drm_edid *dcp_panel_edid_snapshot(struct apple_dcp *dcp, u64 *generation); +const struct drm_edid *dcp_panel_edid_snapshot(struct apple_dcp *dcp, + const struct dcp_display_mode *modes, + unsigned int count, u64 *generation); bool dcp_panel_metadata_current(struct apple_dcp *dcp, u64 generation); bool dcp_desktop_vrr_supported(struct apple_dcp *dcp); int dcp_desktop_vrr_check(struct apple_dcp *dcp, struct drm_crtc_state *state); diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index dbfb03fa2a040..70c52f2015574 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -806,7 +806,9 @@ bool dcp_panel_metadata_current(struct apple_dcp *dcp, u64 generation) return valid; } -const struct drm_edid *dcp_panel_edid_snapshot(struct apple_dcp *dcp, u64 *generation) +const struct drm_edid *dcp_panel_edid_snapshot(struct apple_dcp *dcp, + const struct dcp_display_mode *modes, + unsigned int count, u64 *generation) { struct dcp_display_metadata metadata; const struct drm_edid *edid; @@ -822,7 +824,9 @@ const struct drm_edid *dcp_panel_edid_snapshot(struct apple_dcp *dcp, u64 *gener spin_unlock_irqrestore(&dcp->metadata.lock, flags); if (!valid) return ERR_PTR(-ENODATA); - edid = dcp_panel_edid(&metadata, dcp->panel.width_mm, dcp->panel.height_mm); + /* The caller supplies the same retained catalog it is publishing. */ + edid = dcp_panel_edid(&metadata, modes, count, + dcp->panel.width_mm, dcp->panel.height_mm); if (!IS_ERR(edid) && !dcp_panel_metadata_current(dcp, *generation)) { drm_edid_free(edid); return ERR_PTR(-ESTALE); diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 2a48c8b8df962..510ef60e8e592 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -1947,7 +1947,7 @@ int dcp_get_modes(struct drm_connector *connector) bool panel = dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && dcp_has_panel(dcp); const struct drm_edid *derived = nr_modes ? - (panel ? dcp_panel_edid_snapshot(dcp, &generation) : + (panel ? dcp_panel_edid_snapshot(dcp, modes, nr_modes, &generation) : dcp_firmware_edid(dcp, &generation)) : ERR_PTR(-ENODATA); if (!IS_ERR(derived)) { diff --git a/drivers/gpu/drm/apple/parser-test.c b/drivers/gpu/drm/apple/parser-test.c index 9a7426d1f7016..511ac4e1f66ef 100644 --- a/drivers/gpu/drm/apple/parser-test.c +++ b/drivers/gpu/drm/apple/parser-test.c @@ -1316,6 +1316,19 @@ static void parser_panel_identity(struct kunit *test) struct dcp_parse_ctx ctx; const struct drm_edid *edid; const struct edid *raw; + struct dcp_display_mode modes[2] = {}; + + modes[0].mode = (struct drm_display_mode) { + .clock = 540005, .hdisplay = 3456, .hsync_start = 3500, + .hsync_end = 3550, .htotal = 4000, .vdisplay = 2160, + .vsync_start = 2165, .vsync_end = 2170, .vtotal = 2250, + .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC, + }; + modes[1] = modes[0]; + modes[1].mode.clock = 1080005; + modes[1].vrr = true; + modes[1].min_vrr = 24U << 16; + modes[1].max_vrr = 120U << 16; KUNIT_ASSERT_NOT_NULL(test, blob); put_unaligned_le32(0xd3, blob->data); @@ -1332,13 +1345,17 @@ static void parser_panel_identity(struct kunit *test) KUNIT_ASSERT_EQ(test, parse(blob->data, blob->size, &ctx), 0); KUNIT_ASSERT_EQ(test, parse_panel_metadata(&ctx, &metadata), 0); KUNIT_EXPECT_STREQ(test, metadata.name, "Synthetic Int"); - edid = dcp_panel_edid(&metadata, 345, 223); + edid = dcp_panel_edid(&metadata, modes, ARRAY_SIZE(modes), 345, 223); KUNIT_ASSERT_FALSE(test, IS_ERR(edid)); KUNIT_EXPECT_TRUE(test, drm_edid_valid(edid)); raw = drm_edid_raw(edid); KUNIT_EXPECT_EQ(test, le16_to_cpu(raw->product_id.product_code), 42); KUNIT_EXPECT_EQ(test, le32_to_cpu(raw->product_id.serial_number), 0x12345678U); KUNIT_EXPECT_EQ(test, raw->gamma, 0xff); + KUNIT_EXPECT_EQ(test, le16_to_cpu(raw->detailed_timings[0].pixel_clock), 54001); + KUNIT_EXPECT_EQ(test, raw->detailed_timings[3].data.other_data.data.range.min_vfreq, 24); + KUNIT_EXPECT_EQ(test, raw->detailed_timings[3].data.other_data.data.range.max_vfreq, 120); + KUNIT_EXPECT_EQ(test, modes[0].mode.clock, 540005); drm_edid_free(edid); } diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/README b/tools/testing/selftests/drivers/gpu/apple-dcp/README index dfbdaaab3f367..4f4cc2880068b 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/README +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/README @@ -173,7 +173,7 @@ completion isolation. These tests do not establish which timings a particular panel reports or prove refresh rate, flicker, brightness smoothness or adaptive-sync on hardware. -Desktop panel brightness regressions also cover identity-only EDID parsing and +Desktop panel brightness regressions also cover panel EDID parsing and publication from ProductAttributes without optional color data. Both bare and descriptor-wrapped powerlog reports pass through the production compact receiver, systemep handler and mNits parser. Rapid key repeats with interleaved stale and diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py index 0c1e7c5dd7400..2d9501f3eaa98 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py @@ -395,30 +395,41 @@ def panel_identity(root, output=None): assert(!parse_panel_metadata(&ctx,&metadata)); assert(!strcmp(metadata.manufacturer,"TST") && metadata.product==42 && metadata.serial==0x12345678); assert(!strcmp(metadata.name,"Synthetic Int")); - /* No color fields, date or EDID-representable timing is required. */ + /* Optional color/date fields are absent; timings come from the catalog. */ struct apple_dcp d={.fw_compat=5,.has_panel=true,.panel={345,223},.connector=&d}; __typeof__(*d.desktop) desktop={0};d.desktop=&desktop; + struct dcp_display_mode modes[3]={0}; + for(unsigned i=0;i<3;i++)modes[i].mode=(struct drm_display_mode){.clock=i==0?1080005:i==1?540005:432000, + .hdisplay=3456,.hsync_start=3500,.hsync_end=3550,.htotal=4000, + .vdisplay=2160,.vsync_start=2165,.vsync_end=2170,.vtotal=2250, + .flags=DRM_MODE_FLAG_PHSYNC|DRM_MODE_FLAG_PVSYNC,.type=i==0?DRM_MODE_TYPE_PREFERRED:0}; + modes[0].vrr=true;modes[0].min_vrr=24U<<16;modes[0].max_vrr=120U<<16; u64 generation=99; - assert(IS_ERR(dcp_panel_edid_snapshot(&d,&generation))); + assert(IS_ERR(dcp_panel_edid_snapshot(&d,modes,3,&generation))); dcp_panel_metadata_replace(&d,&metadata); assert(notifications==1 && desktop.panel_metadata_valid); - const struct drm_edid *edid=dcp_panel_edid_snapshot(&d,&generation); + const struct drm_edid *edid=dcp_panel_edid_snapshot(&d,modes,3,&generation); assert(!IS_ERR(edid) && drm_edid_valid(edid) && dcp_panel_metadata_current(&d,generation)); const u8 *bytes=(const void *)&edid->raw; assert(bytes[8]==0x52 && bytes[9]==0x74 && bytes[10]==42 && !bytes[11]); assert(bytes[12]==0x78 && bytes[13]==0x56 && bytes[14]==0x34 && bytes[15]==0x12); - assert(bytes[18]==1 && bytes[19]==3 && bytes[21]==35 && bytes[22]==22 && bytes[23]==0xff); - assert(bytes[54+3]==EDID_DETAIL_MONITOR_NAME && bytes[72+3]==EDID_DETAIL_MONITOR_SERIAL); - assert(!memcmp(bytes+72+5,"12345678\n",9)); + assert(bytes[18]==1 && bytes[19]==4 && bytes[21]==35 && bytes[22]==22 && bytes[23]==0xff); + assert(bytes[72+3]==EDID_DETAIL_MONITOR_NAME && bytes[90+3]==EDID_DETAIL_MONITOR_SERIAL); + assert(!memcmp(bytes+90+5,"12345678\n",9)); if(argc==2) {FILE *file=fopen(argv[1],"wb");assert(file);assert(fwrite(bytes,128,1,file)==1);fclose(file);} + assert(((unsigned)bytes[54]|(unsigned)bytes[55]<<8)==54001); + assert(bytes[108+3]==EDID_DETAIL_MONITOR_RANGE && bytes[108+5]==24 && bytes[108+6]==120); + assert(bytes[108+4] & DRM_EDID_RANGE_OFFSET_MAX_HFREQ); + /* The encoded timing clock rounds to 10 kHz; the DCP catalog is unchanged. */ + assert(modes[1].mode.clock==540005 && modes[0].mode.clock==1080005); drm_edid_free(edid); dcp_panel_metadata_replace(&d,NULL); assert(!dcp_panel_metadata_current(&d,generation)); - assert(IS_ERR(dcp_panel_edid_snapshot(&d,&generation))); + assert(IS_ERR(dcp_panel_edid_snapshot(&d,modes,3,&generation))); /* Another firmware/profile cannot publish a desktop panel identity. */ d.fw_compat=2;dcp_panel_metadata_replace(&d,&metadata); assert(!desktop.panel_metadata_valid); - metadata.manufacturer[0]='?';assert(IS_ERR(dcp_panel_edid(&metadata,345,223))); + metadata.manufacturer[0]='?';assert(IS_ERR(dcp_panel_edid(&metadata,modes,3,345,223))); return 0; } ''' diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py b/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py index db5930b71215b..4194e8d39ad20 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py @@ -144,6 +144,15 @@ def wire(value, out): #define mul_u32_u32(a, b) ((u64)(u32)(a) * (u32)(b)) #define DIV_ROUND_CLOSEST_ULL(n, d) (((n) + (d) / 2) / (d)) #define DIV_ROUND_CLOSEST(n, d) (((n) + (d) / 2) / (d)) +#ifndef min +#define min(a,b) ((a)<(b)?(a):(b)) +#endif +#ifndef max +#define max(a,b) ((a)>(b)?(a):(b)) +#endif +#define max_t(t,a,b) max((t)(a),(t)(b)) +#define div_u64(n,d) ((n)/(d)) +#define DIV_ROUND_UP_ULL(n,d) (((u64)(n)+(d)-1)/(d)) #define trace_iomfb_color_mode(...) ((void)0) #define trace_iomfb_parse_mode_fail(...) ((void)0) #define trace_iomfb_parse_mode_success(...) ((void)0) @@ -638,7 +647,7 @@ def publication(root): } static const struct drm_edid *dcp_firmware_edid(struct apple_dcp *d,u64 *generation) { (void)d; (void)generation; return ERR_PTR(-ENODATA); } static bool dcp_firmware_metadata_current(struct apple_dcp *d,u64 generation) { (void)d; (void)generation; return false; } -static const struct drm_edid *dcp_panel_edid_snapshot(struct apple_dcp *d,u64 *generation) { (void)d;(void)generation;assert(!"panel identity in external fixture");return ERR_PTR(-ENODATA); } +static const struct drm_edid *dcp_panel_edid_snapshot(struct apple_dcp *d,const struct dcp_display_mode *m,unsigned n,u64 *generation) { (void)d;(void)m;(void)n;(void)generation;assert(!"panel identity in external fixture");return ERR_PTR(-ENODATA); } static bool dcp_panel_metadata_current(struct apple_dcp *d,u64 generation) { (void)d;(void)generation;assert(!"panel identity in external fixture");return false; } static void dcp_panel_metadata_replace(struct apple_dcp *d,const struct dcp_display_metadata *m) { (void)d;(void)m;assert(!"panel identity in external receipt"); } From 4b0269870f7ac7f2dadce1fd820e6dce929e630e Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 09:26:57 +1100 Subject: [PATCH 027/184] drm/apple: keep brightness report observations scoped to desktop Preserve the established active-report filter for the Neo and other non-desktop backends. The desktop backlight uses its separate ready state, so T6041 reports still reach the confirmation-only policy after registration. This avoids exposing unrelated backends to newly parsed report traffic. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/gpu/drm/apple/systemep.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/gpu/drm/apple/systemep.c b/drivers/gpu/drm/apple/systemep.c index 31e4060679bb2..44e239c6016b0 100644 --- a/drivers/gpu/drm/apple/systemep.c +++ b/drivers/gpu/drm/apple/systemep.c @@ -75,6 +75,10 @@ static int powerlog_report(struct apple_epic_service *service, enum epic_subtype struct apple_dcp *dcp = service->ep->dcp; int ret; + /* Keep the existing filtering on active non-desktop backends. */ + if (dcp_backlight_active(dcp)) + return 0; + dev_dbg(dcp->dev, "systemep[ch:%u]: report type:%02x len:%zu\n", service->channel, type, data_size); From 40c53dd27c73bfdfbf237998253f4ce00dc0bcd3 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 09:43:59 +1100 Subject: [PATCH 028/184] drm/apple: retain the fixed panel identity across attribute repeats A repeated or partial DisplayAttributes dictionary must not revoke a known internal panel identity or emit another hotplug event. Retain the validated identity when a dictionary omits product data, and advance its generation only when valid identity fields change. This keeps unrelated attribute updates from making userspace reprobe the monitor and restore brightness again. Extend the host case with repeated and partial dictionaries and an actual identity replacement, and add KUnit for the fixed-panel cache lifecycle. Assisted-by: LLM Signed-off-by: Ryan Murray --- Documentation/gpu/apple-dcp-t6041-panel.rst | 3 ++ drivers/gpu/drm/apple/dcp.c | 16 +++++++---- drivers/gpu/drm/apple/parser-test.c | 28 +++++++++++++++++++ .../selftests/drivers/gpu/apple-dcp/README | 3 ++ .../drivers/gpu/apple-dcp/desktop.py | 17 ++++++++--- 5 files changed, 58 insertions(+), 9 deletions(-) diff --git a/Documentation/gpu/apple-dcp-t6041-panel.rst b/Documentation/gpu/apple-dcp-t6041-panel.rst index a32407077a1c2..94ddee52d02aa 100644 --- a/Documentation/gpu/apple-dcp-t6041-panel.rst +++ b/Documentation/gpu/apple-dcp-t6041-panel.rst @@ -63,3 +63,6 @@ reply. Its enclosing command must ACK before the set fails, so a subsequent mode change can reuse the drained channel. Accepted atomic modeset errors are reported through the out-fence. A timed-out command retains its channel until the firmware replies; another set cannot overwrite its stack. + +Repeated or partial DisplayAttributes retain a validated fixed-panel identity. +They do not emit another identity hotplug event or revoke the EDID. diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 70c52f2015574..5f3901acba4de 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -781,16 +781,22 @@ void dcp_panel_metadata_replace(struct apple_dcp *dcp, const struct dcp_display_metadata *metadata) { unsigned long flags; + bool changed = false; - if (dcp->fw_compat != DCP_FIRMWARE_V_26_6_DESKTOP || !dcp_has_panel(dcp)) + if (!metadata || dcp->fw_compat != DCP_FIRMWARE_V_26_6_DESKTOP || + !dcp_has_panel(dcp)) return; spin_lock_irqsave(&dcp->metadata.lock, flags); - dcp->desktop->panel_metadata_generation++; - dcp->desktop->panel_metadata_valid = !!metadata; - if (metadata) + /* Partial or repeated attributes cannot revoke this fixed panel's identity. */ + if (!dcp->desktop->panel_metadata_valid || + memcmp(&dcp->desktop->panel_metadata, metadata, sizeof(*metadata))) { dcp->desktop->panel_metadata = *metadata; + dcp->desktop->panel_metadata_valid = true; + dcp->desktop->panel_metadata_generation++; + changed = true; + } spin_unlock_irqrestore(&dcp->metadata.lock, flags); - if (dcp->connector) + if (changed && dcp->connector) dcp_queue_hotplug(dcp->connector); } diff --git a/drivers/gpu/drm/apple/parser-test.c b/drivers/gpu/drm/apple/parser-test.c index 511ac4e1f66ef..aeea21b07e5eb 100644 --- a/drivers/gpu/drm/apple/parser-test.c +++ b/drivers/gpu/drm/apple/parser-test.c @@ -1359,7 +1359,35 @@ static void parser_panel_identity(struct kunit *test) drm_edid_free(edid); } +static void parser_panel_identity_stable(struct kunit *test) +{ + struct apple_dcp *dcp = kunit_kzalloc(test, sizeof(*dcp), GFP_KERNEL); + struct dcp_desktop_state *desktop = kunit_kzalloc(test, sizeof(*desktop), GFP_KERNEL); + struct dcp_display_metadata metadata = { + .manufacturer = "TST", .name = "Synthetic", .product = 42, .serial = 1, + }; + u64 generation; + + KUNIT_ASSERT_NOT_NULL(test, dcp); + KUNIT_ASSERT_NOT_NULL(test, desktop); + dcp->desktop = desktop; + dcp->fw_compat = DCP_FIRMWARE_V_26_6_DESKTOP; + dcp->panel.width_mm = 345; + spin_lock_init(&dcp->metadata.lock); + dcp_panel_metadata_replace(dcp, &metadata); + generation = desktop->panel_metadata_generation; + KUNIT_EXPECT_TRUE(test, dcp_panel_metadata_current(dcp, generation)); + dcp_panel_metadata_replace(dcp, NULL); + dcp_panel_metadata_replace(dcp, &metadata); + KUNIT_EXPECT_TRUE(test, dcp_panel_metadata_current(dcp, generation)); + metadata.serial++; + dcp_panel_metadata_replace(dcp, &metadata); + KUNIT_EXPECT_FALSE(test, dcp_panel_metadata_current(dcp, generation)); + KUNIT_EXPECT_EQ(test, desktop->panel_metadata.serial, 2U); +} + static struct kunit_case parser_cases[] = { + KUNIT_CASE(parser_panel_identity_stable), KUNIT_CASE(parser_panel_identity), KUNIT_CASE(parser_score_presence), KUNIT_CASE(parser_legacy_ties), diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/README b/tools/testing/selftests/drivers/gpu/apple-dcp/README index 4f4cc2880068b..2938f3f00b9ce 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/README +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/README @@ -181,3 +181,6 @@ new reports must converge monotonically to the last user target in both directions. Report observations cannot restart a fade or request another write. The callback cases exercise the desktop D202/D454 wire adapters, physical resource mapping and release, and unknown-callback failure followed by a successful set. + +Panel identity tests also retain the EDID and generation across repeated and +partial dictionaries, and notify only when valid identity data actually changes. diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py index 2d9501f3eaa98..a89bbf9804a85 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py @@ -423,11 +423,20 @@ def panel_identity(root, output=None): /* The encoded timing clock rounds to 10 kHz; the DCP catalog is unchanged. */ assert(modes[1].mode.clock==540005 && modes[0].mode.clock==1080005); drm_edid_free(edid); - dcp_panel_metadata_replace(&d,NULL); - assert(!dcp_panel_metadata_current(&d,generation)); - assert(IS_ERR(dcp_panel_edid_snapshot(&d,modes,3,&generation))); + /* Repeated and partial dictionaries cannot trigger monitor/brightness restore loops. */ + for(unsigned repeat=0;repeat<32;repeat++) { + dcp_panel_metadata_replace(&d,NULL); + dcp_panel_metadata_replace(&d,&metadata); + assert(dcp_panel_metadata_current(&d,generation) && notifications==1); + } + metadata.serial++; + dcp_panel_metadata_replace(&d,&metadata); + assert(!dcp_panel_metadata_current(&d,generation) && notifications==2); + edid=dcp_panel_edid_snapshot(&d,modes,3,&generation); + assert(!IS_ERR(edid) && dcp_panel_metadata_current(&d,generation)); + drm_edid_free(edid); /* Another firmware/profile cannot publish a desktop panel identity. */ - d.fw_compat=2;dcp_panel_metadata_replace(&d,&metadata); + desktop=(__typeof__(desktop)){0};d.fw_compat=2;dcp_panel_metadata_replace(&d,&metadata); assert(!desktop.panel_metadata_valid); metadata.manufacturer[0]='?';assert(IS_ERR(dcp_panel_edid(&metadata,modes,3,345,223))); return 0; From f840284506bf557e52009a137ef1e04f95537b73 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 10:17:38 +1100 Subject: [PATCH 029/184] drm/apple: receive panel identity through dictionary publications The desktop dictionary setters previously acknowledged every property without retaining its data. Decode DisplayAttributes and direct ProductAttributes on the fixed panel, alongside the existing chunked path. Bound their input records and preserve the existing boolean acknowledgment, including byte and padded results, for null or unrelated properties. Accept a legacy PNP manufacturer code and derive a missing model label from the actual vendor/product IDs. Keep unspecified serial and label fields from erasing known data for the same fixed panel. Optional manufacture fields do not control identity parsing. Extend the host case through the actual D552/D561 bindings, with both dictionary forms, minimal identity, null and unrelated setters, reply canaries and partial updates. External metadata parsing and the contributor DMA callbacks remain unchanged. Assisted-by: LLM Signed-off-by: Ryan Murray --- Documentation/gpu/apple-dcp-t6041-panel.rst | 6 +++ drivers/gpu/drm/apple/dcp-display-metadata.c | 5 +- drivers/gpu/drm/apple/dcp.c | 25 ++++++++-- drivers/gpu/drm/apple/iomfb_v26_6_desktop.c | 38 +++++++++++++- drivers/gpu/drm/apple/parser-test.c | 6 +++ drivers/gpu/drm/apple/parser.c | 26 ++++++++-- drivers/gpu/drm/apple/parser.h | 2 + .../selftests/drivers/gpu/apple-dcp/README | 4 ++ .../drivers/gpu/apple-dcp/desktop.py | 50 ++++++++++++++++++- 9 files changed, 150 insertions(+), 12 deletions(-) diff --git a/Documentation/gpu/apple-dcp-t6041-panel.rst b/Documentation/gpu/apple-dcp-t6041-panel.rst index 94ddee52d02aa..01a03c0f2777d 100644 --- a/Documentation/gpu/apple-dcp-t6041-panel.rst +++ b/Documentation/gpu/apple-dcp-t6041-panel.rst @@ -66,3 +66,9 @@ the firmware replies; another set cannot overwrite its stack. Repeated or partial DisplayAttributes retain a validated fixed-panel identity. They do not emit another identity hotplug event or revoke the EDID. + +Identity accepts both chunked DisplayAttributes and the desktop dictionary +setters. A legacy PNP manufacturer code is decoded when the text code is absent. +Missing optional labels are represented by the firmware vendor/product code and +unspecified numeric serial, without inventing a per-device identity. Partial +updates for the same vendor/product retain known optional identity fields. diff --git a/drivers/gpu/drm/apple/dcp-display-metadata.c b/drivers/gpu/drm/apple/dcp-display-metadata.c index 8819f65cb8655..94cc5ec03417f 100644 --- a/drivers/gpu/drm/apple/dcp-display-metadata.c +++ b/drivers/gpu/drm/apple/dcp-display-metadata.c @@ -231,7 +231,7 @@ const struct drm_edid *dcp_panel_edid(const struct dcp_display_metadata *metadat struct detailed_non_pixel *limits = &block.detailed_timings[3].data.other_data; struct detailed_data_monitor_range *range = &limits->data.range; const struct drm_edid *edid; - char serial[14]; + char serial[14], name[14]; u32 min_v = U32_MAX, max_v = 0, min_h = U32_MAX, max_h = 0, clock = 0; u16 manufacturer = 0; u8 sum = 0; @@ -295,8 +295,9 @@ const struct drm_edid *dcp_panel_edid(const struct dcp_display_metadata *metadat block.gamma = 0xff; block.features = DRM_EDID_FEATURE_PREFERRED_TIMING; memset(block.standard_timings, 1, sizeof(block.standard_timings)); + snprintf(name, sizeof(name), "%.3s %04x", metadata->manufacturer, metadata->product); ret = dcp_metadata_text(&block.detailed_timings[1], EDID_DETAIL_MONITOR_NAME, - metadata->name); + metadata->name[0] ? metadata->name : name); if (ret) return ERR_PTR(ret); snprintf(serial, sizeof(serial), "%08x", metadata->serial); diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 5f3901acba4de..239d7506183b8 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -780,6 +780,7 @@ const struct drm_edid *dcp_firmware_edid(struct apple_dcp *dcp, u64 *generation) void dcp_panel_metadata_replace(struct apple_dcp *dcp, const struct dcp_display_metadata *metadata) { + struct dcp_display_metadata merged; unsigned long flags; bool changed = false; @@ -787,10 +788,28 @@ void dcp_panel_metadata_replace(struct apple_dcp *dcp, !dcp_has_panel(dcp)) return; spin_lock_irqsave(&dcp->metadata.lock, flags); - /* Partial or repeated attributes cannot revoke this fixed panel's identity. */ + merged = *metadata; + if (dcp->desktop->panel_metadata_valid && + merged.product == dcp->desktop->panel_metadata.product && + !memcmp(merged.manufacturer, dcp->desktop->panel_metadata.manufacturer, 4)) { + const struct dcp_display_metadata *old = &dcp->desktop->panel_metadata; + bool same_serial = !merged.serial || merged.serial == old->serial; + + /* Zero/empty optional fields mean unspecified, not a new panel. */ + if (!merged.name[0]) + memcpy(merged.name, old->name, sizeof(merged.name)); + if (!merged.serial) + merged.serial = old->serial; + if (same_serial && !merged.serial_text[0]) + memcpy(merged.serial_text, old->serial_text, sizeof(merged.serial_text)); + if (!merged.year) + merged.year = old->year; + if (!merged.week) + merged.week = old->week; + } if (!dcp->desktop->panel_metadata_valid || - memcmp(&dcp->desktop->panel_metadata, metadata, sizeof(*metadata))) { - dcp->desktop->panel_metadata = *metadata; + memcmp(&dcp->desktop->panel_metadata, &merged, sizeof(merged))) { + dcp->desktop->panel_metadata = merged; dcp->desktop->panel_metadata_valid = true; dcp->desktop->panel_metadata_generation++; changed = true; diff --git a/drivers/gpu/drm/apple/iomfb_v26_6_desktop.c b/drivers/gpu/drm/apple/iomfb_v26_6_desktop.c index ed7f5faadf0f6..033f915ff9f5f 100644 --- a/drivers/gpu/drm/apple/iomfb_v26_6_desktop.c +++ b/drivers/gpu/drm/apple/iomfb_v26_6_desktop.c @@ -91,6 +91,37 @@ static bool trampoline_passthrough_dict_v26_6(struct apple_dcp *dcp, int tag, return true; } +/* The firmware can publish panel identity through dictionary setters too. */ +static bool trampoline_panel_dictionary_v26_6(struct apple_dcp *dcp, int tag, + void *out, void *in) +{ + const struct dcp_dict_passthrough_req_v26_6 *req = in; + const char *key = (const char *)req->key; + struct dcp_display_metadata metadata; + struct dcp_parse_ctx ctx; + int ret; + + trace_iomfb_callback(dcp, tag, __func__); + if (!dcp_has_panel(dcp) || req->value_null || + !memchr(key, 0, sizeof(req->key))) + goto done; + if (strcmp(key, "DisplayAttributes") && strcmp(key, "ProductAttributes")) + goto done; + ret = parse(req->value, sizeof(req->value), &ctx); + if (ret) + goto done; + if (!strcmp(key, "ProductAttributes")) + ret = parse_panel_product_metadata(&ctx, &metadata); + else + ret = parse_panel_metadata(&ctx, &metadata); + if (!ret) + dcp_panel_metadata_replace(dcp, &metadata); +done: + /* Preserve the existing setter acknowledgment for unrelated properties. */ + *(u8 *)out = true; + return true; +} + bool iomfb_check_callback_v26_6_desktop(int tag, u32 in_len, u32 out_len) { switch (tag) { @@ -104,6 +135,9 @@ bool iomfb_check_callback_v26_6_desktop(int tag, u32 in_len, u32 out_len) return in_len == 24 && out_len == 20; case 454: return in_len == 4 && out_len == 4; + case 552: + case 561: + return in_len == 4164 && (out_len == 1 || out_len == 4); default: return true; } @@ -198,8 +232,8 @@ static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = { [451] = trampoline_allocate_buffer, [452] = trampoline_map_physical, [454] = trampoline_release_mem_desc_v26_6, - [552] = trampoline_true, /* set_property_dict_0 */ - [561] = trampoline_true, /* set_property_dict */ + [552] = trampoline_panel_dictionary_v26_6, + [561] = trampoline_panel_dictionary_v26_6, [563] = trampoline_true, /* set_property_int */ [565] = trampoline_true, /* set_property_bool */ [567] = trampoline_true, /* set_property_str */ diff --git a/drivers/gpu/drm/apple/parser-test.c b/drivers/gpu/drm/apple/parser-test.c index aeea21b07e5eb..459de4aaecda9 100644 --- a/drivers/gpu/drm/apple/parser-test.c +++ b/drivers/gpu/drm/apple/parser-test.c @@ -1379,7 +1379,13 @@ static void parser_panel_identity_stable(struct kunit *test) KUNIT_EXPECT_TRUE(test, dcp_panel_metadata_current(dcp, generation)); dcp_panel_metadata_replace(dcp, NULL); dcp_panel_metadata_replace(dcp, &metadata); + { + struct dcp_display_metadata partial = { .manufacturer = "TST", .product = 42 }; + + dcp_panel_metadata_replace(dcp, &partial); + } KUNIT_EXPECT_TRUE(test, dcp_panel_metadata_current(dcp, generation)); + KUNIT_EXPECT_EQ(test, desktop->panel_metadata.serial, 1U); metadata.serial++; dcp_panel_metadata_replace(dcp, &metadata); KUNIT_EXPECT_FALSE(test, dcp_panel_metadata_current(dcp, generation)); diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index 1de2fa263a865..fb16cee1cad22 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -1328,7 +1328,7 @@ static int parse_panel_label(struct dcp_parse_ctx *handle, char out[14]) if (IS_ERR(tag)) return PTR_ERR(tag); - if (!tag->size || tag->size > 255) + if (tag->size > 255) return -EINVAL; value = parse_bytes(handle, tag->size); if (IS_ERR(value)) @@ -1381,12 +1381,14 @@ static int parse_metadata_product(struct dcp_parse_ctx *handle, if (seen & 8) return -EINVAL; seen |= 8; - ret = parse_metadata_number(handle, &out->week, 0, 53); + ret = panel ? skip(handle) : + parse_metadata_number(handle, &out->week, 0, 53); } else if (!strcmp(key, "YearOfManufacture")) { if (seen & 16) return -EINVAL; seen |= 16; - ret = parse_metadata_number(handle, &out->year, 1990, 2245); + ret = panel ? skip(handle) : + parse_metadata_number(handle, &out->year, 1990, 2245); } else if (!strcmp(key, "SerialNumber")) { if (seen & 64) return -EINVAL; @@ -1410,7 +1412,12 @@ static int parse_metadata_product(struct dcp_parse_ctx *handle, if (ret) return ret; } - if ((seen & (panel ? 7 : 31)) != (panel ? 7 : 31) || + if (panel && !(seen & 1) && (seen & 32)) { + for (unsigned int i = 0; i < 3; i++) + out->manufacturer[i] = '@' + ((legacy >> (10 - 5 * i)) & 31); + seen |= 1; + } + if ((seen & (panel ? 5 : 31)) != (panel ? 5 : 31) || strlen(out->manufacturer) != 3) return -EINVAL; for (unsigned int i = 0; i < 3; i++) @@ -1476,6 +1483,17 @@ int parse_display_metadata(struct dcp_parse_ctx *handle, struct dcp_display_meta } /* Identity does not depend on optional colorimetry or EDID-compatible timing. */ +int parse_panel_product_metadata(struct dcp_parse_ctx *handle, + struct dcp_display_metadata *out) +{ + struct dcp_display_metadata parsed = {}; + int ret = parse_metadata_product(handle, &parsed, true); + + if (!ret) + *out = parsed; + return ret; +} + int parse_panel_metadata(struct dcp_parse_ctx *handle, struct dcp_display_metadata *out) { struct dcp_display_metadata parsed = {}; diff --git a/drivers/gpu/drm/apple/parser.h b/drivers/gpu/drm/apple/parser.h index c88f4dd21b855..fecec2656201e 100644 --- a/drivers/gpu/drm/apple/parser.h +++ b/drivers/gpu/drm/apple/parser.h @@ -197,6 +197,8 @@ int replace_modes(struct dcp_parse_ctx *handle, int width_mm, int height_mm, unsigned int notch_height, bool internal); int parse_panel_metadata(struct dcp_parse_ctx *handle, struct dcp_display_metadata *out); +int parse_panel_product_metadata(struct dcp_parse_ctx *handle, + struct dcp_display_metadata *out); int parse_display_attributes(struct dcp_parse_ctx *handle, int *width_mm, int *height_mm, bool *backlight_control); int parse_epic_service_init(struct dcp_parse_ctx *handle, const char **name, diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/README b/tools/testing/selftests/drivers/gpu/apple-dcp/README index 2938f3f00b9ce..51c7c0db9f83f 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/README +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/README @@ -184,3 +184,7 @@ mapping and release, and unknown-callback failure followed by a successful set. Panel identity tests also retain the EDID and generation across repeated and partial dictionaries, and notify only when valid identity data actually changes. + +The identity case also exercises the real D552/D561 table bindings, nested +DisplayAttributes and direct ProductAttributes, legacy manufacturer codes, +minimal identity data, and unchanged byte/padded boolean acknowledgments. diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py index a89bbf9804a85..6216f713f15b1 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py @@ -368,14 +368,23 @@ def panel_identity(root, output=None): 'ProductName': 'Synthetic Internal Panel', 'SerialNumber': 0x12345678}} raw = bytearray(struct.pack('has_panel;} static void dcp_queue_hotplug(void *con) {assert(con);notifications++;} -''' + helpers + '\nstatic const u8 attributes[]={'+','.join(map(str,raw))+'};\n' + r''' +''' + helpers + dictionary + '\nstatic const u8 attributes[]={'+','.join(map(str,raw))+'};\n' + 'static const u8 product[]={'+','.join(map(str,product))+'};\n' + 'static const u8 minimal[]={'+','.join(map(str,minimal))+'};\n' + r''' int main(int argc, char **argv) { struct dcp_parse_ctx ctx; @@ -406,6 +415,41 @@ def panel_identity(root, output=None): modes[0].vrr=true;modes[0].min_vrr=24U<<16;modes[0].max_vrr=120U<<16; u64 generation=99; assert(IS_ERR(dcp_panel_edid_snapshot(&d,modes,3,&generation))); + bool (*handlers[600])(struct apple_dcp *,int,void *,void *)={ +''' + bindings + r''' + }; + for(unsigned form=0;form<3;form++) { + desktop=(__typeof__(desktop)){0};notifications=0; + struct dcp_dict_passthrough_req_v26_6 wire={0};u32 result=0; + strcpy((char *)wire.key,form?"ProductAttributes":"DisplayAttributes"); + const u8 *data=form==2?minimal:form?product:attributes; + size_t size=form==2?sizeof(minimal):form?sizeof(product):sizeof(attributes); + memcpy(wire.value,data,size); + unsigned tag=form?552:561; + assert(iomfb_check_callback_v26_6_desktop(tag,sizeof(wire),4)); + assert(iomfb_check_callback_v26_6_desktop(tag,sizeof(wire),1)); + assert(!iomfb_check_callback_v26_6_desktop(tag,sizeof(wire)-1,4)); + u8 short_reply[2]={0,0xa5}; + assert(handlers[tag](&d,tag,short_reply,&wire)); + assert(short_reply[0]==1 && short_reply[1]==0xa5); + assert(handlers[tag](&d,tag,&result,&wire) && result==1); + assert(desktop.panel_metadata_valid && notifications==1); + assert(desktop.panel_metadata.serial==(form==2?0:metadata.serial)); + assert(!strcmp(desktop.panel_metadata.manufacturer,"TST")); + if(form==2) { + const struct drm_edid *minimal_edid=dcp_panel_edid_snapshot(&d,modes,3,&generation); + assert(!IS_ERR(minimal_edid) && drm_edid_valid(minimal_edid)); + const u8 *m=(const void *)&minimal_edid->raw; + assert(!memcmp(m+72+5,"TST 002a",8) && !memcmp(m+90+5,"00000000",8)); + drm_edid_free(minimal_edid); + } + /* Null and unrelated setters preserve the existing ACK and identity. */ + wire.value_null=1;result=0; + assert(handlers[tag](&d,tag,&result,&wire) && result==1 && notifications==1); + wire.value_null=0;strcpy((char *)wire.key,"Unrelated");result=0; + assert(handlers[tag](&d,tag,&result,&wire) && result==1 && notifications==1); + } + desktop=(__typeof__(desktop)){0};notifications=0; dcp_panel_metadata_replace(&d,&metadata); assert(notifications==1 && desktop.panel_metadata_valid); const struct drm_edid *edid=dcp_panel_edid_snapshot(&d,modes,3,&generation); @@ -429,6 +473,10 @@ def panel_identity(root, output=None): dcp_panel_metadata_replace(&d,&metadata); assert(dcp_panel_metadata_current(&d,generation) && notifications==1); } + struct dcp_display_metadata partial={.manufacturer="TST",.product=42}; + dcp_panel_metadata_replace(&d,&partial); + assert(dcp_panel_metadata_current(&d,generation) && notifications==1); + assert(desktop.panel_metadata.serial==metadata.serial); metadata.serial++; dcp_panel_metadata_replace(&d,&metadata); assert(!dcp_panel_metadata_current(&d,generation) && notifications==2); From c19d5c120633410a287c875f6d0352120d92b763 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 11:42:19 +1100 Subject: [PATCH 030/184] drm/apple: inspect desktop panel property keys behind a debug switch The desktop dictionary setters run without supplying the identity keys we expected. Add opt-in structural inspection to D552, D561, D114 and chunked properties. Bound the traversal by depth and entries and the output per DCP, and emit keys and types only. The default stays silent. Exercise malformed, truncated and oversized dictionaries on the host and in KUnit. No captured firmware values are included. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/gpu/drm/apple/dcp-internal.h | 5 ++ drivers/gpu/drm/apple/iomfb.c | 41 +++++++++++++ drivers/gpu/drm/apple/iomfb_template.c | 3 + drivers/gpu/drm/apple/iomfb_v26_6_desktop.c | 5 ++ drivers/gpu/drm/apple/parser-test.c | 22 +++++++ drivers/gpu/drm/apple/parser.c | 58 +++++++++++++++++++ drivers/gpu/drm/apple/parser.h | 3 + .../drivers/gpu/apple-dcp/desktop.py | 31 ++++++++++ .../selftests/drivers/gpu/apple-dcp/run.py | 1 + 9 files changed, 169 insertions(+) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index f8cbb94b6951e..37f6228dd7af9 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -247,6 +247,7 @@ struct dcp_desktop_state { struct dcp_display_metadata panel_metadata; u64 panel_metadata_generation; bool panel_metadata_valid; + u32 identity_log_count; /* RX records an unsupported callback until the modeset RPC has drained. */ bool modeset_active; bool modeset_callback_error; @@ -892,4 +893,8 @@ static inline bool dcp_ext_dptx_607(const struct apple_dcp *dcp) #define DCP_AUDIO_MAX_CHANS 15 + +void dcp_trace_panel_property(struct apple_dcp *dcp, int tag, const char *key, + size_t key_len, const void *blob, size_t len); + #endif /* __APPLE_DCP_INTERNAL_H__ */ diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 510ef60e8e592..5a67d1801af4e 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -40,6 +40,47 @@ #include "parser.h" #include "trace.h" +/* Enable for one diagnostic boot with apple_drm.panel_identity_debug=1. */ +static bool panel_identity_debug; +module_param(panel_identity_debug, bool, 0600); +MODULE_PARM_DESC(panel_identity_debug, "Bounded desktop panel property keys and types (no values)"); + +static void dcp_identity_key_log(void *data, const char *key, u32 key_len, + u32 type, u32 size, u32 depth) +{ + struct apple_dcp *dcp = data; + char safe[65]; + u32 i; + + if (dcp->desktop->identity_log_count >= 256) + return; + for (i = 0; i < min_t(u32, key_len, sizeof(safe) - 1); i++) + safe[i] = key[i] >= 0x20 && key[i] <= 0x7e ? key[i] : '?'; + safe[i] = 0; + dcp->desktop->identity_log_count++; + dev_info(dcp->dev, "panel identity depth=%u key=%s type=%u size=%u\n", + depth, safe, type, size); +} + +void dcp_trace_panel_property(struct apple_dcp *dcp, int tag, const char *key, + size_t key_len, const void *blob, size_t len) +{ + int ret; + + if (!READ_ONCE(panel_identity_debug) || + dcp->fw_compat != DCP_FIRMWARE_V_26_6_DESKTOP || + !dcp_has_panel(dcp) || dcp->desktop->identity_log_count >= 256) + return; + dev_info(dcp->dev, "panel identity callback=D%03d bytes=%zu null=%d\n", + tag, len, !blob); + dcp_identity_key_log(dcp, key, key_len, 0, len, 0); + if (!blob) + return; + ret = parse_identity_keys(blob, len, dcp_identity_key_log, dcp); + if (ret) + dev_info(dcp->dev, "panel identity inspection stopped: %d\n", ret); +} + static int dcp_tx_offset(enum dcp_context_id id) { switch (id) { diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 0a735158ceda8..e34af2a47e868 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -1264,6 +1264,9 @@ static bool dcpep_process_chunks(struct apple_dcp *dcp, dcp_property_complete(dcp->chunks.data, dcp->chunks.received, dcp->chunks.length); + dcp_trace_panel_property(dcp, 0, req->key, + strnlen(req->key, sizeof(req->key)), dcp->chunks.data, dcp->chunks.length); + ret = parse(dcp->chunks.data, dcp->chunks.length, &ctx); if (ret) { diff --git a/drivers/gpu/drm/apple/iomfb_v26_6_desktop.c b/drivers/gpu/drm/apple/iomfb_v26_6_desktop.c index 033f915ff9f5f..bd8eae4ee6716 100644 --- a/drivers/gpu/drm/apple/iomfb_v26_6_desktop.c +++ b/drivers/gpu/drm/apple/iomfb_v26_6_desktop.c @@ -81,6 +81,9 @@ static bool trampoline_passthrough_dict_v26_6(struct apple_dcp *dcp, int tag, struct dcp_dict_passthrough_resp_v26_6 *resp = out; trace_iomfb_callback(dcp, tag, "dcpep_cb_passthrough_dict"); + dcp_trace_panel_property(dcp, tag, (const char *)req->key, + strnlen((const char *)req->key, sizeof(req->key)), + req->value_null ? NULL : req->value, sizeof(req->value)); if (!req->value_null) memcpy(resp->value, req->value, sizeof(resp->value)); @@ -102,6 +105,8 @@ static bool trampoline_panel_dictionary_v26_6(struct apple_dcp *dcp, int tag, int ret; trace_iomfb_callback(dcp, tag, __func__); + dcp_trace_panel_property(dcp, tag, key, strnlen(key, sizeof(req->key)), + req->value_null ? NULL : req->value, sizeof(req->value)); if (!dcp_has_panel(dcp) || req->value_null || !memchr(key, 0, sizeof(req->key))) goto done; diff --git a/drivers/gpu/drm/apple/parser-test.c b/drivers/gpu/drm/apple/parser-test.c index 459de4aaecda9..108a7ca67b2ef 100644 --- a/drivers/gpu/drm/apple/parser-test.c +++ b/drivers/gpu/drm/apple/parser-test.c @@ -1392,7 +1392,29 @@ static void parser_panel_identity_stable(struct kunit *test) KUNIT_EXPECT_EQ(test, desktop->panel_metadata.serial, 2U); } +static void parser_identity_emit(void *data, const char *key, u32 len, + u32 type, u32 size, u32 depth) +{ + u32 *seen = data; + + (*seen)++; +} + +static void parser_identity_bounds(struct kunit *test) +{ + u32 seen = 0; + __le32 huge[] = { cpu_to_le32(0xd3), cpu_to_le32(0x01ffffff) }; + + KUNIT_EXPECT_EQ(test, parse_identity_keys(huge, sizeof(huge), + parser_identity_emit, &seen), -E2BIG); + KUNIT_EXPECT_EQ(test, seen, 0U); + for (u32 len = 0; len < sizeof(huge); len++) + KUNIT_EXPECT_LT(test, parse_identity_keys(huge, len, + parser_identity_emit, &seen), 0); +} + static struct kunit_case parser_cases[] = { + KUNIT_CASE(parser_identity_bounds), KUNIT_CASE(parser_panel_identity_stable), KUNIT_CASE(parser_panel_identity), KUNIT_CASE(parser_score_presence), diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index fb16cee1cad22..54b9606a4f632 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -2003,3 +2003,61 @@ int parse_system_log_mnits(struct dcp_parse_ctx *handle, struct dcp_system_ev_mn return 0; } + +/* Inspect structure only. Never emit a firmware value or allocate from its size. */ +static int parse_identity_tree(struct dcp_parse_ctx *ctx, dcp_identity_key_fn emit, + void *data, u32 depth, u32 *budget) +{ + const struct dcp_parse_tag *tag; + u32 count; + + if (!*budget || depth > 4) + return -E2BIG; + --*budget; + tag = parse_tag(ctx); + if (IS_ERR(tag)) + return PTR_ERR(tag); + if (tag->type != DCP_TYPE_DICTIONARY && tag->type != DCP_TYPE_ARRAY) { + /* Reuse the bounded scalar decoder from the original position. */ + ctx->pos -= sizeof(*tag); + return skip(ctx); + } + count = tag->size; + if (count > *budget) + return -E2BIG; + for (u32 i = 0; i < count; i++) { + struct dcp_parse_ctx value; + const struct dcp_parse_tag *key_tag, *value_tag; + const char *key = "[]"; + u32 length = 2; + int ret; + + if (tag->type == DCP_TYPE_DICTIONARY) { + key_tag = parse_tag_of_type(ctx, DCP_TYPE_STRING); + if (IS_ERR(key_tag)) + return PTR_ERR(key_tag); + length = key_tag->size; + key = parse_bytes(ctx, length); + if (IS_ERR(key)) + return PTR_ERR(key); + } + value = *ctx; + value_tag = parse_tag(&value); + if (IS_ERR(value_tag)) + return PTR_ERR(value_tag); + emit(data, key, length, value_tag->type, value_tag->size, depth); + ret = parse_identity_tree(ctx, emit, data, depth + 1, budget); + if (ret) + return ret; + } + return 0; +} + +int parse_identity_keys(const void *blob, size_t len, dcp_identity_key_fn emit, void *data) +{ + struct dcp_parse_ctx ctx; + u32 budget = 64; + int ret = parse(blob, len, &ctx); + + return ret ?: parse_identity_tree(&ctx, emit, data, 0, &budget); +} diff --git a/drivers/gpu/drm/apple/parser.h b/drivers/gpu/drm/apple/parser.h index fecec2656201e..9a81c0d15ac1b 100644 --- a/drivers/gpu/drm/apple/parser.h +++ b/drivers/gpu/drm/apple/parser.h @@ -11,6 +11,9 @@ #include "dcp-metadata-epoch.h" struct apple_dcp; +typedef void (*dcp_identity_key_fn)(void *data, const char *key, u32 key_len, + u32 type, u32 size, u32 depth); +int parse_identity_keys(const void *blob, size_t len, dcp_identity_key_fn emit, void *data); struct apple_connector; struct apple_dcp_typec_route; diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py index 6216f713f15b1..9ae0c1808a53a 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py @@ -635,3 +635,34 @@ def keys_and_reports(root): return 0; } ''' + + +def identity_keys(root): + import rate, struct + f = rate.parser_fixture(root) + data = bytearray(struct.pack(' Date: Mon, 26 Oct 2026 11:44:03 +1100 Subject: [PATCH 031/184] drm/apple: give the desktop panel an ADT identity fallback Retain a validated real EDID from dictionary or chunked EDID setters when provided. Otherwise use panel identity from the reserved ADT, with the OF panel dimensions and a generic Apple panel identity if fields are absent. This removes the dependency on ProductAttributes arriving before an EDID can be published. Keep exact DRM modes from the DCP catalog. Read only bounded panel properties from the bootloader's reserved ADT. Hash panel serial bytes into the EDID numeric serial and never use the machine serial. Restrict the reader and fallback to desktop 26.6, leaving T8140 on its existing path. Cover malformed ADT, missing firmware identity, real EDID priority and invalid replacement retention in host tests/KUnit. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/gpu/drm/apple/Makefile | 2 +- drivers/gpu/drm/apple/dcp-internal.h | 5 + drivers/gpu/drm/apple/dcp-panel-adt.c | 143 ++++++++++++++++++ drivers/gpu/drm/apple/dcp-panel-adt.h | 15 ++ drivers/gpu/drm/apple/dcp.c | 110 ++++++++++++-- drivers/gpu/drm/apple/iomfb_template.c | 5 + drivers/gpu/drm/apple/iomfb_v26_6_desktop.c | 5 + drivers/gpu/drm/apple/parser-test.c | 13 ++ drivers/gpu/drm/apple/parser.c | 34 +++++ drivers/gpu/drm/apple/parser.h | 1 + .../drivers/gpu/apple-dcp/desktop.py | 84 +++++++++- .../selftests/drivers/gpu/apple-dcp/run.py | 1 + 12 files changed, 401 insertions(+), 17 deletions(-) create mode 100644 drivers/gpu/drm/apple/dcp-panel-adt.c create mode 100644 drivers/gpu/drm/apple/dcp-panel-adt.h diff --git a/drivers/gpu/drm/apple/Makefile b/drivers/gpu/drm/apple/Makefile index a592e776e99ab..48a3bb9009bc7 100644 --- a/drivers/gpu/drm/apple/Makefile +++ b/drivers/gpu/drm/apple/Makefile @@ -7,7 +7,7 @@ appledrm-y += afk.o dcp.o dcp-fabric.o dcp_backlight.o dptxep.o iomfb.o parser.o appledrm-y += dcp-hdmi.o appledrm-$(CONFIG_DRM_APPLE_AUDIO) += audio.o appledrm-$(CONFIG_DRM_APPLE_AUDIO) += av.o -appledrm-y += connector.o dcp-display-metadata.o dcp-180hz.o +appledrm-y += connector.o dcp-panel-adt.o dcp-display-metadata.o dcp-180hz.o appledrm-y += ibootep.o appledrm-y += dcpext_scanout.o appledrm-y += dcpext_drm.o diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 37f6228dd7af9..7cdec3dbf0e52 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -245,9 +245,12 @@ struct dcp_desktop_state { bool mode_live; /* Identity supplied by this fixed panel, independent of optional color data. */ struct dcp_display_metadata panel_metadata; + struct dcp_display_metadata panel_adt_identity; u64 panel_metadata_generation; bool panel_metadata_valid; u32 identity_log_count; + u8 panel_raw_edid[4096]; + size_t panel_raw_edid_size; /* RX records an unsupported callback until the modeset RPC has drained. */ bool modeset_active; bool modeset_callback_error; @@ -897,4 +900,6 @@ static inline bool dcp_ext_dptx_607(const struct apple_dcp *dcp) void dcp_trace_panel_property(struct apple_dcp *dcp, int tag, const char *key, size_t key_len, const void *blob, size_t len); +void dcp_panel_edid_receive(struct apple_dcp *dcp, const void *blob, size_t len); + #endif /* __APPLE_DCP_INTERNAL_H__ */ diff --git a/drivers/gpu/drm/apple/dcp-panel-adt.c b/drivers/gpu/drm/apple/dcp-panel-adt.c new file mode 100644 index 0000000000000..9dbe34e0c2f2c --- /dev/null +++ b/drivers/gpu/drm/apple/dcp-panel-adt.c @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * Read-only Apple device tree decoding. The wire layout is documented by + * m1n1's public src/adt.h (GPL-2.0-or-later OR BSD-2-Clause). No firmware + * implementation or captured device identifiers are used here. + */ +#include +#include +#include +#include +#include + +#include "dcp-panel-adt.h" + +struct dcp_adt_cursor { + const u8 *data; + size_t pos, size; + u32 nodes; +}; + +static const u8 *dcp_adt_bytes(struct dcp_adt_cursor *c, size_t size) +{ + const u8 *p; + + if (c->pos > c->size || size > c->size - c->pos) + return ERR_PTR(-EINVAL); + p = c->data + c->pos; + c->pos += size; + return p; +} + +/* Hash panel serial bytes into EDID's numeric serial; never log the input. */ +static u32 dcp_adt_serial(const u8 *data, size_t size) +{ + u32 serial = 2166136261U; + + for (size_t i = 0; i < size; i++) + serial = (serial ^ data[i]) * 16777619U; + return serial ?: 1; +} + +static void dcp_adt_panel_property(struct dcp_panel_adt *panel, const char *name, + const u8 *value, u32 size) +{ + struct dcp_display_metadata *id = &panel->identity; + u32 number = size == 4 ? get_unaligned_le32(value) : 0; + + if ((!strcmp(name, "vendor-id") || !strcmp(name, "display-vendor-id")) && + size == 4 && number <= 0x7fff) { + char manufacturer[4] = { (number >> 10 & 31) + '@', + (number >> 5 & 31) + '@', (number & 31) + '@', 0 }; + + if (manufacturer[0] >= 'A' && manufacturer[0] <= 'Z' && + manufacturer[1] >= 'A' && manufacturer[1] <= 'Z' && + manufacturer[2] >= 'A' && manufacturer[2] <= 'Z') + memcpy(id->manufacturer, manufacturer, sizeof(manufacturer)); + } else if ((!strcmp(name, "product-id") || !strcmp(name, "display-product-id") || + !strcmp(name, "panel-id")) && size == 4) { + id->product = number & U16_MAX; + } else if ((!strcmp(name, "serial-number") || + !strcmp(name, "raw-panel-serial-number")) && size && size <= 256) { + id->serial = dcp_adt_serial(value, size); + } else if (!strcmp(name, "display-serial-number") && size == 4) { + id->serial = number; + } else if (!strcmp(name, "width-mm") && size == 4 && number && number <= 2550) { + panel->width_mm = number; + } else if (!strcmp(name, "height-mm") && size == 4 && number && number <= 2550) { + panel->height_mm = number; + } +} + +static int dcp_adt_node(struct dcp_adt_cursor *c, struct dcp_panel_adt *panel, + u32 depth, bool product_parent) +{ + const u8 *header = dcp_adt_bytes(c, 8); + size_t props_start, props_end; + u32 props, children; + char node_name[33] = {}; + bool product, selected; + + if (IS_ERR(header)) + return PTR_ERR(header); + if (depth > 32 || ++c->nodes > 4096) + return -E2BIG; + props = get_unaligned_le32(header); + children = get_unaligned_le32(header + 4); + if (props > 4096 || children > 4096) + return -E2BIG; + props_start = c->pos; + /* Names need not be the first property. Validate every extent first. */ + for (u32 i = 0; i < props; i++) { + const u8 *prop = dcp_adt_bytes(c, 36), *value; + u32 size; + + if (IS_ERR(prop) || !memchr(prop, 0, 32)) + return -EINVAL; + size = get_unaligned_le32(prop + 32) & 0x7fffffff; + value = dcp_adt_bytes(c, ALIGN((size_t)size, 4)); + if (IS_ERR(value)) + return PTR_ERR(value); + if (!strcmp((const char *)prop, "name") && size && size <= 32 && + memchr(value, 0, size)) + memcpy(node_name, value, size); + } + props_end = c->pos; + product = depth == 1 && !strcmp(node_name, "product"); + selected = (depth == 1 && !strcmp(node_name, "panel")) || + (product_parent && !strcmp(node_name, "panel")); + /* /product carries raw-panel-serial-number on machines without /panel. */ + c->pos = props_start; + for (u32 i = 0; i < props; i++) { + const u8 *prop = c->data + c->pos; + u32 size = get_unaligned_le32(prop + 32) & 0x7fffffff; + const char *name = (const char *)prop; + + if (selected || (product && !strcmp(name, "raw-panel-serial-number"))) + dcp_adt_panel_property(panel, name, prop + 36, size); + c->pos += 36 + ALIGN((size_t)size, 4); + } + panel->found |= selected; + c->pos = props_end; + for (u32 i = 0; i < children; i++) { + int ret = dcp_adt_node(c, panel, depth + 1, product); + + if (ret) + return ret; + } + return 0; +} + +int dcp_panel_adt_parse(const void *data, size_t size, struct dcp_panel_adt *panel) +{ + struct dcp_adt_cursor cursor = { .data = data, .size = size }; + struct dcp_panel_adt parsed = *panel; + int ret; + + if (!data || size > SZ_16M) + return -EINVAL; + ret = dcp_adt_node(&cursor, &parsed, 0, false); + if (!ret) + *panel = parsed; + return ret; +} diff --git a/drivers/gpu/drm/apple/dcp-panel-adt.h b/drivers/gpu/drm/apple/dcp-panel-adt.h new file mode 100644 index 0000000000000..2d724adb7d0e1 --- /dev/null +++ b/drivers/gpu/drm/apple/dcp-panel-adt.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0-only OR MIT */ +#ifndef APPLE_DCP_PANEL_ADT_H +#define APPLE_DCP_PANEL_ADT_H + +#include "dcp-display-metadata.h" + +/* Only panel properties, never the machine or account identity. */ +struct dcp_panel_adt { + struct dcp_display_metadata identity; + u32 width_mm, height_mm; + bool found; +}; + +int dcp_panel_adt_parse(const void *data, size_t size, struct dcp_panel_adt *panel); +#endif diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 239d7506183b8..b4eb0511e82ce 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -51,6 +51,10 @@ #include "afk.h" #include "av.h" #include "dcp.h" +#include "dcp-panel-adt.h" +#include +#include +#include #include "dcp-fabric-effects.h" #include "dcp-fabric.h" #include "dcp-hdmi.h" @@ -776,6 +780,48 @@ const struct drm_edid *dcp_firmware_edid(struct apple_dcp *dcp, u64 *generation) return edid; } +/* + * m1n1 reserves the original ADT as a phram node labelled "adt". Read it + * without an MTD dependency. Only the desktop panel calls this; other DCP + * profiles retain their established identity and brightness paths. + */ +static void dcp_panel_adt_identity(struct apple_dcp *dcp) +{ + struct dcp_panel_adt panel = { + .identity = { .manufacturer = "APP", .product = 0x0616 }, + .width_mm = dcp->panel.width_mm, + .height_mm = dcp->panel.height_mm, + }; + struct device_node *np; + const char *label; + struct resource res; + void *data; + int ret = -ENODATA; + + /* Explicit generic label when no serial or product is provided. */ + strscpy(panel.identity.name, "Apple Panel", sizeof(panel.identity.name)); + for_each_compatible_node(np, NULL, "phram") { + if (of_property_read_string(np, "label", &label) || strcmp(label, "adt")) + continue; + if (of_address_to_resource(np, 0, &res) || + !resource_size(&res) || resource_size(&res) > SZ_16M) + break; + data = memremap(res.start, resource_size(&res), MEMREMAP_WB); + if (!data) + break; + ret = dcp_panel_adt_parse(data, resource_size(&res), &panel); + memunmap(data); + break; + } + of_node_put(np); + /* The board's OF panel dimensions remain valid if ADT has none. */ + dcp->panel.width_mm = panel.width_mm; + dcp->panel.height_mm = panel.height_mm; + dcp->desktop->panel_adt_identity = panel.identity; + dev_info(dcp->dev, "panel identity fallback: ADT %s, OF dimensions %ux%u mm\n", + ret ? "unavailable" : "read", panel.width_mm, panel.height_mm); +} + /* Internal panel identity has no detachable Type-C service or EDID transport. */ void dcp_panel_metadata_replace(struct apple_dcp *dcp, const struct dcp_display_metadata *metadata) @@ -819,14 +865,46 @@ void dcp_panel_metadata_replace(struct apple_dcp *dcp, dcp_queue_hotplug(dcp->connector); } +/* Preserve a validated real EDID ahead of any metadata-derived fallback. */ +void dcp_panel_edid_receive(struct apple_dcp *dcp, const void *blob, size_t len) +{ + const struct drm_edid *edid; + const u8 *raw; + size_t size; + unsigned long flags; + bool changed; + + if (dcp->fw_compat != DCP_FIRMWARE_V_26_6_DESKTOP || !dcp_has_panel(dcp) || + parse_panel_edid_data(blob, len, &raw, &size)) + return; + edid = drm_edid_alloc(raw, size); + if (!edid) + return; + if (!drm_edid_valid(edid)) { + drm_edid_free(edid); + return; + } + spin_lock_irqsave(&dcp->metadata.lock, flags); + changed = size != dcp->desktop->panel_raw_edid_size || + memcmp(raw, dcp->desktop->panel_raw_edid, size); + if (changed) { + memcpy(dcp->desktop->panel_raw_edid, raw, size); + dcp->desktop->panel_raw_edid_size = size; + dcp->desktop->panel_metadata_generation++; + } + spin_unlock_irqrestore(&dcp->metadata.lock, flags); + drm_edid_free(edid); + if (changed && dcp->connector) + dcp_queue_hotplug(dcp->connector); +} + bool dcp_panel_metadata_current(struct apple_dcp *dcp, u64 generation) { unsigned long flags; bool valid; spin_lock_irqsave(&dcp->metadata.lock, flags); - valid = dcp->desktop->panel_metadata_valid && - generation == dcp->desktop->panel_metadata_generation; + valid = generation == dcp->desktop->panel_metadata_generation; spin_unlock_irqrestore(&dcp->metadata.lock, flags); return valid; } @@ -837,21 +915,27 @@ const struct drm_edid *dcp_panel_edid_snapshot(struct apple_dcp *dcp, { struct dcp_display_metadata metadata; const struct drm_edid *edid; + u8 *raw; + size_t size; unsigned long flags; - bool valid; + raw = kmalloc(sizeof(dcp->desktop->panel_raw_edid), GFP_KERNEL); + if (!raw) + return ERR_PTR(-ENOMEM); spin_lock_irqsave(&dcp->metadata.lock, flags); - valid = dcp->desktop->panel_metadata_valid; - if (valid) { - metadata = dcp->desktop->panel_metadata; - *generation = dcp->desktop->panel_metadata_generation; - } + size = dcp->desktop->panel_raw_edid_size; + memcpy(raw, dcp->desktop->panel_raw_edid, size); + metadata = dcp->desktop->panel_metadata_valid ? + dcp->desktop->panel_metadata : dcp->desktop->panel_adt_identity; + *generation = dcp->desktop->panel_metadata_generation; spin_unlock_irqrestore(&dcp->metadata.lock, flags); - if (!valid) - return ERR_PTR(-ENODATA); /* The caller supplies the same retained catalog it is publishing. */ - edid = dcp_panel_edid(&metadata, modes, count, - dcp->panel.width_mm, dcp->panel.height_mm); + edid = size ? drm_edid_alloc(raw, size) : + dcp_panel_edid(&metadata, modes, count, + dcp->panel.width_mm, dcp->panel.height_mm); + kfree(raw); + if (!edid) + return ERR_PTR(-ENOMEM); if (!IS_ERR(edid) && !dcp_panel_metadata_current(dcp, *generation)) { drm_edid_free(edid); return ERR_PTR(-ESTALE); @@ -4823,6 +4907,8 @@ static int dcp_comp_bind_pipe(struct device *dev, struct device *main, void *dat of_property_read_u32(panel_np, height_prop[!dcp->notch_height], &dcp->panel.height_mm); + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) + dcp_panel_adt_identity(dcp); of_node_put(panel_np); dcp->fixed_connector_type = DRM_MODE_CONNECTOR_eDP; dcp->connector_type = DRM_MODE_CONNECTOR_eDP; diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index e34af2a47e868..ce638d142171e 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -1267,6 +1267,11 @@ static bool dcpep_process_chunks(struct apple_dcp *dcp, dcp_trace_panel_property(dcp, 0, req->key, strnlen(req->key, sizeof(req->key)), dcp->chunks.data, dcp->chunks.length); + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && + (!strcmp(req->key, "EDID") || !strcmp(req->key, "edid"))) { + dcp_panel_edid_receive(dcp, dcp->chunks.data, dcp->chunks.length); + return true; + } ret = parse(dcp->chunks.data, dcp->chunks.length, &ctx); if (ret) { diff --git a/drivers/gpu/drm/apple/iomfb_v26_6_desktop.c b/drivers/gpu/drm/apple/iomfb_v26_6_desktop.c index bd8eae4ee6716..8068e0c25e395 100644 --- a/drivers/gpu/drm/apple/iomfb_v26_6_desktop.c +++ b/drivers/gpu/drm/apple/iomfb_v26_6_desktop.c @@ -110,6 +110,11 @@ static bool trampoline_panel_dictionary_v26_6(struct apple_dcp *dcp, int tag, if (!dcp_has_panel(dcp) || req->value_null || !memchr(key, 0, sizeof(req->key))) goto done; + /* A real EDID blob, when supplied, takes precedence over product data. */ + if (!strcmp(key, "EDID") || !strcmp(key, "edid")) { + dcp_panel_edid_receive(dcp, req->value, sizeof(req->value)); + goto done; + } if (strcmp(key, "DisplayAttributes") && strcmp(key, "ProductAttributes")) goto done; ret = parse(req->value, sizeof(req->value), &ctx); diff --git a/drivers/gpu/drm/apple/parser-test.c b/drivers/gpu/drm/apple/parser-test.c index 108a7ca67b2ef..d96b29c43ea0a 100644 --- a/drivers/gpu/drm/apple/parser-test.c +++ b/drivers/gpu/drm/apple/parser-test.c @@ -14,6 +14,7 @@ #include "dcp-property.h" #include "iomfb_internal.h" #include "parser.h" +#include "dcp-panel-adt.h" static struct device parser_device = { .init_name = "apple-display-parser-test", @@ -1413,7 +1414,19 @@ static void parser_identity_bounds(struct kunit *test) parser_identity_emit, &seen), 0); } +static void parser_adt_bounds(struct kunit *test) +{ + struct dcp_panel_adt panel = { .identity = { .manufacturer = "APP" } }; + struct dcp_panel_adt before = panel; + __le32 huge[] = { cpu_to_le32(4097), 0 }; + + KUNIT_EXPECT_EQ(test, dcp_panel_adt_parse(huge, sizeof(huge), &panel), -E2BIG); + KUNIT_EXPECT_MEMEQ(test, &panel, &before, sizeof(panel)); + KUNIT_EXPECT_EQ(test, dcp_panel_adt_parse(NULL, 0, &panel), -EINVAL); +} + static struct kunit_case parser_cases[] = { + KUNIT_CASE(parser_adt_bounds), KUNIT_CASE(parser_identity_bounds), KUNIT_CASE(parser_panel_identity_stable), KUNIT_CASE(parser_panel_identity), diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index 54b9606a4f632..0d4e85363e7d2 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -2061,3 +2061,37 @@ int parse_identity_keys(const void *blob, size_t len, dcp_identity_key_fn emit, return ret ?: parse_identity_tree(&ctx, emit, data, 0, &budget); } + +/* A setter may deliver the EDID bytes directly or as an OSSerialize blob. */ +int parse_panel_edid_data(const void *blob, size_t len, const u8 **edid, size_t *size) +{ + struct dcp_parse_ctx ctx; + const struct dcp_parse_tag *tag; + const u8 *data = blob; + u32 bytes; + int ret; + + if (len >= 128 && !memcmp(data, "\x00\xff\xff\xff\xff\xff\xff\x00", 8)) { + bytes = (1U + data[126]) * 128; + if (bytes > len || bytes > 4096) + return -EINVAL; + *edid = data; + *size = bytes; + return 0; + } + ret = parse(blob, len, &ctx); + if (ret) + return ret; + tag = parse_tag_of_type(&ctx, DCP_TYPE_BLOB); + if (IS_ERR(tag) || tag->size < 128 || tag->size > 4096 || tag->size % 128) + return -EINVAL; + data = parse_bytes(&ctx, tag->size); + if (IS_ERR(data)) + return PTR_ERR(data); + if (memcmp(data, "\x00\xff\xff\xff\xff\xff\xff\x00", 8) || + (1U + data[126]) * 128 != tag->size) + return -EINVAL; + *edid = data; + *size = tag->size; + return 0; +} diff --git a/drivers/gpu/drm/apple/parser.h b/drivers/gpu/drm/apple/parser.h index 9a81c0d15ac1b..9b419ce83621b 100644 --- a/drivers/gpu/drm/apple/parser.h +++ b/drivers/gpu/drm/apple/parser.h @@ -11,6 +11,7 @@ #include "dcp-metadata-epoch.h" struct apple_dcp; +int parse_panel_edid_data(const void *blob, size_t len, const u8 **edid, size_t *size); typedef void (*dcp_identity_key_fn)(void *data, const char *key, u32 key_len, u32 type, u32 size, u32 depth); int parse_identity_keys(const void *blob, size_t len, dcp_identity_key_fn emit, void *data); diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py index 9ae0c1808a53a..f45784339437d 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py @@ -48,6 +48,9 @@ def modeset_unmap(root): #undef static_assert #define static_assert(c) _Static_assert(c,#c) #define trace_iomfb_callback(...) ((void)0) +#define dcp_trace_panel_property(...) ((void)0) +#define GFP_KERNEL 0 +#define kmalloc(n,f) malloc(n) #define dev_warn_dma(...) ((void)0) struct dcp_map_physical_req {u64 paddr,size;u32 flags;u8 dva_null,dva_size_null,padding[2];} __attribute__((packed)); struct dcp_map_physical_resp {u64 dva,dva_size;u32 mem_desc_id;} __attribute__((packed)); @@ -374,10 +377,11 @@ def panel_identity(root, output=None): f['edid_header'] + f['parser'] + f['stubs'] + f['builder']) source = (root / 'drivers/gpu/drm/apple/dcp.c').read_text() helpers = '\n'.join(block(source, name) for name in ( - 'void dcp_panel_metadata_replace(', 'bool dcp_panel_metadata_current(', + 'void dcp_panel_metadata_replace(', 'void dcp_panel_edid_receive(', 'bool dcp_panel_metadata_current(', 'const struct drm_edid *dcp_panel_edid_snapshot(')) backend = (root / 'drivers/gpu/drm/apple/iomfb_v26_6_desktop.c').read_text() - dictionary = block(backend, 'struct dcp_dict_passthrough_req_v26_6 {')+';\n' + dictionary = block((root/'drivers/gpu/drm/apple/parser.c').read_text(), 'int parse_panel_edid_data(') + dictionary += block(backend, 'struct dcp_dict_passthrough_req_v26_6 {')+';\n' dictionary += block(backend, 'static bool trampoline_panel_dictionary_v26_6(') dictionary += block(backend, 'bool iomfb_check_callback_v26_6_desktop(') bindings = '\n'.join(line for line in block(backend, 'static const iomfb_cb_handler cb_handlers[').splitlines() @@ -385,11 +389,14 @@ def panel_identity(root, output=None): unit = code + r''' #define DCP_FIRMWARE_V_26_6_DESKTOP 5 #define trace_iomfb_callback(...) ((void)0) +#define dcp_trace_panel_property(...) ((void)0) +#define GFP_KERNEL 0 +#define kmalloc(n,f) malloc(n) #define spin_lock_irqsave(p,f) do {assert(!*(p));*(p)=true;(f)=0;} while(0) #define spin_unlock_irqrestore(p,f) do {assert(*(p));*(p)=false;(void)(f);} while(0) struct apple_dcp { int fw_compat;bool has_panel;struct {bool lock;} metadata; - struct {struct dcp_display_metadata panel_metadata;u64 panel_metadata_generation;bool panel_metadata_valid;} *desktop; + struct {struct dcp_display_metadata panel_metadata, panel_adt_identity;u64 panel_metadata_generation;bool panel_metadata_valid;u8 panel_raw_edid[4096];size_t panel_raw_edid_size;} *desktop; struct {u32 width_mm,height_mm;} panel;void *connector; }; static unsigned notifications; @@ -414,7 +421,9 @@ def panel_identity(root, output=None): .flags=DRM_MODE_FLAG_PHSYNC|DRM_MODE_FLAG_PVSYNC,.type=i==0?DRM_MODE_TYPE_PREFERRED:0}; modes[0].vrr=true;modes[0].min_vrr=24U<<16;modes[0].max_vrr=120U<<16; u64 generation=99; - assert(IS_ERR(dcp_panel_edid_snapshot(&d,modes,3,&generation))); + desktop.panel_adt_identity=(struct dcp_display_metadata){.manufacturer="APP",.product=0x616}; + const struct drm_edid *fallback=dcp_panel_edid_snapshot(&d,modes,3,&generation); + assert(!IS_ERR(fallback) && drm_edid_valid(fallback));drm_edid_free(fallback); bool (*handlers[600])(struct apple_dcp *,int,void *,void *)={ ''' + bindings + r''' }; @@ -466,6 +475,17 @@ def panel_identity(root, output=None): assert(bytes[108+4] & DRM_EDID_RANGE_OFFSET_MAX_HFREQ); /* The encoded timing clock rounds to 10 kHz; the DCP catalog is unchanged. */ assert(modes[1].mode.clock==540005 && modes[0].mode.clock==1080005); + /* A real EDID replaces the fallback; invalid replacement preserves it. */ + u8 real[128];memcpy(real,bytes,128);real[12]^=1;real[127]^=1; + dcp_panel_edid_receive(&d,real,sizeof(real)); + assert(desktop.panel_raw_edid_size==128); + generation=desktop.panel_metadata_generation; + const struct drm_edid *copied=dcp_panel_edid_snapshot(&d,modes,3,&generation); + assert(!IS_ERR(copied) && !memcmp(&copied->raw,real,128));drm_edid_free(copied); + unsigned previous=notifications; + real[127]^=1;dcp_panel_edid_receive(&d,real,sizeof(real)); + assert(notifications==previous); + desktop.panel_raw_edid_size=0;notifications=1; drm_edid_free(edid); /* Repeated and partial dictionaries cannot trigger monitor/brightness restore loops. */ for(unsigned repeat=0;repeat<32;repeat++) { @@ -666,3 +686,59 @@ def identity_keys(root): return 0; } ''' + + +def adt_identity(root): + import struct + def node(props, children=()): + out=bytearray(struct.pack(' +#include +#include +#include +#include +#include +typedef uint8_t u8;typedef uint32_t u32; +#define U16_MAX UINT16_MAX +#define SZ_16M (16U<<20) +#define ALIGN(x,n) (((x)+(n)-1)&~((n)-1)) +#define ERR_PTR(x) ((void *)(intptr_t)(x)) +#define PTR_ERR(x) ((int)(intptr_t)(x)) +#define IS_ERR(x) ((uintptr_t)(x)>(uintptr_t)-4096) +static u32 get_unaligned_le32(const void *p) {u32 n;memcpy(&n,p,4);return n;} +''' + code+=block((base/'dcp-display-metadata.h').read_text(),'struct dcp_display_metadata {')+';\n' + code+=block(header,'struct dcp_panel_adt {')+';\n'+source + code+='static const u8 fixture[]={'+','.join(map(str,data))+'};\n' + return code+r''' +int main(void) +{ + struct dcp_panel_adt panel={.identity={.manufacturer="APP",.product=0x616}}; + assert(!dcp_panel_adt_parse(fixture,sizeof(fixture),&panel)); + assert(panel.found && panel.width_mm==345 && panel.height_mm==223); + assert(!strcmp(panel.identity.manufacturer,"TST") && panel.identity.product==42); + assert(panel.identity.serial && panel.identity.serial==dcp_adt_serial((const u8 *)"SYNTHETIC-PANEL",15)); + for(unsigned n=0;n Date: Mon, 26 Oct 2026 11:44:32 +1100 Subject: [PATCH 032/184] drm/apple: notify userspace after desktop brightness changes Emit the backlight core's change uevent and notify brightness and actual_brightness after a completed desktop present changes the level, or when takeover updates the inherited level. Defer events outside the receive path's locks and drain notification work during shutdown. Do not use backlight_force_update for a fade: it would overwrite the requested target with the intermediate actual level. Keep the T8140 path unchanged. Host tests exercise accepted, rejected, duplicate and out-of-order completions, teardown gating and preservation of the target. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/gpu/drm/apple/dcp-internal.h | 1 + drivers/gpu/drm/apple/dcp_backlight.c | 34 +++++++++- .../drivers/gpu/apple-dcp/desktop.py | 66 +++++++++++++++++++ .../selftests/drivers/gpu/apple-dcp/run.py | 1 + 4 files changed, 101 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 7cdec3dbf0e52..f20ed0ce3d14a 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -700,6 +700,7 @@ struct apple_dcp { void (*kick)(struct apple_dcp *dcp); /* Steps a running fade; fade_start is its start in jiffies. */ struct delayed_work fade_work; + struct work_struct notify_work; struct delayed_work retry_work; unsigned long fade_start; /* Rate limit for firmware re-send requests. */ diff --git a/drivers/gpu/drm/apple/dcp_backlight.c b/drivers/gpu/drm/apple/dcp_backlight.c index 7d884e062f363..180a97b685fd3 100644 --- a/drivers/gpu/drm/apple/dcp_backlight.c +++ b/drivers/gpu/drm/apple/dcp_backlight.c @@ -113,6 +113,30 @@ static void dcp_backlight_desktop_kick(struct apple_dcp *dcp) schedule_work(&dcp->bl_update_wq); } +/* + * Match the backlight core's change event without force_update(), which + * would replace a user's target with an intermediate fade level. + */ +static void dcp_backlight_notify_work(struct work_struct *work) +{ + struct apple_dcp *dcp = container_of(work, struct apple_dcp, backlight.notify_work); + struct backlight_device *bd = READ_ONCE(dcp->brightness.bl_dev); + char *envp[] = { "SOURCE=unknown", NULL }; + + if (!bd || READ_ONCE(dcp->quiescing)) + return; + kobject_uevent_env(&bd->dev.kobj, KOBJ_CHANGE, envp); + sysfs_notify(&bd->dev.kobj, NULL, "brightness"); + sysfs_notify(&bd->dev.kobj, NULL, "actual_brightness"); +} + +static void dcp_backlight_notify(struct apple_dcp *dcp) +{ + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && + !READ_ONCE(dcp->quiescing) && READ_ONCE(dcp->brightness.bl_dev)) + schedule_work(&dcp->backlight.notify_work); +} + /* A loader hint describes the inherited level, not a new brightness request. */ static void dcp_backlight_sync_hint(struct apple_dcp *dcp) { @@ -128,6 +152,7 @@ static void dcp_backlight_sync_hint(struct apple_dcp *dcp) bd->props.brightness = dcp->backlight.state.target; spin_unlock_irqrestore(&dcp->backlight.lock, flags); mutex_unlock(&bd->ops_lock); + dcp_backlight_notify(dcp); } int dcp_backlight_takeover(struct apple_dcp *dcp, u32 millinits) @@ -304,6 +329,7 @@ void dcp_backlight_stop(struct apple_dcp *dcp) { cancel_delayed_work_sync(&dcp->backlight.fade_work); cancel_delayed_work_sync(&dcp->backlight.retry_work); + cancel_work_sync(&dcp->backlight.notify_work); } static void dcp_backlight_release(void *data) @@ -320,6 +346,7 @@ int dcp_backlight_init(struct apple_dcp *dcp, struct device *dev) { spin_lock_init(&dcp->backlight.lock); INIT_DELAYED_WORK(&dcp->backlight.fade_work, dcp_backlight_fade_work); + INIT_WORK(&dcp->backlight.notify_work, dcp_backlight_notify_work); INIT_DELAYED_WORK(&dcp->backlight.retry_work, dcp_backlight_retry_work); return devm_add_action_or_reset(dev, dcp_backlight_release, dcp); } @@ -464,16 +491,21 @@ void dcp_backlight_desktop_finish(struct apple_dcp *dcp, u32 id, { unsigned long flags; unsigned int delay; - bool finished, pending; + bool finished, pending, changed; + int previous; if (!dcp_backlight_desktop(dcp)) return; spin_lock_irqsave(&dcp->backlight.lock, flags); + previous = dcp_bl_actual(&dcp->backlight.state); finished = dcp_bl_swap_finish(&dcp->backlight.state, &dcp->desktop->backlight, id, reply, accepted); + changed = previous != dcp_bl_actual(&dcp->backlight.state); delay = dcp_bl_retry_delay(&dcp->backlight.state); pending = dcp->backlight.state.dirty; spin_unlock_irqrestore(&dcp->backlight.lock, flags); + if (changed) + dcp_backlight_notify(dcp); if (!finished || READ_ONCE(dcp->quiescing) || READ_ONCE(dcp->crashed)) return; if (delay) diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py index f45784339437d..bccf57dcdb209 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py @@ -208,6 +208,7 @@ def brightness_takeover(root): 'static bool dcp_backlight_controls_device(')) return prefix + r''' #include +#include #define READ_ONCE(x) (x) #define DCP_FIRMWARE_V_26_6_DESKTOP 5 #define DCP_FIRMWARE_H17P 1 @@ -256,6 +257,8 @@ def brightness_takeover(root): static void *bl_get_data(struct backlight_device *bd) {return bd->data;} static bool iomfb_is_m4(struct apple_dcp *d) {(void)d;return false;} bool dcp_backlight_active(struct apple_dcp *d) {(void)d;return false;} +static unsigned notify_count; +static void dcp_backlight_notify(struct apple_dcp *d) {(void)d;notify_count++;} static void dcp_backlight_desktop_kick(struct apple_dcp *d) {(void)d;} static void dcp_backlight_kick(struct apple_dcp *d) {(void)d;} static unsigned fades; @@ -742,3 +745,66 @@ def node(props, children=()): return 0; } ''' + + +def brightness_notifications(root): + import display + prefix=display.backlight(root).split('static void completed(')[0] + prefix=prefix.replace('static inline ', 'static inline __attribute__((unused)) ') + source=(root/'drivers/gpu/drm/apple/dcp_backlight.c').read_text() + functions='\n'.join(block(source,name) for name in ( + 'static void dcp_backlight_notify_work(', 'static void dcp_backlight_notify(', + 'static bool dcp_backlight_desktop(', 'void dcp_backlight_desktop_finish(')) + return prefix+r''' +#include +#include +#define READ_ONCE(x) (x) +#define DCP_FIRMWARE_V_26_6_DESKTOP 5 +#define spin_lock_irqsave(p,f) do {assert(!*(p));*(p)=true;(f)=0;} while(0) +#define spin_unlock_irqrestore(p,f) do {assert(*(p));*(p)=false;(void)(f);} while(0) +#define container_of(p,t,m) ((t *)((char *)(p)-offsetof(t,m))) +#define KOBJ_CHANGE 1 +#define system_dfl_wq NULL +#define msecs_to_jiffies(x) (x) +struct work_struct {int unused;}; +struct backlight_device {struct {int kobj;} dev;struct {u32 brightness;} props;}; +struct apple_dcp { + int fw_compat;bool quiescing,crashed; + struct {struct backlight_device *bl_dev;} brightness; + struct {bool lock;struct dcp_backlight_state state;struct work_struct notify_work;int retry_work;} backlight; + struct {struct dcp_backlight_swap backlight;} *desktop; +}; +static unsigned events,brightness,actual,kicks; +static void kobject_uevent_env(int *k,int action,char **env) +{(void)k;assert(action==1 && !strcmp(env[0],"SOURCE=unknown") && !env[1]);events++;} +static void sysfs_notify(int *k,void *dir,const char *name) +{(void)k;assert(!dir);if(!strcmp(name,"brightness"))brightness++;else {assert(!strcmp(name,"actual_brightness"));actual++;}} +static void dcp_backlight_notify_work(struct work_struct *); +static void schedule_work(struct work_struct *w) {dcp_backlight_notify_work(w);} +static void dcp_backlight_kick(struct apple_dcp *d) {assert(!d->backlight.lock);kicks++;} +static void mod_delayed_work(void *q,int *w,unsigned delay) {(void)q;(void)w;assert(delay);} +'''+functions+r''' +int main(void) +{ + struct backlight_device bd={.props={500}}; + struct apple_dcp d={.fw_compat=5,.brightness={&bd}}; + __typeof__(*d.desktop) desktop={0};d.desktop=&desktop; + assert(!dcp_bl_init(&d.backlight.state,600,true,100,false,0)); + assert(!dcp_bl_request(&d.backlight.state,500,false,false)); + u32 target=d.backlight.state.target; + /* Accepted/completed presents update userspace once, duplicate callbacks do not. */ + assert(!dcp_bl_swap_prepare(&d.backlight.state,&desktop.backlight,7,true)); + dcp_backlight_desktop_finish(&d,8,false,true);assert(!events); + dcp_backlight_desktop_finish(&d,7,true,true);assert(!events); + dcp_backlight_desktop_finish(&d,7,false,true);assert(events==1 && brightness==1 && actual==1); + dcp_backlight_desktop_finish(&d,7,false,true);assert(events==1); + assert(d.backlight.state.target==target && bd.props.brightness==500); + assert(!dcp_bl_request(&d.backlight.state,250,false,false)); + assert(!dcp_bl_swap_prepare(&d.backlight.state,&desktop.backlight,9,true)); + dcp_backlight_desktop_finish(&d,9,true,false);assert(events==1); + d.quiescing=true;dcp_backlight_notify(&d);assert(events==1); + d.quiescing=false;d.fw_compat=1;dcp_backlight_notify(&d);assert(events==1); + d.fw_compat=5;d.brightness.bl_dev=NULL;dcp_backlight_notify(&d);assert(events==1); + return 0; +} +''' diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/run.py b/tools/testing/selftests/drivers/gpu/apple-dcp/run.py index cb9f79f41c939..3c26c17ad916f 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/run.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/run.py @@ -60,6 +60,7 @@ def main(): ('display', 'panel-brightness-commit', display.brightness_commit, ((),), ()), ('display', 'panel-brightness-takeover', display.takeover, ((),), ()), ('display', 'panel-mode-catalog', display.modes, ((),), ()), + ('display', 'desktop-brightness-notifications', desktop.brightness_notifications, ((),), ()), ('display', 'desktop-adt-identity', desktop.adt_identity, ((),), ()), ('display', 'desktop-identity-keys', desktop.identity_keys, ((),), ('-Wno-unused-function',)), ('display', 'desktop-panel-identity', lambda r: desktop.panel_identity(r, output), ((),), ()), From 7842bc73ff2a5b7e002a9e7ec0f186fb394873c5 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 11:46:28 +1100 Subject: [PATCH 033/184] docs: describe desktop panel identity sources and change events Document the bounded diagnostics, exact ADT property mappings, generic fallback and target-preserving backlight notifications. Assisted-by: LLM Signed-off-by: Ryan Murray --- Documentation/gpu/apple-dcp-t6041-panel.rst | 50 +++++++++++++++++++ drivers/gpu/drm/apple/iomfb.c | 2 +- .../selftests/drivers/gpu/apple-dcp/README | 8 +++ 3 files changed, 59 insertions(+), 1 deletion(-) diff --git a/Documentation/gpu/apple-dcp-t6041-panel.rst b/Documentation/gpu/apple-dcp-t6041-panel.rst index 01a03c0f2777d..bc7bd3ac4305e 100644 --- a/Documentation/gpu/apple-dcp-t6041-panel.rst +++ b/Documentation/gpu/apple-dcp-t6041-panel.rst @@ -72,3 +72,53 @@ setters. A legacy PNP manufacturer code is decoded when the text code is absent. Missing optional labels are represented by the firmware vendor/product code and unspecified numeric serial, without inventing a per-device identity. Partial updates for the same vendor/product retain known optional identity fields. + +Desktop identity diagnostics and fallback +----------------------------------------- + +Enable ``appledrm.panel_identity_debug=1`` for a diagnostic boot (or set the +module's ``panel_identity_debug`` parameter before a modeset). D552, D561, +D114 and chunked property delivery report the outer key, serialized key names, +value type numbers and lengths. D000 identifies chunked delivery. Type numbers +are dictionary 1, array 2, integer 4, string 9, blob 10 and boolean 11. Values +are never printed. Each property walk stops at depth 4 or 64 objects, and the +DCP stops after 256 key records, with bounded framing/status lines in addition. +The switch defaults off. Malformed serialization only stops inspection. + +A valid real EDID delivered under ``EDID`` or ``edid`` has priority. Without +one, ProductAttributes remain usable, but are no longer a prerequisite for +publishing an identity. The fallback reads the original ADT from m1n1's +reserved-memory ``phram`` node with ``label = "adt"``. The reader accepts at +most 16 MiB, 4096 nodes, 4096 properties per node and 32 levels, and validates +all extents before committing any identity data. + +The supported ADT identity properties are as follows. These are property +mappings, not a claim that a particular firmware supplies every property. + +* ``/panel`` or ``/product/panel`` ``vendor-id`` or ``display-vendor-id`` is a + little-endian 32-bit packed PNP manufacturer code. +* ``product-id``, ``display-product-id`` or ``panel-id`` supplies the low 16 + bits of the EDID product code. +* ``display-serial-number`` supplies a little-endian numeric serial. + ``serial-number`` supplies panel serial bytes, hashed with FNV-1a into the + numeric EDID serial. ``raw-panel-serial-number`` on ``/product`` or a panel + node is handled the same way. Machine serials from other nodes are ignored. +* ``width-mm`` and ``height-mm`` supply bounded physical dimensions. Missing + dimensions retain the OF panel node's ``width-mm`` and ``height-mm`` (or + ``adj-height-mm`` when the notch is hidden). + +If the ADT or its identity fields are absent, the desktop J616 fallback is +manufacturer APP, product 0616, name Apple Panel and unspecified numeric +serial zero, represented by the non-empty serial descriptor ``00000000``. +This generic identity does not claim to identify an individual panel. The +preferred representable timing and range limits come from the retained DCP +TimingElements catalog. Exact mode enumeration continues to use that catalog. +No new mode, color capability or machine serial is invented. The ADT reader, +EDID fallback and real EDID setters are restricted to desktop 26.6; T8140 +continues to use its existing path. + +Completed desktop brightness changes emit a backlight change uevent and notify +both ``brightness`` and ``actual_brightness``. The work runs outside the receive +path's locks and is drained during shutdown. Takeover sends the same event when +it updates the inherited level. Requested brightness remains the user's target +through the fade; notifications do not replace it with an intermediate level. diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 5a67d1801af4e..850decb52cde9 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -40,7 +40,7 @@ #include "parser.h" #include "trace.h" -/* Enable for one diagnostic boot with apple_drm.panel_identity_debug=1. */ +/* Enable for one diagnostic boot with appledrm.panel_identity_debug=1. */ static bool panel_identity_debug; module_param(panel_identity_debug, bool, 0600); MODULE_PARM_DESC(panel_identity_debug, "Bounded desktop panel property keys and types (no values)"); diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/README b/tools/testing/selftests/drivers/gpu/apple-dcp/README index 51c7c0db9f83f..9471075925ef4 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/README +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/README @@ -188,3 +188,11 @@ partial dictionaries, and notify only when valid identity data actually changes. The identity case also exercises the real D552/D561 table bindings, nested DisplayAttributes and direct ProductAttributes, legacy manufacturer codes, minimal identity data, and unchanged byte/padded boolean acknowledgments. + +Desktop identity diagnostics cover bounded traversal of key names and types, +including truncated and oversized dictionaries. ADT identity tests use only +synthetic nodes and panel serials, reject truncated trees transactionally and +ensure the machine's serial cannot override the panel's. Panel EDID tests cover +a missing firmware identity, real EDID priority and invalid replacement retention. +Desktop backlight notification tests exercise accepted, rejected, duplicate and +out-of-order completions and preserve the requested target during notification. From d626b45468908d809347f46e52d50dd11f71dd6d Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 11:48:01 +1100 Subject: [PATCH 034/184] selftests: apple-dcp: retain the ADT fallback EDID for consumer checks Keep a second EDID fixture for the no-ProductAttributes path so the actual libdisplay-info make/model/serial getters and edid-decode can validate it. Avoid redundant metadata hotplugs once a real EDID has priority. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/gpu/drm/apple/dcp.c | 6 ++++-- tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py | 7 ++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index b4eb0511e82ce..4e220a80496ce 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -857,8 +857,10 @@ void dcp_panel_metadata_replace(struct apple_dcp *dcp, memcmp(&dcp->desktop->panel_metadata, &merged, sizeof(merged))) { dcp->desktop->panel_metadata = merged; dcp->desktop->panel_metadata_valid = true; - dcp->desktop->panel_metadata_generation++; - changed = true; + if (!dcp->desktop->panel_raw_edid_size) { + dcp->desktop->panel_metadata_generation++; + changed = true; + } } spin_unlock_irqrestore(&dcp->metadata.lock, flags); if (changed && dcp->connector) diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py index bccf57dcdb209..3998537deb004 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py @@ -426,7 +426,12 @@ def panel_identity(root, output=None): u64 generation=99; desktop.panel_adt_identity=(struct dcp_display_metadata){.manufacturer="APP",.product=0x616}; const struct drm_edid *fallback=dcp_panel_edid_snapshot(&d,modes,3,&generation); - assert(!IS_ERR(fallback) && drm_edid_valid(fallback));drm_edid_free(fallback); + assert(!IS_ERR(fallback) && drm_edid_valid(fallback)); + if(argc==2) { + char path[4096];snprintf(path,sizeof(path),"%s.adt-fallback",argv[1]); + FILE *file=fopen(path,"wb");assert(file);assert(fwrite(&fallback->raw,128,1,file)==1);fclose(file); + } + drm_edid_free(fallback); bool (*handlers[600])(struct apple_dcp *,int,void *,void *)={ ''' + bindings + r''' }; From 18a99665e5403fbcf0a9736c5aa7ec7cacd466ad Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 11:48:46 +1100 Subject: [PATCH 035/184] drm/apple: include root value types in panel identity diagnostics Scalar setters have no dictionary members. Emit the root type and size as well so one boot identifies their actual representation too. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/gpu/drm/apple/parser-test.c | 2 +- drivers/gpu/drm/apple/parser.c | 12 ++++++++++-- .../selftests/drivers/gpu/apple-dcp/desktop.py | 4 ++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/apple/parser-test.c b/drivers/gpu/drm/apple/parser-test.c index d96b29c43ea0a..2627faab3d5fe 100644 --- a/drivers/gpu/drm/apple/parser-test.c +++ b/drivers/gpu/drm/apple/parser-test.c @@ -1408,7 +1408,7 @@ static void parser_identity_bounds(struct kunit *test) KUNIT_EXPECT_EQ(test, parse_identity_keys(huge, sizeof(huge), parser_identity_emit, &seen), -E2BIG); - KUNIT_EXPECT_EQ(test, seen, 0U); + KUNIT_EXPECT_EQ(test, seen, 1U); for (u32 len = 0; len < sizeof(huge); len++) KUNIT_EXPECT_LT(test, parse_identity_keys(huge, len, parser_identity_emit, &seen), 0); diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index 0d4e85363e7d2..f12b9157c89fa 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -2055,11 +2055,19 @@ static int parse_identity_tree(struct dcp_parse_ctx *ctx, dcp_identity_key_fn em int parse_identity_keys(const void *blob, size_t len, dcp_identity_key_fn emit, void *data) { - struct dcp_parse_ctx ctx; + struct dcp_parse_ctx ctx, root; + const struct dcp_parse_tag *tag; u32 budget = 64; int ret = parse(blob, len, &ctx); - return ret ?: parse_identity_tree(&ctx, emit, data, 0, &budget); + if (ret) + return ret; + root = ctx; + tag = parse_tag(&root); + if (IS_ERR(tag)) + return PTR_ERR(tag); + emit(data, "", 6, tag->type, tag->size, 0); + return parse_identity_tree(&ctx, emit, data, 0, &budget); } /* A setter may deliver the EDID bytes directly or as an OSSerialize blob. */ diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py index 3998537deb004..658043764d59b 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py @@ -685,9 +685,9 @@ def identity_keys(root): } int main(void) { - assert(!parse_identity_keys(blob,sizeof(blob),emit,NULL) && seen==6); + assert(!parse_identity_keys(blob,sizeof(blob),emit,NULL) && seen==7); for(unsigned i=0;i Date: Mon, 26 Oct 2026 11:50:08 +1100 Subject: [PATCH 036/184] drm/apple: accept nested panel EDID blobs with usable identity Look for real EDID data in bounded serialized dictionaries as well as raw and direct blob setters, including the in/out dictionary callback. Keep the existing pass-through reply unchanged. Require identity fields usable by userspace in addition to the EDID checksum, so incomplete real blocks cannot reintroduce null monitor strings. Cover nested blobs and a valid checksum with a missing serial in the host fixture. Assisted-by: LLM Signed-off-by: Ryan Murray --- Documentation/gpu/apple-dcp-t6041-panel.rst | 5 +- drivers/gpu/drm/apple/dcp.c | 33 +++++++- drivers/gpu/drm/apple/iomfb_template.c | 6 +- drivers/gpu/drm/apple/iomfb_v26_6_desktop.c | 11 ++- drivers/gpu/drm/apple/parser.c | 75 ++++++++++++++----- .../drivers/gpu/apple-dcp/desktop.py | 18 ++++- 6 files changed, 118 insertions(+), 30 deletions(-) diff --git a/Documentation/gpu/apple-dcp-t6041-panel.rst b/Documentation/gpu/apple-dcp-t6041-panel.rst index bc7bd3ac4305e..479e1c785f2ec 100644 --- a/Documentation/gpu/apple-dcp-t6041-panel.rst +++ b/Documentation/gpu/apple-dcp-t6041-panel.rst @@ -85,7 +85,10 @@ are never printed. Each property walk stops at depth 4 or 64 objects, and the DCP stops after 256 key records, with bounded framing/status lines in addition. The switch defaults off. Malformed serialization only stops inspection. -A valid real EDID delivered under ``EDID`` or ``edid`` has priority. Without +A real EDID with a usable identity takes priority. It may arrive as raw bytes, +a serialized blob, or an ``EDID``, ``edid`` or ``IODisplayEDID`` blob nested +in a dictionary. Checksum-valid blocks without a usable manufacturer, model +and serial are rejected so they cannot reintroduce null userspace strings. Without one, ProductAttributes remain usable, but are no longer a prerequisite for publishing an identity. The fallback reads the original ADT from m1n1's reserved-memory ``phram`` node with ``label = "adt"``. The reader accepts at diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 4e220a80496ce..65041a66405ff 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -867,6 +867,37 @@ void dcp_panel_metadata_replace(struct apple_dcp *dcp, dcp_queue_hotplug(dcp->connector); } +/* A checksum alone does not guarantee usable userspace identity strings. */ +static bool dcp_panel_edid_has_identity(const u8 *raw) +{ + u16 vendor = (u16)raw[8] << 8 | raw[9]; + bool model = raw[10] || raw[11]; + bool serial = raw[12] || raw[13] || raw[14] || raw[15]; + + for (u32 shift = 0; shift <= 10; shift += 5) + if (!((vendor >> shift) & 31) || ((vendor >> shift) & 31) > 26) + return false; + for (u32 offset = 54; offset <= 108; offset += 18) { + const u8 *desc = raw + offset; + bool text = false; + + if (desc[0] || desc[1] || desc[2] || desc[4]) + continue; + for (u32 i = 5; i < 18 && desc[i] != '\n'; i++) { + if (desc[i] < 0x20 || desc[i] > 0x7e) { + text = false; + break; + } + text |= desc[i] != ' '; + } + if (desc[3] == EDID_DETAIL_MONITOR_NAME) + model |= text; + if (desc[3] == EDID_DETAIL_MONITOR_SERIAL) + serial |= text; + } + return model && serial; +} + /* Preserve a validated real EDID ahead of any metadata-derived fallback. */ void dcp_panel_edid_receive(struct apple_dcp *dcp, const void *blob, size_t len) { @@ -882,7 +913,7 @@ void dcp_panel_edid_receive(struct apple_dcp *dcp, const void *blob, size_t len) edid = drm_edid_alloc(raw, size); if (!edid) return; - if (!drm_edid_valid(edid)) { + if (!drm_edid_valid(edid) || !dcp_panel_edid_has_identity(raw)) { drm_edid_free(edid); return; } diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index ce638d142171e..9c3e105636fc3 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -1267,11 +1267,11 @@ static bool dcpep_process_chunks(struct apple_dcp *dcp, dcp_trace_panel_property(dcp, 0, req->key, strnlen(req->key, sizeof(req->key)), dcp->chunks.data, dcp->chunks.length); - if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && - (!strcmp(req->key, "EDID") || !strcmp(req->key, "edid"))) { + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) dcp_panel_edid_receive(dcp, dcp->chunks.data, dcp->chunks.length); + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && + (!strcmp(req->key, "EDID") || !strcmp(req->key, "edid"))) return true; - } ret = parse(dcp->chunks.data, dcp->chunks.length, &ctx); if (ret) { diff --git a/drivers/gpu/drm/apple/iomfb_v26_6_desktop.c b/drivers/gpu/drm/apple/iomfb_v26_6_desktop.c index 8068e0c25e395..d98a51882b6e8 100644 --- a/drivers/gpu/drm/apple/iomfb_v26_6_desktop.c +++ b/drivers/gpu/drm/apple/iomfb_v26_6_desktop.c @@ -85,8 +85,10 @@ static bool trampoline_passthrough_dict_v26_6(struct apple_dcp *dcp, int tag, strnlen((const char *)req->key, sizeof(req->key)), req->value_null ? NULL : req->value, sizeof(req->value)); - if (!req->value_null) + if (!req->value_null) { + dcp_panel_edid_receive(dcp, req->value, sizeof(req->value)); memcpy(resp->value, req->value, sizeof(resp->value)); + } resp->ret = false; memset(resp->padding, 0, sizeof(resp->padding)); @@ -110,11 +112,8 @@ static bool trampoline_panel_dictionary_v26_6(struct apple_dcp *dcp, int tag, if (!dcp_has_panel(dcp) || req->value_null || !memchr(key, 0, sizeof(req->key))) goto done; - /* A real EDID blob, when supplied, takes precedence over product data. */ - if (!strcmp(key, "EDID") || !strcmp(key, "edid")) { - dcp_panel_edid_receive(dcp, req->value, sizeof(req->value)); - goto done; - } + /* A real EDID may be a blob or nested in a property dictionary. */ + dcp_panel_edid_receive(dcp, req->value, sizeof(req->value)); if (strcmp(key, "DisplayAttributes") && strcmp(key, "ProductAttributes")) goto done; ret = parse(req->value, sizeof(req->value), &ctx); diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index f12b9157c89fa..1c6da363af453 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -2070,13 +2070,67 @@ int parse_identity_keys(const void *blob, size_t len, dcp_identity_key_fn emit, return parse_identity_tree(&ctx, emit, data, 0, &budget); } -/* A setter may deliver the EDID bytes directly or as an OSSerialize blob. */ +/* Look for named EDID blobs inside bounded property dictionaries. */ +static int parse_panel_edid_value(struct dcp_parse_ctx *ctx, u32 depth, u32 *budget, + bool edid_key, const u8 **edid, size_t *size) +{ + const struct dcp_parse_tag *tag; + struct dcp_parse_ctx start = *ctx; + + if (depth > 4 || !*budget) + return -E2BIG; + --*budget; + tag = parse_tag(ctx); + if (IS_ERR(tag)) + return PTR_ERR(tag); + if (tag->type == DCP_TYPE_BLOB && edid_key) { + const u8 *data; + + if (tag->size < 128 || tag->size > 4096 || tag->size % 128) + return -EINVAL; + data = parse_bytes(ctx, tag->size); + if (IS_ERR(data)) + return PTR_ERR(data); + if (memcmp(data, "\x00\xff\xff\xff\xff\xff\xff\x00", 8) || + (1U + data[126]) * 128 != tag->size) + return -EINVAL; + *edid = data; + *size = tag->size; + return 0; + } + if (tag->type == DCP_TYPE_DICTIONARY) { + if (tag->size > *budget) + return -E2BIG; + for (u32 i = 0; i < tag->size; i++) { + const struct dcp_parse_tag *key_tag = parse_tag_of_type(ctx, DCP_TYPE_STRING); + const char *key; + bool match; + int ret; + + if (IS_ERR(key_tag)) + return PTR_ERR(key_tag); + key = parse_bytes(ctx, key_tag->size); + if (IS_ERR(key)) + return PTR_ERR(key); + match = (key_tag->size == 4 && (!memcmp(key, "EDID", 4) || + !memcmp(key, "edid", 4))) || + (key_tag->size == 13 && !memcmp(key, "IODisplayEDID", 13)); + ret = parse_panel_edid_value(ctx, depth + 1, budget, match, edid, size); + if (ret != -ENOENT) + return ret; + } + return -ENOENT; + } + *ctx = start; + return skip(ctx) ?: -ENOENT; +} + +/* A setter may deliver raw bytes, a blob, or a dictionary containing a blob. */ int parse_panel_edid_data(const void *blob, size_t len, const u8 **edid, size_t *size) { struct dcp_parse_ctx ctx; - const struct dcp_parse_tag *tag; const u8 *data = blob; - u32 bytes; + u32 bytes, budget = 64; int ret; if (len >= 128 && !memcmp(data, "\x00\xff\xff\xff\xff\xff\xff\x00", 8)) { @@ -2088,18 +2142,5 @@ int parse_panel_edid_data(const void *blob, size_t len, const u8 **edid, size_t return 0; } ret = parse(blob, len, &ctx); - if (ret) - return ret; - tag = parse_tag_of_type(&ctx, DCP_TYPE_BLOB); - if (IS_ERR(tag) || tag->size < 128 || tag->size > 4096 || tag->size % 128) - return -EINVAL; - data = parse_bytes(&ctx, tag->size); - if (IS_ERR(data)) - return PTR_ERR(data); - if (memcmp(data, "\x00\xff\xff\xff\xff\xff\xff\x00", 8) || - (1U + data[126]) * 128 != tag->size) - return -EINVAL; - *edid = data; - *size = tag->size; - return 0; + return ret ?: parse_panel_edid_value(&ctx, 0, &budget, true, edid, size); } diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py index 658043764d59b..064f215fd96e6 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py @@ -380,10 +380,11 @@ def panel_identity(root, output=None): f['edid_header'] + f['parser'] + f['stubs'] + f['builder']) source = (root / 'drivers/gpu/drm/apple/dcp.c').read_text() helpers = '\n'.join(block(source, name) for name in ( - 'void dcp_panel_metadata_replace(', 'void dcp_panel_edid_receive(', 'bool dcp_panel_metadata_current(', + 'void dcp_panel_metadata_replace(', 'static bool dcp_panel_edid_has_identity(', 'void dcp_panel_edid_receive(', 'bool dcp_panel_metadata_current(', 'const struct drm_edid *dcp_panel_edid_snapshot(')) backend = (root / 'drivers/gpu/drm/apple/iomfb_v26_6_desktop.c').read_text() - dictionary = block((root/'drivers/gpu/drm/apple/parser.c').read_text(), 'int parse_panel_edid_data(') + dictionary = block((root/'drivers/gpu/drm/apple/parser.c').read_text(), 'static int parse_panel_edid_value(') + dictionary += block((root/'drivers/gpu/drm/apple/parser.c').read_text(), 'int parse_panel_edid_data(') dictionary += block(backend, 'struct dcp_dict_passthrough_req_v26_6 {')+';\n' dictionary += block(backend, 'static bool trampoline_panel_dictionary_v26_6(') dictionary += block(backend, 'bool iomfb_check_callback_v26_6_desktop(') @@ -487,12 +488,25 @@ def panel_identity(root, output=None): u8 real[128];memcpy(real,bytes,128);real[12]^=1;real[127]^=1; dcp_panel_edid_receive(&d,real,sizeof(real)); assert(desktop.panel_raw_edid_size==128); + u8 nested[148]={0};u32 word; + word=0xd3;memcpy(nested,&word,4);word=0x01000001;memcpy(nested+4,&word,4); + word=0x09000004;memcpy(nested+8,&word,4);memcpy(nested+12,"EDID",4); + word=0x0a000080;memcpy(nested+16,&word,4);memcpy(nested+20,real,128); + const u8 *nested_raw=NULL;size_t nested_size=0; + assert(!parse_panel_edid_data(nested,sizeof(nested),&nested_raw,&nested_size)); + assert(nested_size==128 && !memcmp(nested_raw,real,128)); + dcp_panel_edid_receive(&d,nested,sizeof(nested)); generation=desktop.panel_metadata_generation; const struct drm_edid *copied=dcp_panel_edid_snapshot(&d,modes,3,&generation); assert(!IS_ERR(copied) && !memcmp(&copied->raw,real,128));drm_edid_free(copied); unsigned previous=notifications; real[127]^=1;dcp_panel_edid_receive(&d,real,sizeof(real)); assert(notifications==previous); + /* A checksum-valid block with no serial must not reintroduce null strings. */ + memset(real+12,0,4);memset(real+95,' ',13);real[127]=0; + for(unsigned j=0;j<127;j++)real[127]-=real[j]; + assert(!dcp_panel_edid_has_identity(real)); + dcp_panel_edid_receive(&d,real,sizeof(real));assert(notifications==previous); desktop.panel_raw_edid_size=0;notifications=1; drm_edid_free(edid); /* Repeated and partial dictionaries cannot trigger monitor/brightness restore loops. */ From 075e86fab5f6ddf1a9a842074d9529b8888c5cba Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 11:52:51 +1100 Subject: [PATCH 037/184] drm/apple: drain desktop brightness events before backlight release Register a desktop-only devres action after the backlight resource so notification work is drained before the device is unregistered, including probe unwinds. Clear its published pointer first so a late queue attempt cannot reach a freed device. Exercise this lifetime boundary on the host. Treat blank panel labels as absent so EDID descriptors use non-empty fallbacks. Match current allocation and indentation conventions, and model the new panel-only ingress in the external ownership fixture. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/gpu/drm/apple/dcp-internal.h | 1 - drivers/gpu/drm/apple/dcp-panel-adt.c | 2 +- drivers/gpu/drm/apple/dcp.c | 4 +++- drivers/gpu/drm/apple/dcp_backlight.c | 22 +++++++++++++++++-- drivers/gpu/drm/apple/iomfb.c | 4 ++-- drivers/gpu/drm/apple/iomfb_template.c | 3 ++- drivers/gpu/drm/apple/iomfb_v26_6_desktop.c | 6 ++--- drivers/gpu/drm/apple/parser-test.c | 14 +++++++----- drivers/gpu/drm/apple/parser.c | 12 +++++++--- .../drivers/gpu/apple-dcp/desktop.py | 18 ++++++++++----- .../selftests/drivers/gpu/apple-dcp/rate.py | 2 ++ 11 files changed, 62 insertions(+), 26 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index f20ed0ce3d14a..170e583d645c4 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -897,7 +897,6 @@ static inline bool dcp_ext_dptx_607(const struct apple_dcp *dcp) #define DCP_AUDIO_MAX_CHANS 15 - void dcp_trace_panel_property(struct apple_dcp *dcp, int tag, const char *key, size_t key_len, const void *blob, size_t len); diff --git a/drivers/gpu/drm/apple/dcp-panel-adt.c b/drivers/gpu/drm/apple/dcp-panel-adt.c index 9dbe34e0c2f2c..e3a9b5e4f238e 100644 --- a/drivers/gpu/drm/apple/dcp-panel-adt.c +++ b/drivers/gpu/drm/apple/dcp-panel-adt.c @@ -40,7 +40,7 @@ static u32 dcp_adt_serial(const u8 *data, size_t size) } static void dcp_adt_panel_property(struct dcp_panel_adt *panel, const char *name, - const u8 *value, u32 size) + const u8 *value, u32 size) { struct dcp_display_metadata *id = &panel->identity; u32 number = size == 4 ? get_unaligned_le32(value) : 0; diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 65041a66405ff..6a2eebc1cf057 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -874,6 +874,8 @@ static bool dcp_panel_edid_has_identity(const u8 *raw) bool model = raw[10] || raw[11]; bool serial = raw[12] || raw[13] || raw[14] || raw[15]; + if (vendor & 0x8000) + return false; for (u32 shift = 0; shift <= 10; shift += 5) if (!((vendor >> shift) & 31) || ((vendor >> shift) & 31) > 26) return false; @@ -952,7 +954,7 @@ const struct drm_edid *dcp_panel_edid_snapshot(struct apple_dcp *dcp, size_t size; unsigned long flags; - raw = kmalloc(sizeof(dcp->desktop->panel_raw_edid), GFP_KERNEL); + raw = kmalloc_objs(u8, sizeof(dcp->desktop->panel_raw_edid), GFP_KERNEL); if (!raw) return ERR_PTR(-ENOMEM); spin_lock_irqsave(&dcp->metadata.lock, flags); diff --git a/drivers/gpu/drm/apple/dcp_backlight.c b/drivers/gpu/drm/apple/dcp_backlight.c index 180a97b685fd3..3aeabc1982737 100644 --- a/drivers/gpu/drm/apple/dcp_backlight.c +++ b/drivers/gpu/drm/apple/dcp_backlight.c @@ -121,15 +121,26 @@ static void dcp_backlight_notify_work(struct work_struct *work) { struct apple_dcp *dcp = container_of(work, struct apple_dcp, backlight.notify_work); struct backlight_device *bd = READ_ONCE(dcp->brightness.bl_dev); - char *envp[] = { "SOURCE=unknown", NULL }; + char *envp[2]; if (!bd || READ_ONCE(dcp->quiescing)) return; + envp[0] = "SOURCE=unknown"; + envp[1] = NULL; kobject_uevent_env(&bd->dev.kobj, KOBJ_CHANGE, envp); sysfs_notify(&bd->dev.kobj, NULL, "brightness"); sysfs_notify(&bd->dev.kobj, NULL, "actual_brightness"); } +/* Run before the later-registered backlight devres unregisters the device. */ +static void dcp_backlight_notify_release(void *data) +{ + struct apple_dcp *dcp = data; + + WRITE_ONCE(dcp->brightness.bl_dev, NULL); + cancel_work_sync(&dcp->backlight.notify_work); +} + static void dcp_backlight_notify(struct apple_dcp *dcp) { if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && @@ -722,6 +733,7 @@ int dcp_backlight_register(struct apple_dcp *dcp) { struct device *dev = dcp->dev; struct backlight_device *bl_dev; + int ret; unsigned long flags; struct backlight_properties props = { .type = BACKLIGHT_PLATFORM, @@ -781,7 +793,13 @@ int dcp_backlight_register(struct apple_dcp *dcp) if (IS_ERR(bl_dev)) return PTR_ERR(bl_dev); - dcp->brightness.bl_dev = bl_dev; + /* LIFO devres order drains event work before unregistering the backlight. */ + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) { + ret = devm_add_action_or_reset(dev, dcp_backlight_notify_release, dcp); + if (ret) + return ret; + } + WRITE_ONCE(dcp->brightness.bl_dev, bl_dev); /* A hint may have arrived between the property snapshot and registration. */ if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) dcp_backlight_sync_hint(dcp); diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 850decb52cde9..e27ac426aeef3 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -46,7 +46,7 @@ module_param(panel_identity_debug, bool, 0600); MODULE_PARM_DESC(panel_identity_debug, "Bounded desktop panel property keys and types (no values)"); static void dcp_identity_key_log(void *data, const char *key, u32 key_len, - u32 type, u32 size, u32 depth) + u32 type, u32 size, u32 depth) { struct apple_dcp *dcp = data; char safe[65]; @@ -63,7 +63,7 @@ static void dcp_identity_key_log(void *data, const char *key, u32 key_len, } void dcp_trace_panel_property(struct apple_dcp *dcp, int tag, const char *key, - size_t key_len, const void *blob, size_t len) + size_t key_len, const void *blob, size_t len) { int ret; diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 9c3e105636fc3..b1145f733c9bb 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -1265,7 +1265,8 @@ static bool dcpep_process_chunks(struct apple_dcp *dcp, dcp->chunks.length); dcp_trace_panel_property(dcp, 0, req->key, - strnlen(req->key, sizeof(req->key)), dcp->chunks.data, dcp->chunks.length); + strnlen(req->key, sizeof(req->key)), + dcp->chunks.data, dcp->chunks.length); if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) dcp_panel_edid_receive(dcp, dcp->chunks.data, dcp->chunks.length); diff --git a/drivers/gpu/drm/apple/iomfb_v26_6_desktop.c b/drivers/gpu/drm/apple/iomfb_v26_6_desktop.c index d98a51882b6e8..b9fba6f5656d1 100644 --- a/drivers/gpu/drm/apple/iomfb_v26_6_desktop.c +++ b/drivers/gpu/drm/apple/iomfb_v26_6_desktop.c @@ -82,8 +82,8 @@ static bool trampoline_passthrough_dict_v26_6(struct apple_dcp *dcp, int tag, trace_iomfb_callback(dcp, tag, "dcpep_cb_passthrough_dict"); dcp_trace_panel_property(dcp, tag, (const char *)req->key, - strnlen((const char *)req->key, sizeof(req->key)), - req->value_null ? NULL : req->value, sizeof(req->value)); + strnlen((const char *)req->key, sizeof(req->key)), + req->value_null ? NULL : req->value, sizeof(req->value)); if (!req->value_null) { dcp_panel_edid_receive(dcp, req->value, sizeof(req->value)); @@ -108,7 +108,7 @@ static bool trampoline_panel_dictionary_v26_6(struct apple_dcp *dcp, int tag, trace_iomfb_callback(dcp, tag, __func__); dcp_trace_panel_property(dcp, tag, key, strnlen(key, sizeof(req->key)), - req->value_null ? NULL : req->value, sizeof(req->value)); + req->value_null ? NULL : req->value, sizeof(req->value)); if (!dcp_has_panel(dcp) || req->value_null || !memchr(key, 0, sizeof(req->key))) goto done; diff --git a/drivers/gpu/drm/apple/parser-test.c b/drivers/gpu/drm/apple/parser-test.c index 2627faab3d5fe..85b6c0df99753 100644 --- a/drivers/gpu/drm/apple/parser-test.c +++ b/drivers/gpu/drm/apple/parser-test.c @@ -1394,7 +1394,7 @@ static void parser_panel_identity_stable(struct kunit *test) } static void parser_identity_emit(void *data, const char *key, u32 len, - u32 type, u32 size, u32 depth) + u32 type, u32 size, u32 depth) { u32 *seen = data; @@ -1405,13 +1405,15 @@ static void parser_identity_bounds(struct kunit *test) { u32 seen = 0; __le32 huge[] = { cpu_to_le32(0xd3), cpu_to_le32(0x01ffffff) }; + int ret; - KUNIT_EXPECT_EQ(test, parse_identity_keys(huge, sizeof(huge), - parser_identity_emit, &seen), -E2BIG); + ret = parse_identity_keys(huge, sizeof(huge), parser_identity_emit, &seen); + KUNIT_EXPECT_EQ(test, ret, -E2BIG); KUNIT_EXPECT_EQ(test, seen, 1U); - for (u32 len = 0; len < sizeof(huge); len++) - KUNIT_EXPECT_LT(test, parse_identity_keys(huge, len, - parser_identity_emit, &seen), 0); + for (u32 len = 0; len < sizeof(huge); len++) { + ret = parse_identity_keys(huge, len, parser_identity_emit, &seen); + KUNIT_EXPECT_LT(test, ret, 0); + } } static void parser_adt_bounds(struct kunit *test) diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index 1c6da363af453..33344a5f8b10b 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -1341,6 +1341,11 @@ static int parse_panel_label(struct dcp_parse_ctx *handle, char out[14]) length = 13; memcpy(out, value, length); out[length] = 0; + /* A blank descriptor is absent to libdisplay-info; use the fallback. */ + for (unsigned int i = 0; i < length; i++) + if (out[i] != ' ') + return 0; + out[0] = 0; return 0; } @@ -2006,7 +2011,7 @@ int parse_system_log_mnits(struct dcp_parse_ctx *handle, struct dcp_system_ev_mn /* Inspect structure only. Never emit a firmware value or allocate from its size. */ static int parse_identity_tree(struct dcp_parse_ctx *ctx, dcp_identity_key_fn emit, - void *data, u32 depth, u32 *budget) + void *data, u32 depth, u32 *budget) { const struct dcp_parse_tag *tag; u32 count; @@ -2072,7 +2077,7 @@ int parse_identity_keys(const void *blob, size_t len, dcp_identity_key_fn emit, /* Look for named EDID blobs inside bounded property dictionaries. */ static int parse_panel_edid_value(struct dcp_parse_ctx *ctx, u32 depth, u32 *budget, - bool edid_key, const u8 **edid, size_t *size) + bool edid_key, const u8 **edid, size_t *size) { const struct dcp_parse_tag *tag; struct dcp_parse_ctx start = *ctx; @@ -2102,11 +2107,12 @@ static int parse_panel_edid_value(struct dcp_parse_ctx *ctx, u32 depth, u32 *bud if (tag->size > *budget) return -E2BIG; for (u32 i = 0; i < tag->size; i++) { - const struct dcp_parse_tag *key_tag = parse_tag_of_type(ctx, DCP_TYPE_STRING); + const struct dcp_parse_tag *key_tag; const char *key; bool match; int ret; + key_tag = parse_tag_of_type(ctx, DCP_TYPE_STRING); if (IS_ERR(key_tag)) return PTR_ERR(key_tag); key = parse_bytes(ctx, key_tag->size); diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py index 064f215fd96e6..218b14a6742ca 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py @@ -50,7 +50,7 @@ def modeset_unmap(root): #define trace_iomfb_callback(...) ((void)0) #define dcp_trace_panel_property(...) ((void)0) #define GFP_KERNEL 0 -#define kmalloc(n,f) malloc(n) +#define kmalloc_objs(t,n,f) malloc(sizeof(t)*(n)) #define dev_warn_dma(...) ((void)0) struct dcp_map_physical_req {u64 paddr,size;u32 flags;u8 dva_null,dva_size_null,padding[2];} __attribute__((packed)); struct dcp_map_physical_resp {u64 dva,dva_size;u32 mem_desc_id;} __attribute__((packed)); @@ -210,6 +210,7 @@ def brightness_takeover(root): #include #include #define READ_ONCE(x) (x) +#define WRITE_ONCE(x,v) ((x)=(v)) #define DCP_FIRMWARE_V_26_6_DESKTOP 5 #define DCP_FIRMWARE_H17P 1 #define DCP_IOMFB_METHODS_H17G 7 @@ -218,6 +219,7 @@ def brightness_takeover(root): #define BACKLIGHT_SCALE_LINEAR 1 #define BL_CORE_SUSPENDED 1 #define BL_CORE_FBBLANK 2 +#define devm_add_action_or_reset(...) (0) #define DCP_BL_FADE_STEP_MS 16 #define system_dfl_wq NULL #define IS_ERR(p) false @@ -375,7 +377,7 @@ def panel_identity(root, output=None): 'SerialNumber': 0x12345678}} raw = bytearray(struct.pack(' #include #define READ_ONCE(x) (x) +#define WRITE_ONCE(x,v) ((x)=(v)) #define DCP_FIRMWARE_V_26_6_DESKTOP 5 #define spin_lock_irqsave(p,f) do {assert(!*(p));*(p)=true;(f)=0;} while(0) #define spin_unlock_irqrestore(p,f) do {assert(*(p));*(p)=false;(void)(f);} while(0) @@ -793,7 +797,8 @@ def brightness_notifications(root): struct {bool lock;struct dcp_backlight_state state;struct work_struct notify_work;int retry_work;} backlight; struct {struct dcp_backlight_swap backlight;} *desktop; }; -static unsigned events,brightness,actual,kicks; +static unsigned events,brightness,actual,kicks,cancelled; +static void cancel_work_sync(struct work_struct *w) {(void)w;cancelled++;} static void kobject_uevent_env(int *k,int action,char **env) {(void)k;assert(action==1 && !strcmp(env[0],"SOURCE=unknown") && !env[1]);events++;} static void sysfs_notify(int *k,void *dir,const char *name) @@ -823,7 +828,8 @@ def brightness_notifications(root): dcp_backlight_desktop_finish(&d,9,true,false);assert(events==1); d.quiescing=true;dcp_backlight_notify(&d);assert(events==1); d.quiescing=false;d.fw_compat=1;dcp_backlight_notify(&d);assert(events==1); - d.fw_compat=5;d.brightness.bl_dev=NULL;dcp_backlight_notify(&d);assert(events==1); + d.fw_compat=5;dcp_backlight_notify_release(&d); + assert(!d.brightness.bl_dev && cancelled==1);dcp_backlight_notify(&d);assert(events==1); return 0; } ''' diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py b/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py index 4194e8d39ad20..02e23247f50b2 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py @@ -650,6 +650,8 @@ def publication(root): static const struct drm_edid *dcp_panel_edid_snapshot(struct apple_dcp *d,const struct dcp_display_mode *m,unsigned n,u64 *generation) { (void)d;(void)m;(void)n;(void)generation;assert(!"panel identity in external fixture");return ERR_PTR(-ENODATA); } static bool dcp_panel_metadata_current(struct apple_dcp *d,u64 generation) { (void)d;(void)generation;assert(!"panel identity in external fixture");return false; } static void dcp_panel_metadata_replace(struct apple_dcp *d,const struct dcp_display_metadata *m) { (void)d;(void)m;assert(!"panel identity in external receipt"); } +static void dcp_panel_edid_receive(struct apple_dcp *d,const void *p,size_t n) { (void)d;(void)p;(void)n;assert(!"panel EDID in external receipt"); } +#define dcp_trace_panel_property(...) ((void)0) static struct drm_display_mode *drm_mode_duplicate(struct drm_device *dev,const struct drm_display_mode *m) { assert(dev->mode_config.mutex);struct drm_display_mode *copy=malloc(sizeof(*copy));assert(copy);*copy=*m; From 94909ec2fe98097481256799602fbb1c1215ada3 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 11:53:21 +1100 Subject: [PATCH 038/184] drm/apple: preserve exact panel dimensions in the EDID timing The EDID base size rounds to centimetres, but its detailed timing can retain the panel's millimetres. Use the actual panel dimensions there, including the ADT fallback, and check both fields in the host fixture. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/gpu/drm/apple/dcp-display-metadata.c | 6 ++++++ tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py | 2 ++ 2 files changed, 8 insertions(+) diff --git a/drivers/gpu/drm/apple/dcp-display-metadata.c b/drivers/gpu/drm/apple/dcp-display-metadata.c index 94cc5ec03417f..6355e697a653e 100644 --- a/drivers/gpu/drm/apple/dcp-display-metadata.c +++ b/drivers/gpu/drm/apple/dcp-display-metadata.c @@ -275,6 +275,12 @@ const struct drm_edid *dcp_panel_edid(const struct dcp_display_metadata *metadat mode.clock = DIV_ROUND_CLOSEST(mode.clock, 10) * 10; ret = dcp_metadata_timing(&candidate, &mode, &dimensions); if (!ret && (!timing || (!preferred && (mode.type & DRM_MODE_TYPE_PREFERRED)))) { + struct detailed_pixel_timing *pixel = &candidate.data.pixel_data; + + /* The base size is in cm, but the DTD preserves exact panel mm. */ + pixel->width_mm_lo = width_mm; + pixel->height_mm_lo = height_mm; + pixel->width_height_mm_hi = (width_mm >> 8) << 4 | height_mm >> 8; block.detailed_timings[0] = candidate; timing = true; preferred = mode.type & DRM_MODE_TYPE_PREFERRED; diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py index 218b14a6742ca..f28c13d238322 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py @@ -478,6 +478,8 @@ def panel_identity(root, output=None): assert(bytes[8]==0x52 && bytes[9]==0x74 && bytes[10]==42 && !bytes[11]); assert(bytes[12]==0x78 && bytes[13]==0x56 && bytes[14]==0x34 && bytes[15]==0x12); assert(bytes[18]==1 && bytes[19]==4 && bytes[21]==35 && bytes[22]==22 && bytes[23]==0xff); + assert(((unsigned)bytes[66] | (unsigned)(bytes[68] >> 4) << 8)==345); + assert(((unsigned)bytes[67] | (unsigned)(bytes[68] & 15) << 8)==223); assert(bytes[72+3]==EDID_DETAIL_MONITOR_NAME && bytes[90+3]==EDID_DETAIL_MONITOR_SERIAL); assert(!memcmp(bytes+90+5,"12345678\n",9)); if(argc==2) {FILE *file=fopen(argv[1],"wb");assert(file);assert(fwrite(bytes,128,1,file)==1);fclose(file);} From a30b34da388d10e4b4557bc31ff0f59635ba0c22 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 12:04:57 +1100 Subject: [PATCH 039/184] drm/apple: reserve separate identity trace budgets for panel setters D114 statistics must not exhaust diagnostic output before D552 or D561 publishes panel data. Give each callback class a separate bounded budget, with a fixed total of 512 key records per DCP for the diagnostic boot. Assisted-by: LLM Signed-off-by: Ryan Murray --- Documentation/gpu/apple-dcp-t6041-panel.rst | 4 +++- drivers/gpu/drm/apple/dcp-internal.h | 2 +- drivers/gpu/drm/apple/iomfb.c | 26 +++++++++++++++------ 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/Documentation/gpu/apple-dcp-t6041-panel.rst b/Documentation/gpu/apple-dcp-t6041-panel.rst index 479e1c785f2ec..1bbb62099a9c9 100644 --- a/Documentation/gpu/apple-dcp-t6041-panel.rst +++ b/Documentation/gpu/apple-dcp-t6041-panel.rst @@ -82,7 +82,9 @@ D114 and chunked property delivery report the outer key, serialized key names, value type numbers and lengths. D000 identifies chunked delivery. Type numbers are dictionary 1, array 2, integer 4, string 9, blob 10 and boolean 11. Values are never printed. Each property walk stops at depth 4 or 64 objects, and the -DCP stops after 256 key records, with bounded framing/status lines in addition. +DCP stops after 128 key records per callback class (512 total), with bounded +framing/status lines in addition. D552, D561, D114 and chunked delivery have +separate budgets so periodic statistics cannot hide the panel setters. The switch defaults off. Malformed serialization only stops inspection. A real EDID with a usable identity takes priority. It may arrive as raw bytes, diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 170e583d645c4..a7079c1bbb814 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -248,7 +248,7 @@ struct dcp_desktop_state { struct dcp_display_metadata panel_adt_identity; u64 panel_metadata_generation; bool panel_metadata_valid; - u32 identity_log_count; + u32 identity_log_count[4]; u8 panel_raw_edid[4096]; size_t panel_raw_edid_size; /* RX records an unsupported callback until the modeset RPC has drained. */ diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index e27ac426aeef3..40e2b797b7d60 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -45,19 +45,25 @@ static bool panel_identity_debug; module_param(panel_identity_debug, bool, 0600); MODULE_PARM_DESC(panel_identity_debug, "Bounded desktop panel property keys and types (no values)"); +struct dcp_identity_trace { + struct apple_dcp *dcp; + u32 *count; +}; + static void dcp_identity_key_log(void *data, const char *key, u32 key_len, u32 type, u32 size, u32 depth) { - struct apple_dcp *dcp = data; + struct dcp_identity_trace *trace = data; + struct apple_dcp *dcp = trace->dcp; char safe[65]; u32 i; - if (dcp->desktop->identity_log_count >= 256) + if (*trace->count >= 128) return; for (i = 0; i < min_t(u32, key_len, sizeof(safe) - 1); i++) safe[i] = key[i] >= 0x20 && key[i] <= 0x7e ? key[i] : '?'; safe[i] = 0; - dcp->desktop->identity_log_count++; + ++*trace->count; dev_info(dcp->dev, "panel identity depth=%u key=%s type=%u size=%u\n", depth, safe, type, size); } @@ -65,18 +71,24 @@ static void dcp_identity_key_log(void *data, const char *key, u32 key_len, void dcp_trace_panel_property(struct apple_dcp *dcp, int tag, const char *key, size_t key_len, const void *blob, size_t len) { + struct dcp_identity_trace trace; + u32 slot = tag == 552 ? 0 : tag == 561 ? 1 : tag == 114 ? 2 : 3; int ret; if (!READ_ONCE(panel_identity_debug) || - dcp->fw_compat != DCP_FIRMWARE_V_26_6_DESKTOP || - !dcp_has_panel(dcp) || dcp->desktop->identity_log_count >= 256) + dcp->fw_compat != DCP_FIRMWARE_V_26_6_DESKTOP || !dcp_has_panel(dcp)) + return; + trace.dcp = dcp; + trace.count = &dcp->desktop->identity_log_count[slot]; + /* Periodic statistics cannot consume the panel setters' budget. */ + if (*trace.count >= 128) return; dev_info(dcp->dev, "panel identity callback=D%03d bytes=%zu null=%d\n", tag, len, !blob); - dcp_identity_key_log(dcp, key, key_len, 0, len, 0); + dcp_identity_key_log(&trace, key, key_len, 0, len, 0); if (!blob) return; - ret = parse_identity_keys(blob, len, dcp_identity_key_log, dcp); + ret = parse_identity_keys(blob, len, dcp_identity_key_log, &trace); if (ret) dev_info(dcp->dev, "panel identity inspection stopped: %d\n", ret); } From c972c29d1024380572fcaa3b75e1d3a7208d1ec0 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 12:06:01 +1100 Subject: [PATCH 040/184] selftests: apple-dcp: exercise identity trace gating and budget isolation Drive the production logger with a synthetic inspector. Check default-off and non-desktop gating, sanitization, value omission, fixed limits and that exhausted statistics do not silence D552 or D561. Assisted-by: LLM Signed-off-by: Ryan Murray --- .../drivers/gpu/apple-dcp/desktop.py | 66 +++++++++++++++++++ .../selftests/drivers/gpu/apple-dcp/run.py | 1 + 2 files changed, 67 insertions(+) diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py index f28c13d238322..97a017aea8524 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py @@ -835,3 +835,69 @@ def brightness_notifications(root): return 0; } ''' + + +def identity_trace_budget(root): + source=(root/'drivers/gpu/drm/apple/iomfb.c').read_text() + code=r''' +#include +#include +#include +#include +#include +#include +#include +typedef uint32_t u32; +#define READ_ONCE(x) (x) +#define min_t(t,a,b) ((t)(a)<(t)(b)?(t)(a):(t)(b)) +#define DCP_FIRMWARE_V_26_6_DESKTOP 5 +struct apple_dcp {int fw_compat;bool has_panel;void *dev;struct {u32 identity_log_count[4];} *desktop;}; +static bool panel_identity_debug; +static unsigned messages; +static char output[256]; +static bool dcp_has_panel(struct apple_dcp *d) {return d->has_panel;} +static void dev_info(void *dev,const char *fmt,...) +{ + (void)dev;va_list args;va_start(args,fmt);vsnprintf(output,sizeof(output),fmt,args);va_end(args);messages++; +} +typedef void (*dcp_identity_key_fn)(void *,const char *,u32,u32,u32,u32); +static int parse_identity_keys(const void *blob,size_t len,dcp_identity_key_fn emit,void *data) +{ + (void)blob;(void)len; + for(unsigned i=0;i<70;i++)emit(data,"Key\n",4,9,25,0); + return 0; +} +''' + code+=block(source,'struct dcp_identity_trace {')+';\n' + code+=block(source,'static void dcp_identity_key_log(') + code+=block(source,'void dcp_trace_panel_property(') + return code+r''' +int main(void) +{ + struct apple_dcp d={.fw_compat=5,.has_panel=true}; + __typeof__(*d.desktop) desktop={0};d.desktop=&desktop; + const char secret[]="SYNTHETIC_VALUE_NOT_FOR_LOGS"; + dcp_trace_panel_property(&d,561,"Property",8,secret,sizeof(secret));assert(!messages); + panel_identity_debug=true;d.fw_compat=1; + dcp_trace_panel_property(&d,561,"Property",8,secret,sizeof(secret));assert(!messages); + d.fw_compat=5;d.has_panel=false; + dcp_trace_panel_property(&d,561,"Property",8,secret,sizeof(secret));assert(!messages); + d.has_panel=true; + for(unsigned i=0;i<100;i++)dcp_trace_panel_property(&d,114,"Statistics",10,secret,sizeof(secret)); + assert(desktop.identity_log_count[2]==128 && !desktop.identity_log_count[0] && !desktop.identity_log_count[1]); + unsigned before=messages; + dcp_trace_panel_property(&d,552,"Panel",5,secret,sizeof(secret));assert(messages>before); + dcp_trace_panel_property(&d,561,"Panel",5,secret,sizeof(secret)); + assert(desktop.identity_log_count[0] && desktop.identity_log_count[1]); + assert(strstr(output,"Key?") && !strstr(output,secret)); + for(unsigned i=0;i<100;i++) { + dcp_trace_panel_property(&d,552,"Panel",5,secret,sizeof(secret)); + dcp_trace_panel_property(&d,561,"Panel",5,secret,sizeof(secret)); + dcp_trace_panel_property(&d,0,"Chunks",6,secret,sizeof(secret)); + } + for(unsigned i=0;i<4;i++)assert(desktop.identity_log_count[i]==128); + before=messages;dcp_trace_panel_property(&d,561,"Panel",5,NULL,0);assert(messages==before); + assert(messages<=520); + return 0; +} +''' diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/run.py b/tools/testing/selftests/drivers/gpu/apple-dcp/run.py index 3c26c17ad916f..1b27400946e06 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/run.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/run.py @@ -62,6 +62,7 @@ def main(): ('display', 'panel-mode-catalog', display.modes, ((),), ()), ('display', 'desktop-brightness-notifications', desktop.brightness_notifications, ((),), ()), ('display', 'desktop-adt-identity', desktop.adt_identity, ((),), ()), + ('display', 'desktop-identity-trace-budget', desktop.identity_trace_budget, ((),), ()), ('display', 'desktop-identity-keys', desktop.identity_keys, ((),), ('-Wno-unused-function',)), ('display', 'desktop-panel-identity', lambda r: desktop.panel_identity(r, output), ((),), ()), ('display', 'desktop-powerlog-framing', desktop.powerlog_framing, ((),), ()), From 10e863250bee930b3fad31aa2d66bf9b9a2de3b7 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 12:32:07 +1100 Subject: [PATCH 041/184] drm/apple: reserve panel trace records atomically Use atomic budget reservations for both framing and key records, so the diagnostic bound also holds if callback contexts overlap. Preserve the separate setter budgets and exercise the same fixed limits on the host. Assisted-by: LLM Signed-off-by: Ryan Murray --- Documentation/gpu/apple-dcp-t6041-panel.rst | 4 ++-- drivers/gpu/drm/apple/dcp-internal.h | 3 ++- drivers/gpu/drm/apple/iomfb.c | 7 +++---- .../selftests/drivers/gpu/apple-dcp/desktop.py | 12 ++++++++---- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/Documentation/gpu/apple-dcp-t6041-panel.rst b/Documentation/gpu/apple-dcp-t6041-panel.rst index 1bbb62099a9c9..79572e5099b37 100644 --- a/Documentation/gpu/apple-dcp-t6041-panel.rst +++ b/Documentation/gpu/apple-dcp-t6041-panel.rst @@ -82,8 +82,8 @@ D114 and chunked property delivery report the outer key, serialized key names, value type numbers and lengths. D000 identifies chunked delivery. Type numbers are dictionary 1, array 2, integer 4, string 9, blob 10 and boolean 11. Values are never printed. Each property walk stops at depth 4 or 64 objects, and the -DCP stops after 128 key records per callback class (512 total), with bounded -framing/status lines in addition. D552, D561, D114 and chunked delivery have +DCP stops after 128 diagnostic records per callback class (512 total), with bounded +inspection-status lines in addition. Atomic reservations include framing records. D552, D561, D114 and chunked delivery have separate budgets so periodic statistics cannot hide the panel setters. The switch defaults off. Malformed serialization only stops inspection. diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index a7079c1bbb814..7a3a969fd7e76 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -5,6 +5,7 @@ #define __APPLE_DCP_INTERNAL_H__ #include +#include #include #include #include @@ -248,7 +249,7 @@ struct dcp_desktop_state { struct dcp_display_metadata panel_adt_identity; u64 panel_metadata_generation; bool panel_metadata_valid; - u32 identity_log_count[4]; + atomic_t identity_log_count[4]; u8 panel_raw_edid[4096]; size_t panel_raw_edid_size; /* RX records an unsupported callback until the modeset RPC has drained. */ diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 40e2b797b7d60..6d38d96ed1365 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -47,7 +47,7 @@ MODULE_PARM_DESC(panel_identity_debug, "Bounded desktop panel property keys and struct dcp_identity_trace { struct apple_dcp *dcp; - u32 *count; + atomic_t *count; }; static void dcp_identity_key_log(void *data, const char *key, u32 key_len, @@ -58,12 +58,11 @@ static void dcp_identity_key_log(void *data, const char *key, u32 key_len, char safe[65]; u32 i; - if (*trace->count >= 128) + if (!atomic_add_unless(trace->count, 1, 128)) return; for (i = 0; i < min_t(u32, key_len, sizeof(safe) - 1); i++) safe[i] = key[i] >= 0x20 && key[i] <= 0x7e ? key[i] : '?'; safe[i] = 0; - ++*trace->count; dev_info(dcp->dev, "panel identity depth=%u key=%s type=%u size=%u\n", depth, safe, type, size); } @@ -81,7 +80,7 @@ void dcp_trace_panel_property(struct apple_dcp *dcp, int tag, const char *key, trace.dcp = dcp; trace.count = &dcp->desktop->identity_log_count[slot]; /* Periodic statistics cannot consume the panel setters' budget. */ - if (*trace.count >= 128) + if (!atomic_add_unless(trace.count, 1, 128)) return; dev_info(dcp->dev, "panel identity callback=D%03d bytes=%zu null=%d\n", tag, len, !blob); diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py index 97a017aea8524..d36274f0e3117 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py @@ -848,10 +848,14 @@ def identity_trace_budget(root): #include #include typedef uint32_t u32; +typedef struct {int value;} atomic_t; +static int atomic_read(const atomic_t *n) {return n->value;} +static bool atomic_add_unless(atomic_t *n,int add,int unless) +{if(n->value==unless)return false;n->value+=add;return true;} #define READ_ONCE(x) (x) #define min_t(t,a,b) ((t)(a)<(t)(b)?(t)(a):(t)(b)) #define DCP_FIRMWARE_V_26_6_DESKTOP 5 -struct apple_dcp {int fw_compat;bool has_panel;void *dev;struct {u32 identity_log_count[4];} *desktop;}; +struct apple_dcp {int fw_compat;bool has_panel;void *dev;struct {atomic_t identity_log_count[4];} *desktop;}; static bool panel_identity_debug; static unsigned messages; static char output[256]; @@ -884,18 +888,18 @@ def identity_trace_budget(root): dcp_trace_panel_property(&d,561,"Property",8,secret,sizeof(secret));assert(!messages); d.has_panel=true; for(unsigned i=0;i<100;i++)dcp_trace_panel_property(&d,114,"Statistics",10,secret,sizeof(secret)); - assert(desktop.identity_log_count[2]==128 && !desktop.identity_log_count[0] && !desktop.identity_log_count[1]); + assert(atomic_read(&desktop.identity_log_count[2])==128 && !atomic_read(&desktop.identity_log_count[0]) && !atomic_read(&desktop.identity_log_count[1])); unsigned before=messages; dcp_trace_panel_property(&d,552,"Panel",5,secret,sizeof(secret));assert(messages>before); dcp_trace_panel_property(&d,561,"Panel",5,secret,sizeof(secret)); - assert(desktop.identity_log_count[0] && desktop.identity_log_count[1]); + assert(atomic_read(&desktop.identity_log_count[0]) && atomic_read(&desktop.identity_log_count[1])); assert(strstr(output,"Key?") && !strstr(output,secret)); for(unsigned i=0;i<100;i++) { dcp_trace_panel_property(&d,552,"Panel",5,secret,sizeof(secret)); dcp_trace_panel_property(&d,561,"Panel",5,secret,sizeof(secret)); dcp_trace_panel_property(&d,0,"Chunks",6,secret,sizeof(secret)); } - for(unsigned i=0;i<4;i++)assert(desktop.identity_log_count[i]==128); + for(unsigned i=0;i<4;i++)assert(atomic_read(&desktop.identity_log_count[i])==128); before=messages;dcp_trace_panel_property(&d,561,"Panel",5,NULL,0);assert(messages==before); assert(messages<=520); return 0; From 47f1d5cafd23a6a7689f2038949f17931e4ff53f Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 12:57:04 +1100 Subject: [PATCH 042/184] drm/apple: make the panel EDID date and white point conformant libdisplay-info found a reserved year and an unset white point in the minimal panel EDID even though its identity getters were non-null. Use EDID 1.4 model year 2024 for the J616 family and nominal D65 reference white. Do not claim a manufacture date, calibrated primaries or gamma. Check those encoded fields in the host fixture; keep the independent consumer strict rather than suppressing its diagnostics. Assisted-by: LLM Signed-off-by: Ryan Murray --- Documentation/gpu/apple-dcp-t6041-panel.rst | 4 +++- drivers/gpu/drm/apple/dcp-display-metadata.c | 7 +++++++ tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py | 3 +++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Documentation/gpu/apple-dcp-t6041-panel.rst b/Documentation/gpu/apple-dcp-t6041-panel.rst index 79572e5099b37..564d7e3bb38c9 100644 --- a/Documentation/gpu/apple-dcp-t6041-panel.rst +++ b/Documentation/gpu/apple-dcp-t6041-panel.rst @@ -118,7 +118,9 @@ serial zero, represented by the non-empty serial descriptor ``00000000``. This generic identity does not claim to identify an individual panel. The preferred representable timing and range limits come from the retained DCP TimingElements catalog. Exact mode enumeration continues to use that catalog. -No new mode, color capability or machine serial is invented. The ADT reader, +The EDID records model year 2024 (not a manufacture date) and a nominal D65 +reference white point required by EDID parsing. No calibrated primaries, gamma, +new mode or machine serial is claimed. The ADT reader, EDID fallback and real EDID setters are restricted to desktop 26.6; T8140 continues to use its existing path. diff --git a/drivers/gpu/drm/apple/dcp-display-metadata.c b/drivers/gpu/drm/apple/dcp-display-metadata.c index 6355e697a653e..f04f651a4ac57 100644 --- a/drivers/gpu/drm/apple/dcp-display-metadata.c +++ b/drivers/gpu/drm/apple/dcp-display-metadata.c @@ -293,12 +293,19 @@ const struct drm_edid *dcp_panel_edid(const struct dcp_display_metadata *metadat block.product_id.manufacturer_name = cpu_to_be16(manufacturer); block.product_id.product_code = cpu_to_le16(metadata->product); block.product_id.serial_number = cpu_to_le32(metadata->serial); + /* J616 is a 2024 model; this is not a panel manufacture date. */ + block.product_id.week_of_manufacture = 0xff; + block.product_id.year_of_manufacture = 2024 - 1990; block.version = 1; block.revision = 4; block.input = DRM_EDID_INPUT_DIGITAL; block.width_cm = dimensions.width_cm; block.height_cm = dimensions.height_cm; block.gamma = 0xff; + /* Nominal D65 reference white, without calibrated primaries or gamma. */ + block.white_x = 320 >> 2; + block.white_y = 337 >> 2; + block.blue_white_lo = (320 & 3) << 2 | (337 & 3); block.features = DRM_EDID_FEATURE_PREFERRED_TIMING; memset(block.standard_timings, 1, sizeof(block.standard_timings)); snprintf(name, sizeof(name), "%.3s %04x", metadata->manufacturer, metadata->product); diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py index d36274f0e3117..55212094c14fa 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py @@ -478,6 +478,9 @@ def panel_identity(root, output=None): assert(bytes[8]==0x52 && bytes[9]==0x74 && bytes[10]==42 && !bytes[11]); assert(bytes[12]==0x78 && bytes[13]==0x56 && bytes[14]==0x34 && bytes[15]==0x12); assert(bytes[18]==1 && bytes[19]==4 && bytes[21]==35 && bytes[22]==22 && bytes[23]==0xff); + assert(bytes[16]==0xff && bytes[17]==34); + assert(((unsigned)bytes[33]<<2 | ((bytes[26]>>2)&3))==320); + assert(((unsigned)bytes[34]<<2 | (bytes[26]&3))==337); assert(((unsigned)bytes[66] | (unsigned)(bytes[68] >> 4) << 8)==345); assert(((unsigned)bytes[67] | (unsigned)(bytes[68] & 15) << 8)==223); assert(bytes[72+3]==EDID_DETAIL_MONITOR_NAME && bytes[90+3]==EDID_DETAIL_MONITOR_SERIAL); From be1528aa91837aa272fcb1325e3c51bbf0340086 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 13:06:15 +1100 Subject: [PATCH 043/184] media: apple: isp: add IMX958 P010 preview and config discovery MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Port the processed config-5 preview command sequence from our m1n1 J616s work, with contiguous V4L2 P010 strides and pool geometry. Query all advertised H17 sensor configs and retain the board defaults while appending consistent output modes. Keep NV12 the default and scope the new path to T6040/IMX958. Retire output and metadata leases after the report ACK, restart an empty P010 queue on requeue, bound ring servicing and latch malformed or timed-out command replies. Add command-byte, geometry, ownership and ring-wrap coverage. These source corrections still require sustained capture validation on 26.6.2. Builds on Jonathan Goyvaerts’ T6040 camera support in PR #384. Protocol reference: our m1n1 PR #20 at 9be380c8a5 and Linux PR #231 at a4e7ee86bc5e. No firmware payload is included. Assisted-by: LLM Signed-off-by: Ryan Murray --- .../media/drivers/apple-isp-j616s.rst | 258 +++++++++++++++ .../driver-api/media/drivers/index.rst | 1 + drivers/media/platform/apple/isp/Kconfig | 5 +- .../media/platform/apple/isp/isp-cam-test.c | 295 ++++++++++++++++++ drivers/media/platform/apple/isp/isp-cam.c | 141 ++++++++- drivers/media/platform/apple/isp/isp-cmd.c | 60 +++- drivers/media/platform/apple/isp/isp-cmd.h | 14 + drivers/media/platform/apple/isp/isp-drv.c | 1 + drivers/media/platform/apple/isp/isp-drv.h | 55 ++++ drivers/media/platform/apple/isp/isp-fw.c | 1 + .../media/platform/apple/isp/isp-ipc-test.c | 27 ++ drivers/media/platform/apple/isp/isp-ipc.c | 28 +- .../media/platform/apple/isp/isp-v4l2-test.c | 228 ++++++++++++++ drivers/media/platform/apple/isp/isp-v4l2.c | 229 ++++++++++---- drivers/media/platform/apple/isp/isp-v4l2.h | 1 + 15 files changed, 1265 insertions(+), 79 deletions(-) create mode 100644 Documentation/driver-api/media/drivers/apple-isp-j616s.rst create mode 100644 drivers/media/platform/apple/isp/isp-cam-test.c diff --git a/Documentation/driver-api/media/drivers/apple-isp-j616s.rst b/Documentation/driver-api/media/drivers/apple-isp-j616s.rst new file mode 100644 index 0000000000000..7413c0e866460 --- /dev/null +++ b/Documentation/driver-api/media/drivers/apple-isp-j616s.rst @@ -0,0 +1,258 @@ +.. SPDX-License-Identifier: GPL-2.0-only + +J616s ISP modes and capture protocol +================================== + +Sources and scope +----------------- + +The source reference is Ryan's m1n1 PR 20, branch ``j616s`` at +``9be380c8a5a94609fdbe62d693c7b2121478ad8f``: + +* ``proxyclient/m1n1/fw/isp/isp_sensor.py`` discovers IMX958 and queries + ``CH_CAMERA_CONFIG_GET`` for every configuration reported by ``CH_INFO_GET``. +* ``isp_capture.py:T6040PreviewConfiguration`` contains the successful + processed capture command sequence. ``isp_pixels.py`` decodes its P010 + output as video-range BT.709. +* ``docs/j616s-bringup.md`` records all 13 configuration queries, completed + 1280x720 P010 captures (including an 80-frame ring-wrap trial) and a later + single completed 1920x1080 P010 frame. Intermittent startup/streaming stalls + remain unresolved. Neither the requested AE limits nor the host capture + rate is a measured sensor FPS. + +Both processed output sizes use firmware configuration **5**, whose reported +input dimensions are 1920x2160. The branch does not establish that this input +height is an independently deliverable 2160-row colour image, nor that config +5 is the highest-resolution configuration the sensor offers. It contains no +completed colour result from another configuration. RAW trials produced +stable pixels after stop but no completed RAW report; combined RAW/YUV was +refused at the allocation limit. No sensor full-resolution claim follows. + +Linux PR 231, branch ``j616s/linux-integration-next`` at +``a4e7ee86bc5eed8ff9dab61ef9656adfe000b207``, has the corresponding +``isp-profile.[ch]`` and IPC/buffer preparation. Its T6040 profile is pinned +to **25G76** and remains gated. It is a protocol reference, not an admission +profile for the current firmware. + +This driver change builds on the working T6040 H17 implementation in Linux +PR 384, now in ``integration/aurora``. That PR reports 26.6.x firmware capture +at 1920x1080 NV12 and use of config-5 1080p/720p presets. P010 and additional +firmware-derived output modes still need capture validation on 26.6.2. +Only our source and synthetic protocol tests are included; no Apple binary, +setfile contents, calibration table or firmware image is added. + +Mode and format selection +------------------------- + +On T6040/IMX958, probe queries every advertised configuration, bounded at 64, +using the 0x120-byte H17 ``CH_CAMERA_CONFIG_GET`` exchange. The complete list +is available through dynamic debug at probe and cached for +``VIDIOC_LOG_STATUS`` (``v4l2-ctl --log-status``), without sending commands +during a stream. The log includes +input/output dimensions and the sensor clock word; that word is not FPS. + +The board's existing config-5 1920x1080 and 1280x720 presets stay first and +NV12 stays the default. Additional distinct output sizes are appended only +when the reply's 16-bit and repeated 32-bit dimensions agree, the dimensions +are nonzero, output is even and within input, and output is at most 16384 +in either dimension. The full reported output becomes the crop and output +size for its own config index. Input dimensions alone never add a mode. +Thus a larger consistent firmware output can be selected by ``S_FMT`` and +listed by ``ENUM_FRAMESIZES``; no invented full-resolution preset is added. +These consistency checks cannot establish image quality, supported FPS or +whether a configuration needs additional sensor-specific commands. + +``V4L2_PIX_FMT_P010`` is offered only for T6040/IMX958. The config-5 +1280x720 and 1920x1080 processed previews are enabled when +the runtime input geometry matches the source profile. An unspecified P010 +size selects 1280x720 first. Larger P010 modes require a consistent firmware +output reply. P010 uses two little-endian 16-bit component planes with ten useful +MSB-aligned bits, and equal 64-byte-aligned Y/UV strides. UV immediately +follows Y, as required for contiguous P010. The optional multi-planar V4L2 +API also reports P010 as **one memory plane** containing both component +planes. The firmware still receives two component addresses. + +At 1920x1080, stride is 3840, Y is 4147200 bytes, UV is 2073600 bytes and +``sizeimage`` is 6220800. At 1280x720, stride is 2560 and ``sizeimage`` is +2764800. The proxy's independently page-aligned plane extents were +0x3f8000/0x1fc000 at 1080p and 0x1c4000/0xe4000 at 720p; inserting those +inter-plane gaps into a V4L2 contiguous P010 buffer would misdescribe it. +Linux passes its actual contiguous extents to the rendered pool instead. + +P010 uses the proxy's eight metadata slots (0x8000 bytes each) and two +rendered slots. The queue limits userspace to two rendered buffers, matching +the pool capacity. Completion matches both zero-extended low-32-bit plane +addresses, plane count, pool 9 and a fresh 64-bit submission tag; an old tag +cannot release a new lease. Buffer completion is published to videobuf2 +only after the firmware report ACK and doorbell have been published. +NV12 pool sizes and the T8140/Neo formats, presets and command path retain +their existing behavior. + +Endpoints +--------- + +These are the seven shared-memory ISP channels, not AOP/RTKit endpoints. +The table records the 25G76 source fixture. The current driver negotiates +channel names, types, sources, counts and 64-bit addresses on each boot; +it does not impose the captured ring addresses or counts on 26.6.2. + +================= ==== ====== ===== ======================================= +Channel Type Source Slots Purpose +================= ==== ====== ===== ======================================= +TERMINAL 2 0 768 Firmware log reports / terminal ACKs +IO 0 1 8 Host commands and in-place replies +DEBUG 0 1 8 Discovered; no capture commands sent +BUF_H2T 0 2 64 Host metadata/output descriptor batches +BUF_T2H 1 3 64 Firmware buffer completion/return reports +SHAREDMALLOC 1 3 8 Firmware allocation/free requests +IO_T2H 1 3 8 Discovered; no capture handler installed +================= ==== ====== ===== ======================================= + +A source word selects a doorbell bit, so IO and DEBUG share bit 1; the +three reply channels share bit 3. Each channel descriptor is 0x100 bytes +with a u64 ring address at 0x50. Each slot is 0x40 bytes / eight u64 words. +Publish the payload before arg0, which transfers slot ownership. The +existing IPC parser bounds rings against the allocation, descriptor table, +boot/command storage and other rings. The threaded IRQ services allocations, +terminal messages and buffer reports while a command waits for its ACK. + +Command stream +-------------- + +IO commands have a zero/status u32 followed by the opcode u32, then the +parameters below. Sizes include that eight-byte header. ``ch`` is zero. + +================ =================================== ==== =================== +Opcode Command Size Parameters +================ =================================== ==== =================== +0x0003 CONFIG_GET 0x1c Camera channel count +0x3206 SET_DSID_CLR_MULTI_BC_REG_BASE 0x50 8 slots, range 0x2fc +0x0000 START 0x0c Proxy 0x11, Linux 0 +0x010d CH_INFO_GET 0x190 ch +0x0106 CH_CAMERA_CONFIG_GET 0x120 ch, index (all) +0x0024 FLICKER_SENSOR_SET 0x0c 0 +0x013b CH_SBS_ENABLE 0x10 ch, 1 +0x0107 CH_CAMERA_CONFIG_SELECT 0x10 ch, selected config +0x010e CH_BUFFER_RECYCLE_MODE_SET 0x10 ch, 1 (empty only) +0x010f CH_BUFFER_RECYCLE_START 0x0c ch +0x080c CH_CROP_SCL1_SET 0x1c ch, x, y, width, height +0x0b09 CH_OUTPUT_CONFIG_SCL1_SET 0x38 ch, output geometry +0x0208 CH_AE_FRAME_RATE_MAX_SET 0x10 ch, fps * 256 +0x020a CH_AE_FRAME_RATE_MIN_SET 0x10 ch, min_fps * 256 +0x0117 CH_BUFFER_POOL_CONFIG_SET (meta) 0x9c ch, pool 0, count 8 +0x0117 CH_BUFFER_POOL_CONFIG_SET (output) 0x9c ch, pool 9, count 2 +0x0125 CH_LOCAL_RAW_BUFFER_ENABLE 0x10 ch, 1 +0x0b0d CH_PREVIEW_STREAM_SET 0x10 ch, 1 +0x0138 CH_MASTER_SLAVE_SYNC_MODE_SET 0x10 ch, 0 +0x0100 CH_START 0x0c ch +0x0101 CH_STOP 0x0c ch +0x0104 CH_BUFFER_RETURN 0x0c ch +================ =================================== ==== =================== + +The DSID bases are 0x2201d4000, 0x2221d4000, 0x2241d4000, 0x2261d4000, +then four U64_MAX sentinels. Linux retains its existing boot-time PMP and +logging commands in addition to discovery; it does not replay the proxy's +boot allocations or private power orchestration. + +For config 5, crop is (0, 0, output width, output height). The output command +parameters after ch are width, height, Rec.709=1, **format=0x12**, Y stride, +UV stride, 0, 0, height, 0, width. NV12 continues to use format 0. +Metadata pool sizes/strides are 0x8000, one data block; the output pool +contains actual Y/UV sizes and strides, two data blocks, no compression. + +Before CH_START, BUF_H2T submits metadata and output buffers as a 16-byte +header (type 1, count) and 64-byte descriptors. Each descriptor contains +four u64 addresses, four u32 flags (0x40000000 for each used plane), +u32 plane count, u32 pool and u64 tag. BUF_T2H returns completion reports; +render status 0x10000000 means a completed frame. Submission status is +0x30000000 and return ACK status is 0x80000000. Requeue only a returned lease. +CH_STOP and CH_BUFFER_RETURN retire the stream; resident firmware remains +running for the existing reopen path. + +The proxy used AE max/min 7680/3840 (30/15 fps). Linux retains its V4L2 rate +and exposure-priority controls: the default holds 30/30, and enabling +``exposure_auto_priority`` allows 30/15. These are requested limits, not FPS +measurements. P010 follows the minimal proxy sequence without adding +setfile-load, CNR, MBNR, SIF-format or temporal-filter commands; NV12 keeps +its existing calibrated path. A previously loaded setfile may still be +resident when switching streams. + +26.6.2 comparison +----------------- + +The 25G76 firmware UUID differs from the 26.6.2 image. Matching struct sizes +is source-level ABI evidence, not a new live qualification of P010. + +* Boot block: both use 0x290 bytes, IPC address at 0x08, shared base/size at + 0x10/0x18, extra heap at 0x20/0x28, platform at 0x30, queue offset+1 at + 0x50, 0x40 at 0x68, camera scheme at 0xd0 and the no-optical-card flag + at 0x120. The current H17 path checks descriptor flag bit 1. +* Memory: 25G76 pinned TEXT/DATA sizes 0xca8000/0x1344000, remap 1<<40, + shared base 0x1fec000, and a 256 MiB shared aperture. Linux uses the + bootloader's current TEXT/DATA/main-memory reservations, backs the missing + main-memory tail, and retains T6040's **0xde600000** shared-size descriptor. + No pinned segment size, heap request, IOVA or firmware hash is ported. +* IPC: 0x100-byte descriptors, 0x40-byte slots, 64-bit addresses and the + seven names agree. Counts and addresses remain negotiated, with the + existing overlap checks retained. The driver uses its three-DART IOMMU + domain and bootloader address-filter setup, not proxy DART table writes. +* Commands: DSID's 0x50-byte eight-slot layout, + CH_INFO_GET's 0x190-byte record, scaler-1 crop/output, 0x9c-byte pools, + local RAW, preview, sync and channel start/stop layouts agree with the + current H17 builders. START is an important parameter difference: the + proxy sends 0x11, while the working Linux H17 boot path sends 0. Linux + retains its current boot command; issuing START a second time after + selecting P010 would violate the proxy's guarded startup lifecycle. The new config query explicitly exchanges 0x120, + rather than the unused legacy 0xdc current-config helper. Header/channel/ + index checks and duplicated geometry checks guard decoding. The H17 + appended bytes remain uninterpreted. +* Metadata: CH_INFO_GET reports 0x4d00 on both paths. The proxy rounds its + metadata backing/pool to 0x8000; P010 does the same even on a 4 KiB host. + NV12 retains the current driver's 0x4d00 pool and 16-buffer capacity. + Neither path configures a T6040 capture-metadata pool or capture watchdog. +* Startup: Linux retains its once-per-boot resident-firmware admission, + current reset-vector check and H17 handshake; the 25G76 SHA/UUID admission + and recovery model from PR 231 are not substituted. Linux's command + timeout/IRQ service remain in use; the proxy's 30-second inactivity / + 40-second wall startup budget is not copied. + +Validation on hardware must record the current config list first, then +exercise both P010 presets, requeue, stop/reopen and a longer run. If a larger +firmware output appears, test its format and command requirements separately. +Do not label input dimensions, a successful query, or a requested rate as a +completed full-resolution stream or measured FPS. + +Open qualification and performance checks +----------------------------------------- + +``tools/testing/selftests/media/apple-isp-stress.c`` exercises long runs, +repeated start/stop and format/size switches through MMAP. It logs completed +frames, delivered FPS, maximum inter-frame gap, sequence gaps and buffer errors. +Use the established nominal rate as its explicit rate argument. A driver-generated +sequence is not a firmware frame counter, so timing and sequence checks alone +cannot prove the sensor dropped no frames internally. + +The P010 path requeues even the last returned lease into an otherwise empty +queue, publishes completion ACKs before releasing output or metadata leases, +acquires message validity before reading payload and bounds each IRQ ring drain +to one ring traversal so terminal/allocation traffic cannot starve completions. +Command replies must match the requested reply length, zero status and original +header. A timeout or malformed ACK latches failure; no late ACK can authorize +another command. The existing forced coprocessor stop runs before uncertain +leases are released. Resident firmware then needs a reboot before another capture. +These corrections address source-visible stalls/races. They are not evidence +that the intermittent stalls observed by the USB proxy have been reproduced +or cured on the current firmware. + +No verified IMX958 RAW Bayer packing/order, RAW completion path or nominal FPS +for every configuration is present in the public source. The recorded RAW trial +reads stable memory after stop but receives no completed RAW report. A Bayer +FOURCC cannot be chosen from that evidence. The RAW format and an exhaustive +per-config FPS table remain open pending the original capture evidence and +runtime command replies; neither is inferred from the P010 output or input height. +The 30/15 values in the preview are AE requests, not per-mode nominal measurements. + +The T6040 device tree's current fixed TEXT/DATA reservations should eventually +come from a bootloader handoff describing the loaded image and mapping lifetimes. +They are firmware-build-specific. This change does not implement that handoff +or replace those reservations with 25G76 addresses. diff --git a/Documentation/driver-api/media/drivers/index.rst b/Documentation/driver-api/media/drivers/index.rst index 7f6f3dcd5c90c..f1ead3c9e389f 100644 --- a/Documentation/driver-api/media/drivers/index.rst +++ b/Documentation/driver-api/media/drivers/index.rst @@ -12,6 +12,7 @@ Video4Linux (V4L) drivers .. toctree:: :maxdepth: 5 + apple-isp-j616s bttv-devel cx2341x-devel cx88-devel diff --git a/drivers/media/platform/apple/isp/Kconfig b/drivers/media/platform/apple/isp/Kconfig index b51d19ee115de..7a797a1a7e1a3 100644 --- a/drivers/media/platform/apple/isp/Kconfig +++ b/drivers/media/platform/apple/isp/Kconfig @@ -24,8 +24,9 @@ config VIDEO_APPLE_ISP_KUNIT_TEST depends on KUNIT=y || VIDEO_APPLE_ISP=m default KUNIT_ALL_TESTS help - Builds unit tests for the videobuf2 queue set-up and for the - shared memory the firmware frees into the Apple ISP driver. They + Builds unit tests for queue setup, P010 layouts and buffer identity, + firmware sensor geometry, the capture command stream and shared + memory retirement. The command tests stub the IPC transport and need no hardware. Only useful for kernel developers running KUnit. If unsure, say N. diff --git a/drivers/media/platform/apple/isp/isp-cam-test.c b/drivers/media/platform/apple/isp/isp-cam-test.c new file mode 100644 index 0000000000000..aef545b27c051 --- /dev/null +++ b/drivers/media/platform/apple/isp/isp-cam-test.c @@ -0,0 +1,295 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Synthetic geometry checks: no camera firmware or setfile data. */ +#include +#include +#include +#include "isp-ipc.h" + +static void isp_test_sensor_geometry(struct kunit *test) +{ + struct cmd_ch_camera_config config = { + .in_width = 1920, .in_height = 2160, + .out_width = 1920, .out_height = 1080, + .in_width2 = 1920, .in_height2 = 2160, + .out_width2 = 1920, .out_height2 = 1080, + }; + + KUNIT_EXPECT_TRUE(test, isp_sensor_config_usable(&config)); + /* A future larger output is accepted only when both descriptions agree. */ + config.in_width = 4096; + config.in_width2 = 4096; + config.in_height = 3072; + config.in_height2 = 3072; + config.out_width = 4096; + config.out_width2 = 4096; + config.out_height = 3072; + config.out_height2 = 3072; + KUNIT_EXPECT_TRUE(test, isp_sensor_config_usable(&config)); + config.out_height2 = 2160; + KUNIT_EXPECT_FALSE(test, isp_sensor_config_usable(&config)); + config.out_height2 = 3072; + config.in_height2 = 2160; + KUNIT_EXPECT_FALSE(test, isp_sensor_config_usable(&config)); + config.in_height2 = 3072; + config.out_width = 4097; + config.out_width2 = 4097; + KUNIT_EXPECT_FALSE(test, isp_sensor_config_usable(&config)); + config.out_width = 0; + config.out_width2 = 0; + KUNIT_EXPECT_FALSE(test, isp_sensor_config_usable(&config)); + config.out_width = 8192; + config.out_width2 = 8192; + KUNIT_EXPECT_FALSE(test, isp_sensor_config_usable(&config)); +} + +struct isp_test_command { + u32 words[39]; + u32 size; +}; + +/* Derived from the proxy command fixture, with contiguous V4L2 plane sizes. */ +static const struct isp_test_command isp_test_preview_commands[] = { + {{0, 0x24, 0}, 0xc}, + {{0, 0x13b, 0, 1}, 0x10}, + {{0, 0x107, 0, 5}, 0x10}, + {{0, 0x10e, 0, 1}, 0x10}, + {{0, 0x10f, 0}, 0xc}, + {{0, 0x80c, 0, 0, 0, 1280, 720}, 0x1c}, + {{0, 0xb09, 0, 1280, 720, 1, 0x12, 2560, 2560, 0, 0, 720, 0, 1280}, 0x38}, + {{0, 0x208, 0, 7680}, 0x10}, + {{0, 0x20a, 0, 3840}, 0x10}, + {{[1] = 0x117, [3] = 8 << 16, [4] = 0x8000, [5] = 0x8000, [37] = 1}, 0x9c}, + {{[1] = 0x117, [3] = 9 | 2 << 16, [4] = 1843200, [5] = 2560, + [12] = 921600, [13] = 2560, [37] = 2}, 0x9c}, + {{0, 0x125, 0, 1}, 0x10}, + {{0, 0xb0d, 0, 1}, 0x10}, + {{0, 0x138, 0, 0}, 0x10}, +}; + +struct isp_test_command_trace { + unsigned int count; + unsigned int fail_at; +}; + +static int isp_test_send(struct apple_isp *isp, void *args, u32 insize, + u32 outsize, int timeout) +{ + struct kunit *test = kunit_get_current_test(); + struct isp_test_command_trace *trace = test->priv; + const struct isp_test_command *expected; + + if (trace->count >= ARRAY_SIZE(isp_test_preview_commands)) { + KUNIT_FAIL(test, "unexpected extra command"); + return -EIO; + } + expected = &isp_test_preview_commands[trace->count++]; + KUNIT_EXPECT_EQ(test, insize, expected->size); + KUNIT_EXPECT_EQ(test, outsize, expected->words[1] == 0x117 ? insize : 0); + if (insize == expected->size) + KUNIT_EXPECT_MEMEQ(test, args, expected->words, insize); + if (trace->count == trace->fail_at) + return -ETIME; + return 0; +} + +static void isp_test_free_controls(void *ptr) +{ + v4l2_ctrl_handler_free(ptr); +} + +static struct apple_isp *isp_test_capture_alloc(struct kunit *test) +{ + static const struct apple_isp_hw hw = { + .gen = ISP_GEN_T6040, .fw_abi = ISP_FW_ABI_H17, + .scl1 = true, .meta_size = ISP_META_SIZE_T8140, + }; + static struct isp_preset preset = { + .index = 5, .input_dim = {1920, 2160}, + .output_dim = {1280, 720}, .crop_size = {1280, 720}, + }; + struct apple_isp *isp = kunit_kzalloc(test, sizeof(*isp), GFP_KERNEL); + struct isp_format *fmt; + int err; + + KUNIT_ASSERT_NOT_NULL(test, isp); + isp->dev = kunit_device_register(test, "apple-isp-camera-test"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, isp->dev); + isp->hw = &hw; + isp->frame_rate = 30; + fmt = isp_get_current_format(isp); + fmt->version = 0x958; + fmt->pixelformat = V4L2_PIX_FMT_P010; + fmt->preset = &preset; + fmt->strides[0] = 2560; + fmt->strides[1] = 2560; + fmt->plane_size[0] = 1843200; + fmt->plane_size[1] = 921600; + v4l2_ctrl_handler_init(&isp->ctrl_handler, 1); + err = kunit_add_action_or_reset(test, isp_test_free_controls, &isp->ctrl_handler); + KUNIT_ASSERT_EQ(test, err, 0); + isp->exposure_priority = + v4l2_ctrl_new_std(&isp->ctrl_handler, NULL, + V4L2_CID_EXPOSURE_AUTO_PRIORITY, 0, 1, 1, 1); + KUNIT_ASSERT_EQ(test, isp->ctrl_handler.error, 0); + kunit_activate_static_stub(test, isp_cmd_send, isp_test_send); + return isp; +} + +static void isp_test_preview_stream(struct kunit *test) +{ + struct apple_isp *isp = isp_test_capture_alloc(test); + struct isp_test_command_trace trace = {}; + + test->priv = &trace; + KUNIT_EXPECT_EQ(test, isp_ch_configure_capture(isp, 0), 0); + KUNIT_EXPECT_EQ(test, trace.count, ARRAY_SIZE(isp_test_preview_commands)); +} + +static void isp_test_preview_command_failures(struct kunit *test) +{ + struct apple_isp *isp = isp_test_capture_alloc(test); + struct isp_test_command_trace trace; + + test->priv = &trace; + for (unsigned int i = 1; i <= ARRAY_SIZE(isp_test_preview_commands); i++) { + trace = (struct isp_test_command_trace){ .fail_at = i }; + KUNIT_EXPECT_EQ(test, isp_ch_configure_capture(isp, 0), -ETIME); + KUNIT_EXPECT_EQ(test, trace.count, i); + } +} + +static int isp_test_config_reply(struct apple_isp *isp, void *args, u32 insize, + u32 outsize, int timeout) +{ + struct kunit *test = kunit_get_current_test(); + u32 *count = test->priv; + struct cmd_ch_camera_config_h17 *reply = isp->cmd_virt; + struct cmd_ch_camera_config *config = &reply->config; + + KUNIT_EXPECT_EQ(test, insize, 0x120); + KUNIT_EXPECT_EQ(test, outsize, 0x120); + memcpy(reply, args, sizeof(*reply)); + KUNIT_EXPECT_EQ(test, config->preset, (*count)++); + config->in_width = 1920; + config->in_height = 2160; + config->out_width = 1920; + config->out_height = 1080; + if (config->preset == 1) { + config->in_width = 4096; + config->in_height = 3072; + config->out_width = 4096; + config->out_height = 3072; + } + config->in_width2 = config->in_width; + config->in_height2 = config->in_height; + config->out_width2 = config->out_width; + config->out_height2 = config->out_height; + if (config->preset == 2) + config->out_height2 = 2160; + return 0; +} + +static void isp_test_sensor_config_list(struct kunit *test) +{ + struct apple_isp *isp = isp_test_capture_alloc(test); + struct isp_preset presets[] = { + { .index = 5, .input_dim = {1920, 2160}, + .output_dim = {1920, 1080}, .crop_size = {1920, 1080} }, + { .index = 5, .input_dim = {1920, 2160}, + .output_dim = {1280, 720}, .crop_size = {1280, 720} }, + }; + u32 count = 0; + + isp->cmd_virt = kunit_kzalloc(test, 0x120, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, isp->cmd_virt); + isp->presets = presets; + isp->num_presets = ARRAY_SIZE(presets); + test->priv = &count; + kunit_activate_static_stub(test, isp_cmd_send, isp_test_config_reply); + KUNIT_ASSERT_EQ(test, isp_ch_cache_sensor_configs(isp, 0, 6), 0); + KUNIT_EXPECT_EQ(test, count, 6); + KUNIT_EXPECT_EQ(test, isp->num_sensor_configs, 6); + KUNIT_EXPECT_EQ(test, isp->num_presets, 3); + KUNIT_EXPECT_EQ(test, isp->presets[0].index, 5); + KUNIT_EXPECT_EQ(test, isp->presets[0].output_dim.y, 1080); + KUNIT_EXPECT_TRUE(test, isp->presets[1].p010); + KUNIT_EXPECT_TRUE(test, isp->presets[2].p010); + KUNIT_EXPECT_EQ(test, isp->presets[2].index, 1); + KUNIT_EXPECT_EQ(test, isp->presets[2].output_dim.x, 4096); + KUNIT_EXPECT_EQ(test, isp->presets[2].crop_size.y, 3072); + KUNIT_EXPECT_FALSE(test, isp->sensor_configs[2].usable); + KUNIT_EXPECT_EQ(test, isp_ch_cache_sensor_configs(isp, 0, 0), -EINVAL); + KUNIT_EXPECT_EQ(test, isp_ch_cache_sensor_configs(isp, 0, 65), -EINVAL); + KUNIT_EXPECT_EQ(test, count, 6); +} + +struct isp_test_ack_state { + unsigned int fault, calls; +}; + +static int isp_test_ack_reply(struct apple_isp *isp, struct isp_channel *chan, + unsigned long timeout) +{ + struct kunit *test = kunit_get_current_test(); + struct isp_test_ack_state *state = test->priv; + + state->calls++; + chan->rsp = (struct isp_message){ .arg0 = chan->req.arg0 | ISP_IPC_FLAG_ACK, + .arg1 = chan->req.arg2 }; + switch (state->fault) { + case 1: + chan->rsp.arg2 = 1; + break; + case 2: + put_unaligned_le32(1, isp->cmd_virt); + break; + case 3: + chan->rsp.arg1 += 4; + break; + case 4: + return -ETIME; + } + return 0; +} + +static void isp_test_command_ack_failure_latches(struct kunit *test) +{ + struct apple_isp *isp = isp_test_capture_alloc(test); + struct isp_test_ack_state state = {}; + + isp->cmd_virt = kunit_kzalloc(test, 0x400, GFP_KERNEL); + isp->chan_io = kunit_kzalloc(test, sizeof(*isp->chan_io), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, isp->cmd_virt); + KUNIT_ASSERT_NOT_NULL(test, isp->chan_io); + isp->chan_io->name = "IO"; + test->priv = &state; + kunit_deactivate_static_stub(test, isp_cmd_send); + kunit_activate_static_stub(test, ipc_chan_send, isp_test_ack_reply); + KUNIT_EXPECT_EQ(test, isp_cmd_ch_camera_config_select(isp, 0, 5), 0); + for (state.fault = 1; state.fault <= 4; state.fault++) { + isp->ipc_failed = false; + state.calls = 0; + KUNIT_EXPECT_EQ(test, isp_cmd_ch_camera_config_select(isp, 0, 5), + state.fault == 4 ? -ETIME : -EIO); + KUNIT_EXPECT_TRUE(test, isp->ipc_failed); + /* A late ACK cannot admit another command on an uncertain slot. */ + KUNIT_EXPECT_EQ(test, isp_cmd_ch_camera_config_select(isp, 0, 5), -EIO); + KUNIT_EXPECT_EQ(test, state.calls, 1); + } +} + +static struct kunit_case isp_cam_test_cases[] = { + KUNIT_CASE(isp_test_command_ack_failure_latches), + KUNIT_CASE(isp_test_sensor_geometry), + KUNIT_CASE(isp_test_sensor_config_list), + KUNIT_CASE(isp_test_preview_stream), + KUNIT_CASE(isp_test_preview_command_failures), + {} +}; + +static struct kunit_suite isp_cam_test_suite = { + .name = "apple-isp-camera", + .test_cases = isp_cam_test_cases, +}; + +kunit_test_suite(isp_cam_test_suite); diff --git a/drivers/media/platform/apple/isp/isp-cam.c b/drivers/media/platform/apple/isp/isp-cam.c index a266c60609614..59f19e571bb64 100644 --- a/drivers/media/platform/apple/isp/isp-cam.c +++ b/drivers/media/platform/apple/isp/isp-cam.c @@ -10,8 +10,6 @@ #include "isp-iommu.h" #include "isp-regs.h" -#define ISP_MAX_PRESETS 32 - struct isp_setfile { u32 version; u32 magic; @@ -217,6 +215,110 @@ static int isp_check_presets(struct apple_isp *isp, u32 num_fw_presets) return 0; } +/* Do not mistake stacked sensor input rows for a processed output mode. */ +static bool isp_sensor_config_usable(const struct cmd_ch_camera_config *config) +{ + return config->in_width && config->in_height && + config->out_width && config->out_height && + config->out_width <= 16384 && config->out_height <= 16384 && + !(config->out_width & 1) && !(config->out_height & 1) && + config->out_width <= config->in_width && + config->out_height <= config->in_height && + config->in_width == config->in_width2 && + config->in_height == config->in_height2 && + config->out_width == config->out_width2 && + config->out_height == config->out_height2; +} + +static int isp_ch_cache_sensor_configs(struct apple_isp *isp, u32 ch, u32 count) +{ + struct cmd_ch_camera_config_h17 args; + struct isp_preset *presets; + unsigned int num = isp->num_presets; + int err; + + /* The extended reply is established only for the IMX958 T6040 path. */ + if (!isp_supports_p010(isp)) + return 0; + if (!count || count > ISP_MAX_SENSOR_CONFIGS) + return -EINVAL; + + presets = devm_kcalloc(isp->dev, num + count, sizeof(*presets), GFP_KERNEL); + if (!presets) + return -ENOMEM; + memcpy(presets, isp->presets, num * sizeof(*presets)); + + for (u32 i = 0; i < count; i++) { + struct cmd_ch_camera_config *config = &args.config; + struct isp_sensor_config *cached = &isp->sensor_configs[i]; + bool duplicate = false; + + err = isp_cmd_ch_camera_config_get(isp, ch, i, &args); + if (err) + return err; + if (config->opcode != (u64)CISP_CMD_CH_CAMERA_CONFIG_GET << 32 || + config->chan != ch || config->preset != i) + return -EIO; + + cached->input_dim = (struct coord){config->in_width, config->in_height}; + cached->output_dim = (struct coord){config->out_width, config->out_height}; + cached->sensor_clk = config->sensor_clk; + cached->usable = isp_sensor_config_usable(config); + dev_dbg(isp->dev, "sensor config %u: input %ux%u output %ux%u clock %u%s\n", + i, config->in_width, config->in_height, + config->out_width, config->out_height, config->sensor_clk, + cached->usable ? "" : " (unverified geometry)"); + print_hex_dump_debug("apple-isp config: ", DUMP_PREFIX_OFFSET, + 16, 4, &args, sizeof(args), false); + /* Both processed preview sizes are recorded in the proxy source. */ + for (unsigned int j = 0; j < num; j++) { + struct isp_preset *preset = &presets[j]; + bool preview = i == 5 && config->in_width == 1920 && + config->in_height == 2160 && + ((preset->output_dim.x == 1280 && + preset->output_dim.y == 720) || + (preset->output_dim.x == 1920 && + preset->output_dim.y == 1080)); + bool reported = cached->usable && + preset->output_dim.x == config->out_width && + preset->output_dim.y == config->out_height; + + if (preset->index == i && + preset->input_dim.x == config->in_width && + preset->input_dim.y == config->in_height && + !preset->crop_offset.x && !preset->crop_offset.y && + preset->crop_size.x == preset->output_dim.x && + preset->crop_size.y == preset->output_dim.y) + preset->p010 = preview || reported; + } + if (!cached->usable) + continue; + + /* Keep the board's config-5 presets, including the default, first. */ + for (unsigned int j = 0; j < num; j++) { + if (presets[j].output_dim.x == config->out_width && + presets[j].output_dim.y == config->out_height) { + duplicate = true; + break; + } + } + if (duplicate) + continue; + + presets[num++] = (struct isp_preset){ + .index = i, + .input_dim = cached->input_dim, + .output_dim = cached->output_dim, + .crop_size = cached->output_dim, + .p010 = true, + }; + } + isp->num_sensor_configs = count; + isp->presets = presets; + isp->num_presets = num; + return 0; +} + static int isp_ch_cache_sensor_info(struct apple_isp *isp, u32 ch) { struct isp_format *fmt = isp_get_format(isp, ch); @@ -264,6 +366,8 @@ static int isp_ch_cache_sensor_info(struct apple_isp *isp, u32 ch) } err = isp_check_presets(isp, args->num_presets); + if (!err) + err = isp_ch_cache_sensor_configs(isp, ch, args->num_presets); exit: kfree(args); @@ -475,7 +579,7 @@ static int isp_ch_configure_pools(struct apple_isp *isp, u32 ch) return 0; return isp_cmd_ch_buffer_pool_config_set_rendered(isp, ch, - ISP_MAX_BUFFERS, + isp_num_rendered(isp), fmt->plane_size[0], fmt->strides[0], fmt->plane_size[1], @@ -488,7 +592,13 @@ static int isp_ch_configure_capture(struct apple_isp *isp, u32 ch) bool h17 = isp->hw->fw_abi == ISP_FW_ABI_H17; int err; - isp_cmd_flicker_sensor_set(isp, 0); + err = isp_cmd_flicker_sensor_set(isp, 0); + if (isp_is_p010(isp) && err) + return err; + + /* The proxy-qualified P010 route uses the firmware defaults. */ + if (isp_is_p010(isp)) + goto select_config; /* * The setfile isn't requisite but then we don't get calibration. If @@ -504,6 +614,7 @@ static int isp_ch_configure_capture(struct apple_isp *isp, u32 ch) return err; } +select_config: err = isp_cmd_ch_sbs_enable(isp, ch, 1); if (err) return err; @@ -531,7 +642,8 @@ static int isp_ch_configure_capture(struct apple_isp *isp, u32 ch) err = isp_cmd_ch_output_config_set(isp, ch, fmt->preset->output_dim.x, fmt->preset->output_dim.y, fmt->strides, CISP_COLORSPACE_REC709, - CISP_OUTPUT_FORMAT_YUV_2PLANE); + isp_is_p010(isp) ? CISP_OUTPUT_FORMAT_P010 : + CISP_OUTPUT_FORMAT_YUV_2PLANE); if (err) return err; @@ -559,6 +671,9 @@ static int isp_ch_configure_capture(struct apple_isp *isp, u32 ch) if (err) return err; + if (isp_is_p010(isp)) + return isp_cmd_ch_master_slave_sync_mode_set(isp, ch, 0); + err = isp_cmd_ch_cnr_start(isp, ch); if (err) return err; @@ -633,7 +748,10 @@ int apple_isp_start_camera(struct apple_isp *isp) err = isp_configure_capture(isp); if (err) { dev_err(isp->dev, "failed to configure capture: %d\n", err); - apple_isp_firmware_shutdown(isp); + if (isp->ipc_failed) + apple_isp_firmware_halt(isp); + else + apple_isp_firmware_shutdown(isp); return err; } @@ -642,7 +760,10 @@ int apple_isp_start_camera(struct apple_isp *isp) void apple_isp_stop_camera(struct apple_isp *isp) { - apple_isp_firmware_shutdown(isp); + if (isp->ipc_failed) + apple_isp_firmware_halt(isp); + else + apple_isp_firmware_shutdown(isp); } /* @@ -714,9 +835,15 @@ int apple_isp_start_capture(struct apple_isp *isp) void apple_isp_stop_capture(struct apple_isp *isp) { + if (isp->ipc_failed || isp->fw_state == ISP_FW_DEAD) + return; isp_cmd_ch_stop(isp, 0); // TODO channel mask isp_cmd_ch_buffer_return(isp, isp->current_ch); /* ... until its buffers are back. */ apple_isp_wdt_stop(isp); } + +#if IS_ENABLED(CONFIG_VIDEO_APPLE_ISP_KUNIT_TEST) +#include "isp-cam-test.c" +#endif diff --git a/drivers/media/platform/apple/isp/isp-cmd.c b/drivers/media/platform/apple/isp/isp-cmd.c index fd7dcfd52899a..a525e68c230f5 100644 --- a/drivers/media/platform/apple/isp/isp-cmd.c +++ b/drivers/media/platform/apple/isp/isp-cmd.c @@ -5,30 +5,54 @@ #include "isp-drv.h" #include "isp-iommu.h" #include "isp-ipc.h" +#include + +#if IS_ENABLED(CONFIG_VIDEO_APPLE_ISP_KUNIT_TEST) +#include +#endif #define CISP_OPCODE_SHIFT 32UL #define CISP_OPCODE(x) (((u64)(x)) << CISP_OPCODE_SHIFT) #define CISP_OPCODE_GET(x) (((u64)(x)) >> CISP_OPCODE_SHIFT) #define CISP_TIMEOUT msecs_to_jiffies(3000) -#define CISP_SEND_IN(x, a) (cisp_send((x), &(a), sizeof(a), 0, CISP_TIMEOUT)) -#define CISP_SEND_INOUT(x, a) (cisp_send((x), &(a), sizeof(a), sizeof(a), CISP_TIMEOUT)) +#define CISP_SEND_IN(x, a) (isp_cmd_send((x), &(a), sizeof(a), 0, CISP_TIMEOUT)) +#define CISP_SEND_INOUT(x, a) (isp_cmd_send((x), &(a), sizeof(a), sizeof(a), CISP_TIMEOUT)) #define CISP_SEND_OUT(x, a) (cisp_send_read((x), (a), sizeof(*a), sizeof(*a))) -#define CISP_POST_IN(x, a) (cisp_send((x), &(a), sizeof(a), 0, 0)) -#define CISP_POST_INOUT(x, a) (cisp_send((x), &(a), sizeof(a), sizeof(a), 0)) +#define CISP_POST_IN(x, a) (isp_cmd_send((x), &(a), sizeof(a), 0, 0)) +#define CISP_POST_INOUT(x, a) (isp_cmd_send((x), &(a), sizeof(a), sizeof(a), 0)) -static int cisp_send(struct apple_isp *isp, void *args, u32 insize, u32 outsize, int timeout) +int isp_cmd_send(struct apple_isp *isp, void *args, u32 insize, u32 outsize, int timeout) { - struct isp_channel *chan = isp->chan_io; - struct isp_message *req = &chan->req; + struct isp_channel *chan; + struct isp_message *req; int err; +#if IS_ENABLED(CONFIG_VIDEO_APPLE_ISP_KUNIT_TEST) + KUNIT_STATIC_STUB_REDIRECT(isp_cmd_send, isp, args, insize, outsize, timeout); +#endif + if (isp->ipc_failed) + return -EIO; + chan = isp->chan_io; + req = &chan->req; + req->arg0 = isp->cmd_iova; req->arg1 = insize; req->arg2 = outsize; memcpy(isp->cmd_virt, args, insize); err = ipc_chan_send(isp, chan, timeout); + if (isp_is_p010(isp)) { + struct isp_message *rsp = &chan->rsp; + + if (!err && timeout && + (rsp->arg1 != outsize || rsp->arg2 || rsp->arg3 || + rsp->arg4 || rsp->arg5 || rsp->arg6 || rsp->arg7 || + get_unaligned_le64(isp->cmd_virt) != get_unaligned_le64(args))) + err = -EIO; + if (err) + isp->ipc_failed = true; + } if (err) { u64 opcode; memcpy(&opcode, args, sizeof(opcode)); @@ -45,7 +69,7 @@ static int cisp_send_read(struct apple_isp *isp, void *args, u32 insize, u32 outsize) { /* TODO do I need to lock the iova space? */ - int err = cisp_send(isp, args, insize, outsize, CISP_TIMEOUT); + int err = isp_cmd_send(isp, args, insize, outsize, CISP_TIMEOUT); if (err) return err; @@ -165,7 +189,7 @@ int isp_cmd_set_dsid_clr_multi_bc(struct apple_isp *isp, const u64 *bases, return -EINVAL; memcpy(args.dsid_clr_base, bases, count * sizeof(*bases)); - return cisp_send(isp, &args, + return isp_cmd_send(isp, &args, offsetof(typeof(args), dsid_clr_base) + count * sizeof(*bases), 0, CISP_TIMEOUT); @@ -232,6 +256,8 @@ int isp_cmd_flicker_sensor_set(struct apple_isp *isp, u32 mode) .opcode = CISP_OPCODE(CISP_CMD_FLICKER_SENSOR_SET), .mode = mode, }; + if (isp_is_p010(isp)) + return CISP_SEND_IN(isp, args); return CISP_SEND_INOUT(isp, args); } @@ -260,6 +286,16 @@ int isp_cmd_ch_info_get(struct apple_isp *isp, u32 chan, return err; } +int isp_cmd_ch_camera_config_get(struct apple_isp *isp, u32 chan, u32 index, + struct cmd_ch_camera_config_h17 *args) +{ + memset(args, 0, sizeof(*args)); + args->config.opcode = CISP_OPCODE(CISP_CMD_CH_CAMERA_CONFIG_GET); + args->config.chan = chan; + args->config.preset = index; + return CISP_SEND_OUT(isp, args); +} + int isp_cmd_ch_camera_config_current_get(struct apple_isp *isp, u32 chan, struct cmd_ch_camera_config *args) { @@ -468,9 +504,9 @@ int isp_cmd_ch_buffer_pool_config_set(struct apple_isp *isp, u32 chan, u16 type) .opcode = CISP_OPCODE(CISP_CMD_CH_BUFFER_POOL_CONFIG_SET), .chan = chan, .type = type, - .count = ISP_MAX_BUFFERS, - .meta_size0 = size, - .meta_size1 = size, + .count = isp_num_meta(isp), + .meta_size0 = isp_is_p010(isp) ? 0x8000 : size, + .meta_size1 = isp_is_p010(isp) ? 0x8000 : size, .unk0 = 0, .unk1 = 0, .unk2 = 0, diff --git a/drivers/media/platform/apple/isp/isp-cmd.h b/drivers/media/platform/apple/isp/isp-cmd.h index 695c0a9f7ab70..a3167108b128e 100644 --- a/drivers/media/platform/apple/isp/isp-cmd.h +++ b/drivers/media/platform/apple/isp/isp-cmd.h @@ -124,6 +124,7 @@ #define CISP_COLORSPACE_REC709 0x1 #define CISP_OUTPUT_FORMAT_YUV_2PLANE 0x0 #define CISP_OUTPUT_FORMAT_YUV_1PLANE 0x1 +#define CISP_OUTPUT_FORMAT_P010 0x12 #define CISP_OUTPUT_FORMAT_RGB 0x2 #define CISP_BUFFER_RECYCLE_MODE_EMPTY_ONLY 0x1 @@ -255,6 +256,8 @@ struct cmd_flicker_sensor_set { } __packed; static_assert(sizeof(struct cmd_flicker_sensor_set) == 0xc); +int isp_cmd_send(struct apple_isp *isp, void *args, u32 insize, u32 outsize, + int timeout); int isp_cmd_start(struct apple_isp *isp, u32 mode); int isp_cmd_stop(struct apple_isp *isp, u32 mode); int isp_cmd_power_down(struct apple_isp *isp); @@ -380,6 +383,15 @@ struct cmd_ch_camera_config { } __packed; static_assert(sizeof(struct cmd_ch_camera_config) == 0xdc); +/* H17 appends 0x44 bytes to the common camera configuration record. */ +struct cmd_ch_camera_config_h17 { + struct cmd_ch_camera_config config; + u8 unknown[0x44]; +} __packed; +static_assert(sizeof(struct cmd_ch_camera_config_h17) == 0x120); +static_assert(offsetof(struct cmd_ch_camera_config, in_width) == 0x10); +static_assert(offsetof(struct cmd_ch_camera_config, out_width2) == 0xd4); + struct cmd_ch_camera_config_select { u64 opcode; u32 chan; @@ -516,6 +528,8 @@ int isp_cmd_ch_start(struct apple_isp *isp, u32 chan); int isp_cmd_ch_stop(struct apple_isp *isp, u32 chan); int isp_cmd_ch_info_get(struct apple_isp *isp, u32 chan, struct cmd_ch_info *args); +int isp_cmd_ch_camera_config_get(struct apple_isp *isp, u32 chan, u32 index, + struct cmd_ch_camera_config_h17 *args); int isp_cmd_ch_camera_config_current_get(struct apple_isp *isp, u32 chan, struct cmd_ch_camera_config *args); int isp_cmd_ch_camera_config_select(struct apple_isp *isp, u32 chan, diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c index da9d459dd8bdb..04c50011abda1 100644 --- a/drivers/media/platform/apple/isp/isp-drv.c +++ b/drivers/media/platform/apple/isp/isp-drv.c @@ -538,6 +538,7 @@ static int apple_isp_probe(struct platform_device *pdev) mutex_init(&isp->gc_lock); INIT_LIST_HEAD(&isp->bufs_pending); INIT_LIST_HEAD(&isp->bufs_submitted); + INIT_LIST_HEAD(&isp->bufs_returned); isp->wq = alloc_workqueue("apple-isp-wq", WQ_UNBOUND, 0); if (!isp->wq) { dev_err(dev, "failed to create workqueue\n"); diff --git a/drivers/media/platform/apple/isp/isp-drv.h b/drivers/media/platform/apple/isp/isp-drv.h index ed3e48eef569e..e02d675f0e998 100644 --- a/drivers/media/platform/apple/isp/isp-drv.h +++ b/drivers/media/platform/apple/isp/isp-drv.h @@ -30,6 +30,7 @@ /* used to limit the user space buffers to the buffer_pool_config */ #define ISP_MAX_BUFFERS 16 +#define ISP_MAX_SENSOR_CONFIGS 64 /* capture metadata buffers, for firmware that has that pool */ #define ISP_CAPMETA_BUFFERS 8 @@ -139,6 +140,8 @@ struct isp_preset { struct coord output_dim; struct coord crop_offset; struct coord crop_size; + /* Processed P010 geometry established by preview or a config reply. */ + bool p010; }; struct apple_isp_hw { @@ -255,9 +258,17 @@ enum isp_sensor_id { ISP_VD56G0_6222_01, }; +struct isp_sensor_config { + struct coord input_dim; + struct coord output_dim; + u32 sensor_clk; + bool usable; +}; + struct isp_format { enum isp_sensor_id id; u32 version; + u32 pixelformat; struct isp_preset *preset; unsigned int num_planes; u32 strides[VB2_MAX_PLANES]; @@ -274,6 +285,9 @@ struct apple_isp { u32 cam_connections_scheme; struct isp_preset *presets; int num_presets; + /* Read-only sensor configuration replies, cached before video registration. */ + unsigned int num_sensor_configs; + struct isp_sensor_config sensor_configs[ISP_MAX_SENSOR_CONFIGS]; int num_channels; struct isp_format fmts[ISP_MAX_CHANNELS]; @@ -358,16 +372,25 @@ struct apple_isp { spinlock_t buf_lock; struct list_head bufs_pending; struct list_head bufs_submitted; + struct list_head bufs_returned; + u64 buffer_tag; + unsigned long meta_returned; + /* A timed-out command leaves slot ownership ambiguous until reboot. */ + bool ipc_failed; }; struct isp_chan_ops { int (*handle)(struct apple_isp *isp, struct isp_channel *chan); + /* Called after the response slot and its doorbell are published. */ + void (*complete)(struct apple_isp *isp); }; struct isp_buffer { struct vb2_v4l2_buffer vb; struct list_head link; struct isp_surf surfs[VB2_MAX_PLANES]; + u64 tag; + enum vb2_buffer_state return_state; }; #define to_isp_buffer(x) container_of((x), struct isp_buffer, vb) @@ -395,4 +418,36 @@ enum { ((isp)->hw->capture_meta_size ? ISP_CAPMETA_BUFFERS : 0) #define isp_get_current_format(isp) (isp_get_format(isp, isp->current_ch)) +static inline bool isp_supports_p010(struct apple_isp *isp) +{ + return isp->hw->gen == ISP_GEN_T6040 && + isp_get_current_format(isp)->version == 0x958; +} + +static inline bool isp_has_p010(struct apple_isp *isp) +{ + if (!isp_supports_p010(isp)) + return false; + for (int i = 0; i < isp->num_presets; i++) { + if (isp->presets[i].p010) + return true; + } + return false; +} + +static inline bool isp_is_p010(struct apple_isp *isp) +{ + return isp_get_current_format(isp)->pixelformat == V4L2_PIX_FMT_P010; +} + +static inline unsigned int isp_num_rendered(struct apple_isp *isp) +{ + return isp_is_p010(isp) ? 2 : ISP_MAX_BUFFERS; +} + +static inline unsigned int isp_num_meta(struct apple_isp *isp) +{ + return isp_is_p010(isp) ? 8 : ISP_MAX_BUFFERS; +} + #endif /* __ISP_DRV_H__ */ diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c index 30ff704ef193f..d448225fceec5 100644 --- a/drivers/media/platform/apple/isp/isp-fw.c +++ b/drivers/media/platform/apple/isp/isp-fw.c @@ -212,6 +212,7 @@ static const struct isp_chan_ops sm_ops = { static const struct isp_chan_ops bt_ops = { .handle = ipc_bt_handle, + .complete = ipc_bt_complete, }; static irqreturn_t apple_isp_isr(int irq, void *dev) diff --git a/drivers/media/platform/apple/isp/isp-ipc-test.c b/drivers/media/platform/apple/isp/isp-ipc-test.c index 6521cecb4cfb6..efd24f6794a5a 100644 --- a/drivers/media/platform/apple/isp/isp-ipc-test.c +++ b/drivers/media/platform/apple/isp/isp-ipc-test.c @@ -144,7 +144,34 @@ static void isp_test_sm_free_log_bt(struct kunit *test) KUNIT_EXPECT_NULL(test, t->isp->bt_surf); } +static void isp_test_command_ring_wrap(struct kunit *test) +{ + struct isp_sm_test *t = isp_sm_test_alloc(test); + struct isp_channel *chan = t->chan; + struct isp_message *slots; + + chan->num = 8; + chan->virt = kunit_kcalloc(test, chan->num, sizeof(*slots), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, chan->virt); + slots = chan->virt; + chan->req.arg0 = 0x5000000; + isp_get_current_format(t->isp)->pixelformat = V4L2_PIX_FMT_P010; + for (unsigned int i = 0; i < 100000; i++) { + unsigned int cursor = chan->cursor; + + /* Re-publication must clear the old ACK before waiting on a wrap. */ + slots[cursor].arg0 = chan->req.arg0; + KUNIT_ASSERT_FALSE(test, chan_tx_done(t->isp, chan)); + KUNIT_ASSERT_EQ(test, chan->cursor, cursor); + slots[cursor].arg0 |= ISP_IPC_FLAG_ACK; + KUNIT_ASSERT_TRUE(test, chan_tx_done(t->isp, chan)); + KUNIT_ASSERT_EQ(test, chan->cursor, (cursor + 1) % chan->num); + } + KUNIT_EXPECT_EQ(test, chan->cursor, 0); +} + static struct kunit_case isp_ipc_test_cases[] = { + KUNIT_CASE(isp_test_command_ring_wrap), KUNIT_CASE(isp_test_sm_free_releases), KUNIT_CASE(isp_test_sm_free_alias), KUNIT_CASE(isp_test_sm_free_unknown), diff --git a/drivers/media/platform/apple/isp/isp-ipc.c b/drivers/media/platform/apple/isp/isp-ipc.c index f709f6e305763..da946f360ff5c 100644 --- a/drivers/media/platform/apple/isp/isp-ipc.c +++ b/drivers/media/platform/apple/isp/isp-ipc.c @@ -6,6 +6,10 @@ #include "isp-regs.h" #include "isp-fw.h" +#if IS_ENABLED(CONFIG_VIDEO_APPLE_ISP_KUNIT_TEST) +#include +#endif + #define ISP_IPC_FLAG_TERMINAL_ACK 0x3 /* longer firmware log lines are cut */ #define ISP_IPC_TERMINAL_MAX_LEN 512 @@ -66,7 +70,16 @@ static inline void chan_read_msg_index(struct apple_isp *isp, struct isp_channel *chan, struct isp_message *msg, u32 index) { - memcpy(msg, chan_msg_virt(chan, index), sizeof(*msg)); + if (isp_is_p010(isp)) { + u64 *slot = chan_msg_virt(chan, index); + + /* Acquire validity before consuming the firmware-owned payload. */ + msg->arg0 = READ_ONCE(*slot); + dma_rmb(); + memcpy(&msg->arg1, slot + 1, sizeof(*msg) - sizeof(msg->arg0)); + } else { + memcpy(msg, chan_msg_virt(chan, index), sizeof(*msg)); + } } static inline void chan_read_msg(struct apple_isp *isp, @@ -124,6 +137,8 @@ static int chan_handle_once(struct apple_isp *isp, struct isp_channel *chan) isp_mbox2_write32(isp, ISP_MBOX2_IRQ_DOORBELL, chan->doorbell); chan_update_cursor(chan); + if (chan->ops->complete) + chan->ops->complete(isp); return 0; } @@ -139,10 +154,12 @@ static inline bool chan_rx_done(struct apple_isp *isp, struct isp_channel *chan) int ipc_chan_handle(struct apple_isp *isp, struct isp_channel *chan) { + unsigned int budget = isp_is_p010(isp) ? chan->num : UINT_MAX; int err = 0; mutex_lock(&chan->lock); - while (1) { + /* A busy producer must not starve the other completion/control rings. */ + while (budget--) { chan_read_msg(isp, chan, &chan->req); if (chan_rx_done(isp, chan)) { err = 0; @@ -176,6 +193,11 @@ int ipc_chan_send(struct apple_isp *isp, struct isp_channel *chan, { long t; +#if IS_ENABLED(CONFIG_VIDEO_APPLE_ISP_KUNIT_TEST) + KUNIT_STATIC_STUB_REDIRECT(ipc_chan_send, isp, chan, timeout); +#endif + if (isp->ipc_failed) + return -EIO; chan_write_msg(isp, chan, &chan->req); dma_wmb(); @@ -186,6 +208,8 @@ int ipc_chan_send(struct apple_isp *isp, struct isp_channel *chan, t = wait_event_timeout(isp->wait, chan_tx_done(isp, chan), timeout); if (t == 0) { + if (isp_is_p010(isp)) + isp->ipc_failed = true; dev_err(isp->dev, "%s: timed out on request [0x%llx, 0x%llx, 0x%llx]\n", chan->name, chan->req.arg0, chan->req.arg1, diff --git a/drivers/media/platform/apple/isp/isp-v4l2-test.c b/drivers/media/platform/apple/isp/isp-v4l2-test.c index 895a6d1299c22..47f1f8900d138 100644 --- a/drivers/media/platform/apple/isp/isp-v4l2-test.c +++ b/drivers/media/platform/apple/isp/isp-v4l2-test.c @@ -7,6 +7,7 @@ #include #include +#include #include #define ISP_TEST_FRAME_SIZE 0x1000 @@ -19,6 +20,8 @@ static struct apple_isp *isp_test_alloc(struct kunit *test) KUNIT_ASSERT_NOT_ERR_OR_NULL(test, isp); mutex_init(&isp->video_lock); + spin_lock_init(&isp->buf_lock); + INIT_LIST_HEAD(&isp->bufs_returned); isp_get_current_format(isp)->total_size = ISP_TEST_FRAME_SIZE; return isp; @@ -107,7 +110,232 @@ static void isp_test_create_bufs_limit(struct kunit *test) sizes), -EINVAL); } +static const struct apple_isp_hw isp_test_t6040 = { + .gen = ISP_GEN_T6040, + .fw_abi = ISP_FW_ABI_H17, +}; + +static void isp_test_p010_format(struct kunit *test) +{ + struct apple_isp *isp = isp_test_alloc(test); + struct isp_format *fmt = isp_get_current_format(isp); + struct isp_preset preset = { .output_dim = { 1920, 1080 }, .p010 = true }; + struct v4l2_format f = {}; + unsigned int sizes[VB2_MAX_PLANES] = {}; + unsigned int nbuffers = 16, num_planes = 0; + + isp->hw = &isp_test_t6040; + fmt->version = 0x958; + KUNIT_ASSERT_EQ(test, isp_set_preset(isp, fmt, &preset, V4L2_PIX_FMT_P010), 0); + isp_get_sp_pix_format(isp, &f, fmt); + KUNIT_EXPECT_EQ(test, f.fmt.pix.pixelformat, V4L2_PIX_FMT_P010); + KUNIT_EXPECT_EQ(test, f.fmt.pix.bytesperline, 3840); + KUNIT_EXPECT_EQ(test, f.fmt.pix.sizeimage, 6220800); + KUNIT_EXPECT_EQ(test, fmt->plane_size[0], (size_t)4147200); + KUNIT_EXPECT_EQ(test, fmt->plane_size[1], (size_t)2073600); + KUNIT_EXPECT_EQ(test, f.fmt.pix.quantization, V4L2_QUANTIZATION_LIM_RANGE); + isp_get_mp_pix_format(isp, &f, fmt); + KUNIT_EXPECT_EQ(test, f.fmt.pix_mp.pixelformat, V4L2_PIX_FMT_P010); + KUNIT_EXPECT_EQ(test, f.fmt.pix_mp.num_planes, 1); + KUNIT_EXPECT_EQ(test, f.fmt.pix_mp.plane_fmt[0].sizeimage, 6220800); + + isp->vbq.drv_priv = isp; + isp_test_set_num_buffers(test, &isp->vbq, 0); + KUNIT_ASSERT_EQ(test, + isp_vb2_queue_setup(&isp->vbq, &nbuffers, &num_planes, sizes, NULL), 0); + KUNIT_EXPECT_EQ(test, nbuffers, 2); + KUNIT_EXPECT_EQ(test, num_planes, 1); + KUNIT_EXPECT_EQ(test, sizes[0], 6220800); + KUNIT_EXPECT_EQ(test, isp_num_meta(isp), 8); + + preset.output_dim = (struct coord){1280, 720}; + KUNIT_ASSERT_EQ(test, isp_set_preset(isp, fmt, &preset, V4L2_PIX_FMT_P010), 0); + KUNIT_EXPECT_EQ(test, fmt->strides[0], 2560); + KUNIT_EXPECT_EQ(test, fmt->total_size, (size_t)2764800); + /* NV12 remains the default and restores the existing pool capacity. */ + KUNIT_ASSERT_EQ(test, isp_set_preset(isp, fmt, &preset, V4L2_PIX_FMT_NV12), 0); + KUNIT_EXPECT_EQ(test, fmt->pixelformat, V4L2_PIX_FMT_NV12); + KUNIT_EXPECT_EQ(test, fmt->total_size, (size_t)1382400); + KUNIT_EXPECT_EQ(test, isp_num_rendered(isp), ISP_MAX_BUFFERS); + KUNIT_EXPECT_EQ(test, isp_num_meta(isp), ISP_MAX_BUFFERS); +} + +static void isp_test_p010_admission(struct kunit *test) +{ + static const struct apple_isp_hw neo = { .gen = ISP_GEN_T8140 }; + struct apple_isp *isp = isp_test_alloc(test); + struct isp_format *fmt = isp_get_current_format(isp); + struct isp_preset preset = { .output_dim = {1280, 720}, .p010 = true }; + struct v4l2_fmtdesc desc = { .index = 1 }; + + isp->presets = &preset; + isp->num_presets = 1; + isp->hw = &isp_test_t6040; + fmt->version = 0x958; + KUNIT_ASSERT_EQ(test, isp_enum_pixelformat(isp, &desc), 0); + KUNIT_EXPECT_EQ(test, desc.pixelformat, V4L2_PIX_FMT_P010); + desc.index = 2; + KUNIT_EXPECT_EQ(test, isp_enum_pixelformat(isp, &desc), -EINVAL); + isp->multiplanar = true; + KUNIT_ASSERT_EQ(test, isp_enum_pixelformat(isp, &desc), 0); + KUNIT_EXPECT_EQ(test, desc.pixelformat, V4L2_PIX_FMT_P010); + isp->hw = &neo; + KUNIT_EXPECT_EQ(test, isp_enum_pixelformat(isp, &desc), -EINVAL); + KUNIT_ASSERT_EQ(test, isp_set_preset(isp, fmt, &preset, V4L2_PIX_FMT_P010), 0); + KUNIT_EXPECT_EQ(test, fmt->pixelformat, V4L2_PIX_FMT_NV12); + KUNIT_EXPECT_EQ(test, fmt->strides[0], 1280); +} + +static void isp_test_p010_completion_identity(struct kunit *test) +{ + struct isp_format fmt = { .plane_size = {4147200, 2073600} }; + struct isp_buffer buf = { .surfs = {{ .iova = 0x10020000000ULL }}, .tag = 42 }; + struct isp_buflist_buffer desc = { + .iovas = {0x20000000, 0x203f4800}, + .num_planes = 2, .pool_type = CISP_POOL_TYPE_RENDERED_SCL1, .tag = 42, + }; + + KUNIT_EXPECT_TRUE(test, isp_p010_buffer_matches(&fmt, &buf, &desc)); + desc.tag--; + KUNIT_EXPECT_FALSE(test, isp_p010_buffer_matches(&fmt, &buf, &desc)); + desc.tag++; + desc.iovas[1]++; + KUNIT_EXPECT_FALSE(test, isp_p010_buffer_matches(&fmt, &buf, &desc)); + desc.iovas[1]--; + desc.iovas[0] |= BIT_ULL(40); + KUNIT_EXPECT_FALSE(test, isp_p010_buffer_matches(&fmt, &buf, &desc)); + desc.iovas[0] &= ~BIT_ULL(40); + desc.pool_type = CISP_POOL_TYPE_RENDERED; + KUNIT_EXPECT_FALSE(test, isp_p010_buffer_matches(&fmt, &buf, &desc)); + desc.pool_type = CISP_POOL_TYPE_RENDERED_SCL1; + desc.num_planes = 1; + KUNIT_EXPECT_FALSE(test, isp_p010_buffer_matches(&fmt, &buf, &desc)); +} + +static void isp_test_p010_report_defers_completion(struct kunit *test) +{ + struct apple_isp *isp = isp_test_alloc(test); + struct isp_format *fmt = isp_get_current_format(isp); + struct isp_buffer *buf = kunit_kzalloc(test, sizeof(*buf), GFP_KERNEL); + struct { + u64 type, count; + struct isp_buflist_buffer desc; + } report = { + .type = 2, .count = 1, + .desc = { .iovas = {0x20000000, 0x203f4800}, + .num_planes = 2, .pool_type = 9, .tag = 42 }, + }; + struct isp_surf report_surf = { + .iova = 0x30000000, .size = sizeof(report), .virt = &report, + }; + struct isp_channel chan = { + .req = { .arg0 = report_surf.iova, .arg1 = sizeof(report), + .arg2 = ISP_IPC_BUFEXC_FLAG_RENDER }, + }; + + KUNIT_ASSERT_NOT_NULL(test, buf); + fmt->pixelformat = V4L2_PIX_FMT_P010; + fmt->plane_size[0] = 4147200; + isp->fw_iova_mask = U64_MAX; + isp->bt_surf = &report_surf; + buf->surfs[0].iova = 0x10020000000ULL; + buf->tag = 42; + INIT_LIST_HEAD(&isp->bufs_submitted); + list_add(&buf->link, &isp->bufs_submitted); + /* There is no vb2 queue: completing it before the ACK would fault. */ + KUNIT_ASSERT_EQ(test, ipc_bt_handle(isp, &chan), 0); + KUNIT_EXPECT_TRUE(test, list_empty(&isp->bufs_submitted)); + KUNIT_EXPECT_PTR_EQ(test, + list_first_entry(&isp->bufs_returned, struct isp_buffer, link), buf); + KUNIT_EXPECT_EQ(test, buf->return_state, VB2_BUF_STATE_DONE); + KUNIT_EXPECT_EQ(test, chan.rsp.arg0, report_surf.iova | ISP_IPC_FLAG_ACK); +} + +static void isp_test_p010_mode_selection(struct kunit *test) +{ + struct apple_isp *isp = isp_test_alloc(test); + struct isp_preset presets[] = { + { .index = 5, .output_dim = {1920, 1080} }, + { .index = 5, .output_dim = {1280, 720}, .p010 = true }, + { .index = 1, .output_dim = {4096, 3072} }, + }; + + isp->hw = &isp_test_t6040; + isp_get_current_format(isp)->version = 0x958; + isp->presets = presets; + isp->num_presets = ARRAY_SIZE(presets); + KUNIT_EXPECT_PTR_EQ(test, isp_select_preset(isp, 0, 0, V4L2_PIX_FMT_P010), &presets[1]); + KUNIT_EXPECT_PTR_EQ(test, isp_select_preset(isp, 1920, 1080, V4L2_PIX_FMT_P010), + &presets[1]); + KUNIT_EXPECT_PTR_EQ(test, isp_select_preset(isp, 4096, 3072, V4L2_PIX_FMT_P010), + &presets[1]); + KUNIT_EXPECT_PTR_EQ(test, isp_select_preset(isp, 1920, 1080, V4L2_PIX_FMT_NV12), + &presets[0]); + /* Only a matching firmware output enables the larger P010 size. */ + presets[2].p010 = true; + KUNIT_EXPECT_PTR_EQ(test, isp_select_preset(isp, 4096, 3072, V4L2_PIX_FMT_P010), + &presets[2]); +} + +static int isp_test_buffer_ack(struct apple_isp *isp, struct isp_channel *chan, + unsigned long timeout) +{ + unsigned int *calls = kunit_get_current_test()->priv; + + (*calls)++; + return 0; +} + +static void isp_test_p010_last_lease_requeue(struct kunit *test) +{ + struct apple_isp *isp = isp_test_alloc(test); + struct isp_format *fmt = isp_get_current_format(isp); + struct isp_buffer *buf = kunit_kzalloc(test, sizeof(*buf), GFP_KERNEL); + struct isp_buffer *submitted; + unsigned int calls = 0; + + KUNIT_ASSERT_NOT_NULL(test, buf); + isp->hw = &isp_test_t6040; + fmt->pixelformat = V4L2_PIX_FMT_P010; + fmt->num_planes = 2; + fmt->plane_size[0] = 1843200; + fmt->plane_size[1] = 921600; + INIT_LIST_HEAD(&isp->bufs_pending); + INIT_LIST_HEAD(&isp->bufs_submitted); + isp->cmd_virt = kunit_kzalloc(test, ISP_CMD_AREA_SIZE(0), GFP_KERNEL); + isp->chan_bh = kunit_kzalloc(test, sizeof(*isp->chan_bh), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, isp->cmd_virt); + KUNIT_ASSERT_NOT_NULL(test, isp->chan_bh); + for (unsigned int i = 0; i < isp_num_meta(isp); i++) { + isp->meta_surfs[i] = kunit_kzalloc(test, sizeof(struct isp_surf), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, isp->meta_surfs[i]); + isp->meta_surfs[i]->submitted = true; + } + isp->vbq.drv_priv = isp; + buf->vb.vb2_buf.vb2_queue = &isp->vbq; + buf->surfs[0].iova = 0x20000000; + set_bit(ISP_STATE_STREAMING, &isp->state); + test->priv = &calls; + kunit_activate_static_stub(test, ipc_chan_send, isp_test_buffer_ack); + for (unsigned int i = 1; i <= 10000; i++) { + /* Both lists empty, as after the final completion was dequeued. */ + isp_vb2_buf_queue(&buf->vb.vb2_buf); + KUNIT_ASSERT_EQ(test, calls, i); + KUNIT_ASSERT_EQ(test, buf->tag, (u64)i); + KUNIT_ASSERT_TRUE(test, list_empty(&isp->bufs_pending)); + submitted = list_first_entry(&isp->bufs_submitted, struct isp_buffer, link); + KUNIT_ASSERT_PTR_EQ(test, submitted, buf); + list_del(&buf->link); + } +} + static struct kunit_case isp_v4l2_test_cases[] = { + KUNIT_CASE(isp_test_p010_last_lease_requeue), + KUNIT_CASE(isp_test_p010_report_defers_completion), + KUNIT_CASE(isp_test_p010_mode_selection), + KUNIT_CASE(isp_test_p010_format), + KUNIT_CASE(isp_test_p010_admission), + KUNIT_CASE(isp_test_p010_completion_identity), KUNIT_CASE(isp_test_queue_init), KUNIT_CASE(isp_test_reqbufs_limit), KUNIT_CASE(isp_test_create_bufs_limit), diff --git a/drivers/media/platform/apple/isp/isp-v4l2.c b/drivers/media/platform/apple/isp/isp-v4l2.c index 072b8523279c2..35c30582ab81d 100644 --- a/drivers/media/platform/apple/isp/isp-v4l2.c +++ b/drivers/media/platform/apple/isp/isp-v4l2.c @@ -21,7 +21,6 @@ #define ISP_MIN_FRAMES 2 #define ISP_MAX_PLANES 4 -#define ISP_MAX_PIX_FORMATS 2 #define ISP_BUFFER_TIMEOUT msecs_to_jiffies(1500) #define ISP_STRIDE_ALIGNMENT 64 @@ -37,8 +36,7 @@ struct isp_buflist_buffer { u32 flags[ISP_MAX_PLANES]; u32 num_planes; u32 pool_type; - u32 tag; - u32 pad; + u64 tag; } __packed; static_assert(sizeof(struct isp_buflist_buffer) == ISP_BUFLIST_DESC_SIZE); @@ -57,6 +55,35 @@ static_assert(ISP_CMD_AREA_SIZE(0) >= ISP_IPC_BUFEXC_STAT_SIZE); */ #define ISP_BUFLIST_POOL_CAPTURE_META 2 +static bool isp_p010_buffer_matches(const struct isp_format *fmt, + const struct isp_buffer *buf, + const struct isp_buflist_buffer *desc) +{ + /* H17 reports zero-extended low 32 bits, not the submitted high IOVA. */ + return desc->num_planes == 2 && + desc->pool_type == CISP_POOL_TYPE_RENDERED_SCL1 && + desc->iovas[0] == (u32)buf->surfs[0].iova && + desc->iovas[1] == (u32)(buf->surfs[0].iova + fmt->plane_size[0]) && + !desc->iovas[2] && !desc->iovas[3] && desc->tag == buf->tag; +} + +void ipc_bt_complete(struct apple_isp *isp) +{ + struct isp_buffer *buf, *tmp; + unsigned long flags, i; + + /* The firmware has received its ACK before userspace can reuse a lease. */ + spin_lock_irqsave(&isp->buf_lock, flags); + for_each_set_bit(i, &isp->meta_returned, ISP_MAX_BUFFERS) + isp->meta_surfs[i]->submitted = false; + isp->meta_returned = 0; + list_for_each_entry_safe(buf, tmp, &isp->bufs_returned, link) { + list_del(&buf->link); + vb2_buffer_done(&buf->vb.vb2_buf, buf->return_state); + } + spin_unlock_irqrestore(&isp->buf_lock, flags); +} + int ipc_bt_handle(struct apple_isp *isp, struct isp_channel *chan) { struct isp_message *req = &chan->req, *rsp = &chan->rsp; @@ -96,11 +123,14 @@ int ipc_bt_handle(struct apple_isp *isp, struct isp_channel *chan) bufd->pool_type); */ if (bufd->pool_type == 0) { - for (int j = 0; j < ARRAY_SIZE(isp->meta_surfs); j++) { + for (int j = 0; j < isp_num_meta(isp); j++) { struct isp_surf *meta = isp->meta_surfs[j]; if ((u32)bufd->iovas[0] == (u32)meta->iova) { WARN_ON(!meta->submitted); - meta->submitted = false; + if (isp_is_p010(isp)) + __set_bit(j, &isp->meta_returned); + else + meta->submitted = false; } } } else if (bufd->pool_type == ISP_BUFLIST_POOL_CAPTURE_META && @@ -114,8 +144,10 @@ int ipc_bt_handle(struct apple_isp *isp, struct isp_channel *chan) } else { list_for_each_entry_safe_reverse( buf, tmp, &isp->bufs_submitted, link) { - if ((u32)buf->surfs[0].iova == - (u32)bufd->iovas[0]) { + if (isp_is_p010(isp) ? + isp_p010_buffer_matches(isp_get_current_format(isp), + buf, bufd) : + (u32)buf->surfs[0].iova == (u32)bufd->iovas[0]) { enum vb2_buffer_state state = VB2_BUF_STATE_ERROR; @@ -126,9 +158,13 @@ int ipc_bt_handle(struct apple_isp *isp, struct isp_channel *chan) if (req->arg2 == ISP_IPC_BUFEXC_FLAG_RENDER) state = VB2_BUF_STATE_DONE; - vb2_buffer_done(&buf->vb.vb2_buf, - state); - list_del(&buf->link); + if (isp_is_p010(isp)) { + buf->return_state = state; + list_move_tail(&buf->link, &isp->bufs_returned); + } else { + vb2_buffer_done(&buf->vb.vb2_buf, state); + list_del(&buf->link); + } } } } @@ -166,7 +202,7 @@ static int isp_submit_buffers(struct apple_isp *isp, bool pre_start) bl->type = 1; spin_lock_irqsave(&isp->buf_lock, flags); - for (int i = 0; i < ARRAY_SIZE(isp->meta_surfs); i++) { + for (int i = 0; i < isp_num_meta(isp); i++) { struct isp_surf *meta = isp->meta_surfs[i]; if (meta->submitted) @@ -205,9 +241,13 @@ static int isp_submit_buffers(struct apple_isp *isp, bool pre_start) } /* One rendered pool per batch; the rest stays pending for the next. */ - while (num_rendered < ISP_MAX_BUFFERS && + while (num_rendered < isp_num_rendered(isp) && (buf = list_first_entry_or_null(&isp->bufs_pending, struct isp_buffer, link))) { + if (isp_is_p010(isp)) { + buf->tag = ++isp->buffer_tag; + bufd->tag = buf->tag; + } bufd->num_planes = fmt->num_planes; bufd->pool_type = isp->hw->scl1 ? CISP_POOL_TYPE_RENDERED_SCL1 : CISP_POOL_TYPE_RENDERED; @@ -249,6 +289,11 @@ static int isp_submit_buffers(struct apple_isp *isp, bool pre_start) err = ipc_chan_send(isp, chan, ISP_BUFFER_TIMEOUT); if (err) { + if (isp_is_p010(isp)) { + /* Keep leases owned until stop_camera halts the coprocessor. */ + isp->ipc_failed = true; + return err; + } /* If we fail, consider the buffer not submitted. */ dev_err(isp->dev, "%s: failed to send bufs: [0x%llx, 0x%llx, 0x%llx]\n", @@ -269,7 +314,7 @@ static int isp_submit_buffers(struct apple_isp *isp, bool pre_start) &isp->bufs_pending); } } - for (int j = 0; j < ARRAY_SIZE(isp->meta_surfs); j++) { + for (int j = 0; j < isp_num_meta(isp); j++) { struct isp_surf *meta = isp->meta_surfs[j]; if (bufd->iovas[0] == meta->iova) { meta->submitted = false; @@ -308,10 +353,10 @@ static int isp_vb2_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers, * REQBUFS frees the old buffers first, CREATE_BUFS adds to them, so * count the buffers the queue already has. */ - if (q_num_bufs >= ISP_MAX_BUFFERS) + if (q_num_bufs >= isp_num_rendered(isp)) return -ENOBUFS; *nbuffers = min_t(unsigned int, *nbuffers, - ISP_MAX_BUFFERS - q_num_bufs); + isp_num_rendered(isp) - q_num_bufs); if (*num_planes) { if (sizes[0] < fmt->total_size) @@ -407,8 +452,15 @@ static void isp_vb2_buf_queue(struct vb2_buffer *vb) list_add_tail(&buf->link, &isp->bufs_pending); spin_unlock_irqrestore(&isp->buf_lock, flags); - if (test_bit(ISP_STATE_STREAMING, &isp->state) && !empty) - isp_submit_buffers(isp, false); + /* A returned last lease must restart an empty P010 queue immediately. */ + if (test_bit(ISP_STATE_STREAMING, &isp->state) && + (isp_is_p010(isp) || !empty)) { + if (isp_submit_buffers(isp, false) && isp_is_p010(isp)) { + apple_isp_firmware_halt(isp); + isp_vb2_release_buffers(isp, VB2_BUF_STATE_ERROR); + vb2_queue_error(&isp->vbq); + } + } } static int apple_isp_start_streaming(struct apple_isp *isp) @@ -565,18 +617,23 @@ static const struct vb2_ops isp_vb2_ops = { }; static int isp_set_preset(struct apple_isp *isp, struct isp_format *fmt, - struct isp_preset *preset) + struct isp_preset *preset, u32 pixelformat) { + unsigned int bytes_per_sample; int i; size_t total_size; fmt->preset = preset; - /* I really fucking hope they all use NV12. */ + /* P010 has MSB-aligned 10-bit samples in little-endian 16-bit words. */ + fmt->pixelformat = pixelformat == V4L2_PIX_FMT_P010 && + isp_supports_p010(isp) && preset->p010 ? + V4L2_PIX_FMT_P010 : V4L2_PIX_FMT_NV12; + bytes_per_sample = fmt->pixelformat == V4L2_PIX_FMT_P010 ? 2 : 1; fmt->num_planes = 2; - fmt->strides[0] = ALIGN(preset->output_dim.x, ISP_STRIDE_ALIGNMENT); - /* UV subsampled interleaved */ - fmt->strides[1] = ALIGN(preset->output_dim.x, ISP_STRIDE_ALIGNMENT); + fmt->strides[0] = ALIGN(preset->output_dim.x * bytes_per_sample, + ISP_STRIDE_ALIGNMENT); + fmt->strides[1] = fmt->strides[0]; fmt->plane_size[0] = fmt->strides[0] * preset->output_dim.y; fmt->plane_size[1] = fmt->strides[1] * preset->output_dim.y / 2; @@ -589,17 +646,25 @@ static int isp_set_preset(struct apple_isp *isp, struct isp_format *fmt, } static struct isp_preset *isp_select_preset(struct apple_isp *isp, u32 width, - u32 height) + u32 height, u32 pixelformat) { struct isp_preset *preset, *best = &isp->presets[0]; int i, score, best_score = INT_MAX; - /* Default if no dimensions */ - if (width == 0 || height == 0) - return &isp->presets[0]; + bool p010 = pixelformat == V4L2_PIX_FMT_P010 && isp_has_p010(isp); + + /* An unspecified P010 size selects the processed 720p preview first. */ + if (width == 0 || height == 0) { + if (!p010) + return &isp->presets[0]; + width = 1280; + height = 720; + } for (i = 0; i < isp->num_presets; i++) { preset = &isp->presets[i]; + if (p010 && !preset->p010) + continue; score = abs((int)preset->output_dim.x - (int)width) + abs((int)preset->output_dim.y - (int)height); if (score < best_score) { @@ -623,22 +688,22 @@ static int isp_vidioc_querycap(struct file *file, void *priv, return 0; } -static int isp_vidioc_enum_format(struct file *file, void *fh, - struct v4l2_fmtdesc *f) +static int isp_enum_pixelformat(struct apple_isp *isp, struct v4l2_fmtdesc *f) { - struct apple_isp *isp = video_drvdata(file); - - if (f->index >= ISP_MAX_PIX_FORMATS) - return -EINVAL; - switch (f->index) { case 0: f->pixelformat = V4L2_PIX_FMT_NV12; break; case 1: - if (!isp->multiplanar) + if (isp->multiplanar) { + f->pixelformat = V4L2_PIX_FMT_NV12M; + break; + } + fallthrough; + case 2: + if (!isp_has_p010(isp) || f->index != (isp->multiplanar ? 2 : 1)) return -EINVAL; - f->pixelformat = V4L2_PIX_FMT_NV12M; + f->pixelformat = V4L2_PIX_FMT_P010; break; default: return -EINVAL; @@ -647,20 +712,36 @@ static int isp_vidioc_enum_format(struct file *file, void *fh, return 0; } +static int isp_vidioc_enum_format(struct file *file, void *fh, + struct v4l2_fmtdesc *f) +{ + return isp_enum_pixelformat(video_drvdata(file), f); +} + static int isp_vidioc_enum_framesizes(struct file *file, void *fh, struct v4l2_frmsizeenum *f) { struct apple_isp *isp = video_drvdata(file); + struct isp_preset *preset = NULL; + u32 index = 0; - if (f->index >= isp->num_presets) + if (f->pixel_format != V4L2_PIX_FMT_NV12 && + f->pixel_format != V4L2_PIX_FMT_NV12M && + !(f->pixel_format == V4L2_PIX_FMT_P010 && isp_has_p010(isp))) return -EINVAL; - if ((f->pixel_format != V4L2_PIX_FMT_NV12) && - (f->pixel_format != V4L2_PIX_FMT_NV12M)) + for (int i = 0; i < isp->num_presets; i++) { + if (f->pixel_format == V4L2_PIX_FMT_P010 && !isp->presets[i].p010) + continue; + if (index++ == f->index) { + preset = &isp->presets[i]; + break; + } + } + if (!preset) return -EINVAL; - - f->discrete.width = isp->presets[f->index].output_dim.x; - f->discrete.height = isp->presets[f->index].output_dim.y; + f->discrete.width = preset->output_dim.x; + f->discrete.height = preset->output_dim.y; f->type = V4L2_FRMSIZE_TYPE_DISCRETE; return 0; @@ -676,10 +757,13 @@ static int isp_vidioc_enum_frameintervals(struct file *file, void *fh, return -EINVAL; if (interval->pixel_format != V4L2_PIX_FMT_NV12 && - interval->pixel_format != V4L2_PIX_FMT_NV12M) + interval->pixel_format != V4L2_PIX_FMT_NV12M && + !(interval->pixel_format == V4L2_PIX_FMT_P010 && isp_has_p010(isp))) return -EINVAL; for (i = 0; i < isp->num_presets; i++) { + if (interval->pixel_format == V4L2_PIX_FMT_P010 && !isp->presets[i].p010) + continue; if (isp->presets[i].output_dim.x == interval->width && isp->presets[i].output_dim.y == interval->height) break; @@ -726,10 +810,12 @@ static inline void isp_get_sp_pix_format(struct apple_isp *isp, f->fmt.pix.sizeimage = fmt->total_size; f->fmt.pix.field = V4L2_FIELD_NONE; - f->fmt.pix.pixelformat = V4L2_PIX_FMT_NV12; + f->fmt.pix.pixelformat = fmt->pixelformat; f->fmt.pix.colorspace = V4L2_COLORSPACE_REC709; f->fmt.pix.ycbcr_enc = V4L2_YCBCR_ENC_709; f->fmt.pix.xfer_func = V4L2_XFER_FUNC_709; + if (fmt->pixelformat == V4L2_PIX_FMT_P010) + f->fmt.pix.quantization = V4L2_QUANTIZATION_LIM_RANGE; } static inline void isp_get_mp_pix_format(struct apple_isp *isp, @@ -746,9 +832,19 @@ static inline void isp_get_mp_pix_format(struct apple_isp *isp, f->fmt.pix_mp.field = V4L2_FIELD_NONE; f->fmt.pix_mp.pixelformat = V4L2_PIX_FMT_NV12M; + /* P010 is contiguous, including through the multi-planar API. */ + if (fmt->pixelformat == V4L2_PIX_FMT_P010) { + f->fmt.pix_mp.pixelformat = V4L2_PIX_FMT_P010; + f->fmt.pix_mp.num_planes = 1; + f->fmt.pix_mp.plane_fmt[0].sizeimage = fmt->total_size; + memset(&f->fmt.pix_mp.plane_fmt[1], 0, + sizeof(f->fmt.pix_mp.plane_fmt[1])); + } f->fmt.pix_mp.colorspace = V4L2_COLORSPACE_REC709; f->fmt.pix_mp.ycbcr_enc = V4L2_YCBCR_ENC_709; f->fmt.pix_mp.xfer_func = V4L2_XFER_FUNC_709; + if (fmt->pixelformat == V4L2_PIX_FMT_P010) + f->fmt.pix_mp.quantization = V4L2_QUANTIZATION_LIM_RANGE; } static int isp_vidioc_get_format(struct file *file, void *fh, @@ -774,8 +870,9 @@ static int isp_vidioc_set_format(struct file *file, void *fh, if (vb2_is_busy(&isp->vbq)) return -EBUSY; - preset = isp_select_preset(isp, f->fmt.pix.width, f->fmt.pix.height); - err = isp_set_preset(isp, fmt, preset); + preset = isp_select_preset(isp, f->fmt.pix.width, f->fmt.pix.height, + f->fmt.pix.pixelformat); + err = isp_set_preset(isp, fmt, preset, f->fmt.pix.pixelformat); if (err) return err; @@ -794,8 +891,9 @@ static int isp_vidioc_try_format(struct file *file, void *fh, struct isp_preset *preset; int err; - preset = isp_select_preset(isp, f->fmt.pix.width, f->fmt.pix.height); - err = isp_set_preset(isp, &fmt, preset); + preset = isp_select_preset(isp, f->fmt.pix.width, f->fmt.pix.height, + f->fmt.pix.pixelformat); + err = isp_set_preset(isp, &fmt, preset, f->fmt.pix.pixelformat); if (err) return err; @@ -832,9 +930,9 @@ static int isp_vidioc_set_format_mplane(struct file *file, void *fh, if (vb2_is_busy(&isp->vbq)) return -EBUSY; - preset = isp_select_preset(isp, f->fmt.pix_mp.width, - f->fmt.pix_mp.height); - err = isp_set_preset(isp, fmt, preset); + preset = isp_select_preset(isp, f->fmt.pix_mp.width, f->fmt.pix_mp.height, + f->fmt.pix_mp.pixelformat); + err = isp_set_preset(isp, fmt, preset, f->fmt.pix_mp.pixelformat); if (err) return err; @@ -856,9 +954,9 @@ static int isp_vidioc_try_format_mplane(struct file *file, void *fh, if (!isp->multiplanar) return -ENOTTY; - preset = isp_select_preset(isp, f->fmt.pix_mp.width, - f->fmt.pix_mp.height); - err = isp_set_preset(isp, &fmt, preset); + preset = isp_select_preset(isp, f->fmt.pix_mp.width, f->fmt.pix_mp.height, + f->fmt.pix_mp.pixelformat); + err = isp_set_preset(isp, &fmt, preset, f->fmt.pix_mp.pixelformat); if (err) return err; @@ -940,6 +1038,24 @@ static int isp_vidioc_set_param(struct file *file, void *fh, return 0; } +static int isp_vidioc_log_status(struct file *file, void *fh) +{ + struct apple_isp *isp = video_drvdata(file); + + v4l2_ctrl_log_status(file, fh); + for (unsigned int i = 0; i < isp->num_sensor_configs; i++) { + const struct isp_sensor_config *config = &isp->sensor_configs[i]; + + v4l2_info(&isp->v4l2_dev, + "sensor config %u: input %ux%u output %ux%u clock %u%s\n", + i, config->input_dim.x, config->input_dim.y, + config->output_dim.x, config->output_dim.y, + config->sensor_clk, + config->usable ? "" : " (unverified geometry)"); + } + return 0; +} + static const struct v4l2_ioctl_ops isp_v4l2_ioctl_ops = { .vidioc_querycap = isp_vidioc_querycap, @@ -969,7 +1085,7 @@ static const struct v4l2_ioctl_ops isp_v4l2_ioctl_ops = { .vidioc_streamon = vb2_ioctl_streamon, .vidioc_streamoff = vb2_ioctl_streamoff, - .vidioc_log_status = v4l2_ctrl_log_status, + .vidioc_log_status = isp_vidioc_log_status, .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, .vidioc_unsubscribe_event = v4l2_event_unsubscribe, }; @@ -1044,7 +1160,7 @@ int apple_isp_setup_video(struct apple_isp *isp) struct isp_format *fmt = isp_get_current_format(isp); int err; - err = isp_set_preset(isp, fmt, &isp->presets[0]); + err = isp_set_preset(isp, fmt, &isp->presets[0], V4L2_PIX_FMT_NV12); if (err) { dev_err(isp->dev, "failed to set default preset: %d\n", err); return err; @@ -1054,7 +1170,8 @@ int apple_isp_setup_video(struct apple_isp *isp) for (int i = 0; i < ARRAY_SIZE(isp->meta_surfs); i++) { isp->meta_surfs[i] = - isp_alloc_surface_vmap(isp, isp->hw->meta_size); + isp_alloc_surface_vmap(isp, isp_has_p010(isp) ? 0x8000 : + isp->hw->meta_size); if (!isp->meta_surfs[i]) { isp_err(isp, "failed to alloc meta surface\n"); err = -ENOMEM; diff --git a/drivers/media/platform/apple/isp/isp-v4l2.h b/drivers/media/platform/apple/isp/isp-v4l2.h index 622288296aaae..55135594576b2 100644 --- a/drivers/media/platform/apple/isp/isp-v4l2.h +++ b/drivers/media/platform/apple/isp/isp-v4l2.h @@ -9,6 +9,7 @@ int apple_isp_setup_video(struct apple_isp *isp); void apple_isp_remove_video(struct apple_isp *isp); void apple_isp_free_video(struct apple_isp *isp); +void ipc_bt_complete(struct apple_isp *isp); int ipc_bt_handle(struct apple_isp *isp, struct isp_channel *chan); int apple_isp_video_suspend(struct apple_isp *isp); From 910d31f6d65aa1eb497dcfd01fb1ca52e692e169 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 13:06:16 +1100 Subject: [PATCH 044/184] selftests: media: add Apple ISP capture delivery stress runner Exercise every offered format and discrete size with long MMAP capture, repeated stream restarts and format switches. Record delivered rate, timing gaps and buffer errors, and fail on substitutions or stalls. Allow a mode filter for different nominal rates. A driver-generated sequence alone is not evidence of zero firmware drops. Assisted-by: LLM Signed-off-by: Ryan Murray --- tools/testing/selftests/media/Makefile | 4 + .../testing/selftests/media/README.apple-isp | 34 +++ .../selftests/media/apple-isp-stress.c | 278 ++++++++++++++++++ 3 files changed, 316 insertions(+) create mode 100644 tools/testing/selftests/media/Makefile create mode 100644 tools/testing/selftests/media/README.apple-isp create mode 100644 tools/testing/selftests/media/apple-isp-stress.c diff --git a/tools/testing/selftests/media/Makefile b/tools/testing/selftests/media/Makefile new file mode 100644 index 0000000000000..b63cb0f120e7e --- /dev/null +++ b/tools/testing/selftests/media/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only +CFLAGS += -O2 -Wall -Wextra -Werror +TEST_GEN_PROGS_EXTENDED := apple-isp-stress +include ../lib.mk diff --git a/tools/testing/selftests/media/README.apple-isp b/tools/testing/selftests/media/README.apple-isp new file mode 100644 index 0000000000000..57b95740120c0 --- /dev/null +++ b/tools/testing/selftests/media/README.apple-isp @@ -0,0 +1,34 @@ +Apple ISP capture delivery stress +================================= + +Build with make -C tools/testing/selftests/media. This is a manual hardware +runner; run_tests does not start the camera automatically. No image files are +written. Run only on the agreed J616s test kernel while audio is idle. + +Example for configurations whose expected rate has been established as 30 fps: + + ./apple-isp-stress /dev/video0 600 10 30 | tee capture-results.jsonl + +It enumerates every offered single-planar format and discrete size. Each gets +a ten-minute run, then ten five-second start/stop cycles, switching formats and +sizes between streams. A substituted format/rate, poll timeout, queue error, +V4L2 buffer error, sequence discontinuity or delivery gap exceeding 1.5 nominal +frame periods fails. Delivered FPS must reach 99.8% of the supplied rate, allowing +29.97/30 rounding. The rate argument is an explicit test expectation, not a +rate inferred from the sensor clock or set by the tool as an alleged sensor fact. +For modes with a different nominal rate, filter the run explicitly, for example +`./apple-isp-stress /dev/video0 600 10 25 P010 1920 1080`. A filtered run tests +that mode's restart cycles; the unfiltered run also exercises format switches. This runner stops +on the first failure to preserve evidence and avoid retrying ambiguous firmware. + +The driver's sequence increments for received reports, so consecutive sequence +values alone cannot establish zero sensor/firmware drops. The timing checks +measure delivery gaps and completed frames. A true zero-drop sensor claim also +needs a validated firmware frame counter or an independent sensor-rate oracle. +No RAW packing, Bayer order or nominal per-config FPS is guessed by this tool. +It tests RAW automatically if a verified RAW format is added to ENUM_FMT later. + +Preserve the current firmware config dump, v4l2-ctl --all/--list-formats-ext, +the JSONL output, dmesg before/after, exact kernel and source hashes. Check both +P010 presets for correct image content separately; this runner deliberately +avoids a converter or disk writer in the capture loop. diff --git a/tools/testing/selftests/media/apple-isp-stress.c b/tools/testing/selftests/media/apple-isp-stress.c new file mode 100644 index 0000000000000..8490a7abe3d68 --- /dev/null +++ b/tools/testing/selftests/media/apple-isp-stress.c @@ -0,0 +1,278 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* V4L2 delivery stress test. Does not infer sensor FPS from a driver counter. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_MODES 256 +#define MAX_BUFFERS 16 + +struct mode { + uint32_t fourcc, width, height; +}; + +struct mapping { + void *address; + size_t size; +}; + +static int xioctl(int fd, unsigned long cmd, void *arg) +{ + int ret; + + do { + ret = ioctl(fd, cmd, arg); + } while (ret < 0 && errno == EINTR); + return ret; +} + +static double monotonic(void) +{ + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + return ts.tv_sec + ts.tv_nsec / 1000000000.0; +} + +static int capture(int fd, struct mode mode, unsigned int seconds, + unsigned int fps, unsigned int cycle) +{ + struct v4l2_format format = { .type = V4L2_BUF_TYPE_VIDEO_CAPTURE }; + struct v4l2_streamparm parm = { .type = V4L2_BUF_TYPE_VIDEO_CAPTURE }; + struct v4l2_requestbuffers req = { + .count = 4, .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, .memory = V4L2_MEMORY_MMAP, + }; + struct mapping maps[MAX_BUFFERS] = {}; + enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + unsigned int frames = 0, bad = 0, gaps = 0, sequence_gaps = 0, mapped = 0; + unsigned int last_sequence = 0; + double start, end, first = 0, last = 0, max_gap = 0, measured_fps = 0; + bool streaming = false; + int ret = -1; + + format.fmt.pix.width = mode.width; + format.fmt.pix.height = mode.height; + format.fmt.pix.pixelformat = mode.fourcc; + format.fmt.pix.field = V4L2_FIELD_NONE; + if (xioctl(fd, VIDIOC_S_FMT, &format) < 0) + goto error; + /* Fail instead of counting a silently substituted mode as tested. */ + if (format.fmt.pix.width != mode.width || format.fmt.pix.height != mode.height || + format.fmt.pix.pixelformat != mode.fourcc) { + fprintf(stderr, "requested mode was substituted\n"); + goto out; + } + parm.parm.capture.timeperframe.numerator = 1; + parm.parm.capture.timeperframe.denominator = fps; + if (xioctl(fd, VIDIOC_S_PARM, &parm) < 0) + goto error; + if (parm.parm.capture.timeperframe.numerator != 1 || + parm.parm.capture.timeperframe.denominator != fps) { + fprintf(stderr, "requested rate was substituted\n"); + goto out; + } + if (xioctl(fd, VIDIOC_REQBUFS, &req) < 0) + goto error; + if (req.count < 2 || req.count > MAX_BUFFERS) + goto out; + for (unsigned int i = 0; i < req.count; i++) { + struct v4l2_buffer buf = { + .type = type, .memory = V4L2_MEMORY_MMAP, .index = i, + }; + + if (xioctl(fd, VIDIOC_QUERYBUF, &buf) < 0) + goto error; + maps[i].size = buf.length; + maps[i].address = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, + MAP_SHARED, fd, buf.m.offset); + if (maps[i].address == MAP_FAILED) + goto error; + mapped++; + if (xioctl(fd, VIDIOC_QBUF, &buf) < 0) + goto error; + } + if (xioctl(fd, VIDIOC_STREAMON, &type) < 0) + goto error; + streaming = true; + start = monotonic(); + end = start + seconds; + while (monotonic() < end) { + struct pollfd pfd = { .fd = fd, .events = POLLIN }; + struct v4l2_buffer buf = { .type = type, .memory = V4L2_MEMORY_MMAP }; + double timestamp, gap; + int ready = poll(&pfd, 1, 2000); + + if (ready < 0 && errno == EINTR) + continue; + if (ready <= 0 || (pfd.revents & (POLLERR | POLLHUP | POLLNVAL))) { + fprintf(stderr, "stream stalled or queue failed\n"); + goto out; + } + if (xioctl(fd, VIDIOC_DQBUF, &buf) < 0) + goto error; + if (buf.index >= req.count || buf.bytesused > maps[buf.index].size) + goto out; + if (buf.flags & V4L2_BUF_FLAG_ERROR) + bad++; + timestamp = buf.timestamp.tv_sec + buf.timestamp.tv_usec / 1000000.0; + if (frames) { + gap = timestamp - last; + if (gap > max_gap) + max_gap = gap; + if (gap <= 0 || gap > 1.5 / fps) + gaps++; + if ((uint32_t)(buf.sequence - last_sequence) != 1) + sequence_gaps++; + } else { + first = timestamp; + } + last = timestamp; + last_sequence = buf.sequence; + frames++; + /* Requeue immediately; image copies/conversion do not limit the test. */ + if (xioctl(fd, VIDIOC_QBUF, &buf) < 0) + goto error; + } + if (frames > 1 && last > first) + measured_fps = (frames - 1) / (last - first); + /* Allow clock/29.97-vs-30 rounding; gaps and buffer errors must be zero. */ + ret = bad || gaps || sequence_gaps || measured_fps < fps * 0.998 ? 1 : 0; + printf("{\"cycle\":%u,\"fourcc\":\"%.4s\",\"width\":%u,\"height\":%u,", + cycle, (char *)&mode.fourcc, mode.width, mode.height); + printf("\"stride\":%u,\"sizeimage\":%u,\"requested_fps\":%u,\"frames\":%u,", + format.fmt.pix.bytesperline, format.fmt.pix.sizeimage, fps, frames); + printf("\"delivered_fps\":%.6f,\"max_gap_ms\":%.3f,\"timing_gaps\":%u,", + measured_fps, max_gap * 1000, gaps); + printf("\"buffer_errors\":%u,\"sequence_gaps\":%u,\"pass\":%s}\n", + bad, sequence_gaps, ret ? "false" : "true"); + fflush(stdout); + goto out; +error: + perror("V4L2 ioctl"); +out: + if (streaming && xioctl(fd, VIDIOC_STREAMOFF, &type) < 0) { + perror("STREAMOFF"); + ret = -1; + } + for (unsigned int i = 0; i < mapped; i++) + munmap(maps[i].address, maps[i].size); + req.count = 0; + if (xioctl(fd, VIDIOC_REQBUFS, &req) < 0) + ret = -1; + return ret; +} + +int main(int argc, char **argv) +{ + struct mode modes[MAX_MODES]; + unsigned int count = 0, seconds, cycles, fps; + int fd, failed = 0; + char *end; + unsigned long values[3]; + + if (argc != 5 && argc != 8) { + fprintf(stderr, "usage: %s device seconds cycles fps [FOURCC width height]\n", + argv[0]); + return 2; + } + for (int i = 0; i < 3; i++) { + errno = 0; + values[i] = strtoul(argv[i + 2], &end, 10); + if (errno || *end || !values[i] || values[i] > 86400) + return 2; + } + seconds = values[0]; + cycles = values[1]; + fps = values[2]; + if (fps > 240 || cycles > 1000 || seconds < 5) + return 2; + fd = open(argv[1], O_RDWR | O_NONBLOCK); + if (fd < 0) { + perror(argv[1]); + return 2; + } + for (unsigned int i = 0; ; i++) { + struct v4l2_fmtdesc format = { .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, .index = i }; + + if (xioctl(fd, VIDIOC_ENUM_FMT, &format) < 0) { + if (errno != EINVAL) + failed = 1; + break; + } + /* This harness exercises the driver's contiguous, single-planar API. */ + if (format.pixelformat == V4L2_PIX_FMT_NV12M) + continue; + for (unsigned int j = 0; ; j++) { + struct v4l2_frmsizeenum size = { + .pixel_format = format.pixelformat, .index = j, + }; + + if (xioctl(fd, VIDIOC_ENUM_FRAMESIZES, &size) < 0) { + if (errno != EINVAL) + failed = 1; + break; + } + if (size.type != V4L2_FRMSIZE_TYPE_DISCRETE || count == MAX_MODES) { + failed = 1; + break; + } + modes[count++] = (struct mode){format.pixelformat, size.discrete.width, + size.discrete.height}; + } + } + if (argc == 8) { + uint32_t fourcc; + unsigned long width, height; + bool found = false; + + if (strlen(argv[5]) != 4) { + close(fd); + return 2; + } + memcpy(&fourcc, argv[5], sizeof(fourcc)); + errno = 0; + width = strtoul(argv[6], &end, 10); + if (errno || *end || !width || width > UINT32_MAX) { + close(fd); + return 2; + } + errno = 0; + height = strtoul(argv[7], &end, 10); + if (errno || *end || !height || height > UINT32_MAX) { + close(fd); + return 2; + } + for (unsigned int i = 0; i < count; i++) { + if (modes[i].fourcc == fourcc && modes[i].width == width && + modes[i].height == height) { + modes[0] = modes[i]; + found = true; + break; + } + } + count = found ? 1 : 0; + } + if (!count) + failed = 1; + /* Long runs first, then repeated stops/restarts and format/size switches. */ + for (unsigned int cycle = 0; cycle <= cycles && !failed; cycle++) { + for (unsigned int i = 0; i < count; i++) { + if (capture(fd, modes[i], cycle ? 5 : seconds, fps, cycle)) { + failed = 1; + break; + } + } + } + close(fd); + return failed ? 1 : 0; +} From a86ad6c3ba438153cf5b39384d5d619bbf94cf3d Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 13:06:58 +1100 Subject: [PATCH 045/184] drm/apple: cancel brightness notifications only on desktop contexts KUnit's external unbind path caught cancellation of the desktop-only work item on a context which does not initialize it. Match the producer's firmware guard when stopping notifications. The legacy/Neo stop path then keeps its existing two delayed-work cancellations. Exercise the real stop helper with desktop and non-desktop profiles in the host fixture; retain panic_on_warn in the complete KUnit gate. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/gpu/drm/apple/dcp_backlight.c | 3 ++- .../testing/selftests/drivers/gpu/apple-dcp/desktop.py | 10 +++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp_backlight.c b/drivers/gpu/drm/apple/dcp_backlight.c index 3aeabc1982737..2a9318beb4d32 100644 --- a/drivers/gpu/drm/apple/dcp_backlight.c +++ b/drivers/gpu/drm/apple/dcp_backlight.c @@ -340,7 +340,8 @@ void dcp_backlight_stop(struct apple_dcp *dcp) { cancel_delayed_work_sync(&dcp->backlight.fade_work); cancel_delayed_work_sync(&dcp->backlight.retry_work); - cancel_work_sync(&dcp->backlight.notify_work); + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) + cancel_work_sync(&dcp->backlight.notify_work); } static void dcp_backlight_release(void *data) diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py index 55212094c14fa..5ba5d5e05e550 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py @@ -781,7 +781,8 @@ def brightness_notifications(root): source=(root/'drivers/gpu/drm/apple/dcp_backlight.c').read_text() functions='\n'.join(block(source,name) for name in ( 'static void dcp_backlight_notify_work(', 'static void dcp_backlight_notify_release(', 'static void dcp_backlight_notify(', - 'static bool dcp_backlight_desktop(', 'void dcp_backlight_desktop_finish(')) + 'static bool dcp_backlight_desktop(', 'void dcp_backlight_desktop_finish(', + 'void dcp_backlight_stop(')) return prefix+r''' #include #include @@ -799,10 +800,11 @@ def brightness_notifications(root): struct apple_dcp { int fw_compat;bool quiescing,crashed; struct {struct backlight_device *bl_dev;} brightness; - struct {bool lock;struct dcp_backlight_state state;struct work_struct notify_work;int retry_work;} backlight; + struct {bool lock;struct dcp_backlight_state state;struct work_struct notify_work;int fade_work,retry_work;} backlight; struct {struct dcp_backlight_swap backlight;} *desktop; }; -static unsigned events,brightness,actual,kicks,cancelled; +static unsigned events,brightness,actual,kicks,cancelled,delayed_cancelled; +static void cancel_delayed_work_sync(int *work) {(void)work;delayed_cancelled++;} static void cancel_work_sync(struct work_struct *w) {(void)w;cancelled++;} static void kobject_uevent_env(int *k,int action,char **env) {(void)k;assert(action==1 && !strcmp(env[0],"SOURCE=unknown") && !env[1]);events++;} @@ -833,8 +835,10 @@ def brightness_notifications(root): dcp_backlight_desktop_finish(&d,9,true,false);assert(events==1); d.quiescing=true;dcp_backlight_notify(&d);assert(events==1); d.quiescing=false;d.fw_compat=1;dcp_backlight_notify(&d);assert(events==1); + dcp_backlight_stop(&d);assert(!cancelled && delayed_cancelled==2); d.fw_compat=5;dcp_backlight_notify_release(&d); assert(!d.brightness.bl_dev && cancelled==1);dcp_backlight_notify(&d);assert(events==1); + dcp_backlight_stop(&d);assert(cancelled==2 && delayed_cancelled==4); return 0; } ''' From 3bd32e8909402c48eea988b72df229093f2a5cf6 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 13:09:55 +1100 Subject: [PATCH 046/184] media: apple: isp: reject P010 command ACKs after the deadline Check the command deadline before advancing the ring cursor, including when an ACK arrives at the wait timeout boundary. Keep the failed channel latched until coprocessor stop. Cover the boundary in KUnit. Emit the capture stress result after cleanup so a failed STREAMOFF cannot leave a passing result. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/media/platform/apple/isp/isp-drv.h | 2 ++ .../media/platform/apple/isp/isp-ipc-test.c | 19 +++++++++++++++++++ drivers/media/platform/apple/isp/isp-ipc.c | 8 ++++++++ .../selftests/media/apple-isp-stress.c | 18 +++++++++--------- 4 files changed, 38 insertions(+), 9 deletions(-) diff --git a/drivers/media/platform/apple/isp/isp-drv.h b/drivers/media/platform/apple/isp/isp-drv.h index e02d675f0e998..db042f94aed47 100644 --- a/drivers/media/platform/apple/isp/isp-drv.h +++ b/drivers/media/platform/apple/isp/isp-drv.h @@ -117,6 +117,8 @@ struct isp_channel { void *virt; u32 doorbell; u32 cursor; + unsigned long deadline; + bool deadline_active; struct mutex lock; struct isp_message req; struct isp_message rsp; diff --git a/drivers/media/platform/apple/isp/isp-ipc-test.c b/drivers/media/platform/apple/isp/isp-ipc-test.c index efd24f6794a5a..8b6decd63f274 100644 --- a/drivers/media/platform/apple/isp/isp-ipc-test.c +++ b/drivers/media/platform/apple/isp/isp-ipc-test.c @@ -170,7 +170,26 @@ static void isp_test_command_ring_wrap(struct kunit *test) KUNIT_EXPECT_EQ(test, chan->cursor, 0); } +static void isp_test_late_ack_keeps_cursor(struct kunit *test) +{ + struct isp_sm_test *t = isp_sm_test_alloc(test); + struct isp_message response = { .arg0 = 0x5000001 }; + + isp_get_current_format(t->isp)->pixelformat = V4L2_PIX_FMT_P010; + t->chan->virt = &response; + t->chan->num = 8; + t->chan->req.arg0 = 0x5000000; + t->chan->deadline_active = true; + t->chan->deadline = jiffies; + KUNIT_EXPECT_FALSE(test, chan_tx_done(t->isp, t->chan)); + KUNIT_EXPECT_EQ(test, t->chan->cursor, 0); + t->chan->deadline = jiffies + HZ; + KUNIT_EXPECT_TRUE(test, chan_tx_done(t->isp, t->chan)); + KUNIT_EXPECT_EQ(test, t->chan->cursor, 1); +} + static struct kunit_case isp_ipc_test_cases[] = { + KUNIT_CASE(isp_test_late_ack_keeps_cursor), KUNIT_CASE(isp_test_command_ring_wrap), KUNIT_CASE(isp_test_sm_free_releases), KUNIT_CASE(isp_test_sm_free_alias), diff --git a/drivers/media/platform/apple/isp/isp-ipc.c b/drivers/media/platform/apple/isp/isp-ipc.c index da946f360ff5c..92cfc6ab35f5b 100644 --- a/drivers/media/platform/apple/isp/isp-ipc.c +++ b/drivers/media/platform/apple/isp/isp-ipc.c @@ -177,6 +177,9 @@ int ipc_chan_handle(struct apple_isp *isp, struct isp_channel *chan) static inline bool chan_tx_done(struct apple_isp *isp, struct isp_channel *chan) { + if (isp_is_p010(isp) && chan->deadline_active && + time_after_eq(jiffies, chan->deadline)) + return false; dma_rmb(); chan_read_msg(isp, chan, &chan->rsp); @@ -198,6 +201,10 @@ int ipc_chan_send(struct apple_isp *isp, struct isp_channel *chan, #endif if (isp->ipc_failed) return -EIO; + if (isp_is_p010(isp) && timeout) { + chan->deadline = jiffies + timeout; + chan->deadline_active = true; + } chan_write_msg(isp, chan, &chan->req); dma_wmb(); @@ -217,6 +224,7 @@ int ipc_chan_send(struct apple_isp *isp, struct isp_channel *chan, return -ETIME; } + chan->deadline_active = false; isp_dbg(isp, "%s: request success (%ld)\n", chan->name, t); return 0; diff --git a/tools/testing/selftests/media/apple-isp-stress.c b/tools/testing/selftests/media/apple-isp-stress.c index 8490a7abe3d68..cd85e51ecba09 100644 --- a/tools/testing/selftests/media/apple-isp-stress.c +++ b/tools/testing/selftests/media/apple-isp-stress.c @@ -147,15 +147,6 @@ static int capture(int fd, struct mode mode, unsigned int seconds, measured_fps = (frames - 1) / (last - first); /* Allow clock/29.97-vs-30 rounding; gaps and buffer errors must be zero. */ ret = bad || gaps || sequence_gaps || measured_fps < fps * 0.998 ? 1 : 0; - printf("{\"cycle\":%u,\"fourcc\":\"%.4s\",\"width\":%u,\"height\":%u,", - cycle, (char *)&mode.fourcc, mode.width, mode.height); - printf("\"stride\":%u,\"sizeimage\":%u,\"requested_fps\":%u,\"frames\":%u,", - format.fmt.pix.bytesperline, format.fmt.pix.sizeimage, fps, frames); - printf("\"delivered_fps\":%.6f,\"max_gap_ms\":%.3f,\"timing_gaps\":%u,", - measured_fps, max_gap * 1000, gaps); - printf("\"buffer_errors\":%u,\"sequence_gaps\":%u,\"pass\":%s}\n", - bad, sequence_gaps, ret ? "false" : "true"); - fflush(stdout); goto out; error: perror("V4L2 ioctl"); @@ -169,6 +160,15 @@ static int capture(int fd, struct mode mode, unsigned int seconds, req.count = 0; if (xioctl(fd, VIDIOC_REQBUFS, &req) < 0) ret = -1; + printf("{\"cycle\":%u,\"fourcc\":\"%.4s\",\"width\":%u,\"height\":%u,", + cycle, (char *)&mode.fourcc, mode.width, mode.height); + printf("\"stride\":%u,\"sizeimage\":%u,\"requested_fps\":%u,\"frames\":%u,", + format.fmt.pix.bytesperline, format.fmt.pix.sizeimage, fps, frames); + printf("\"delivered_fps\":%.6f,\"max_gap_ms\":%.3f,\"timing_gaps\":%u,", + measured_fps, max_gap * 1000, gaps); + printf("\"buffer_errors\":%u,\"sequence_gaps\":%u,\"pass\":%s}\n", + bad, sequence_gaps, ret ? "false" : "true"); + fflush(stdout); return ret; } From b17a883263f0252e2285836b5d9454065c98a6e1 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 13:12:47 +1100 Subject: [PATCH 047/184] media: apple: isp: requeue the last IMX958 NV12 buffer immediately Apply the empty-queue restart to both processed IMX958 formats. Exercise 10,000 empty-to-submitted transitions for each of P010 and NV12. Other sensors retain their queue policy. Assisted-by: LLM Signed-off-by: Ryan Murray --- .../media/platform/apple/isp/isp-v4l2-test.c | 30 +++++++++++-------- drivers/media/platform/apple/isp/isp-v4l2.c | 4 +-- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/drivers/media/platform/apple/isp/isp-v4l2-test.c b/drivers/media/platform/apple/isp/isp-v4l2-test.c index 47f1f8900d138..58af78581bf7e 100644 --- a/drivers/media/platform/apple/isp/isp-v4l2-test.c +++ b/drivers/media/platform/apple/isp/isp-v4l2-test.c @@ -286,7 +286,7 @@ static int isp_test_buffer_ack(struct apple_isp *isp, struct isp_channel *chan, return 0; } -static void isp_test_p010_last_lease_requeue(struct kunit *test) +static void isp_test_imx958_last_lease_requeue(struct kunit *test) { struct apple_isp *isp = isp_test_alloc(test); struct isp_format *fmt = isp_get_current_format(isp); @@ -296,6 +296,7 @@ static void isp_test_p010_last_lease_requeue(struct kunit *test) KUNIT_ASSERT_NOT_NULL(test, buf); isp->hw = &isp_test_t6040; + fmt->version = 0x958; fmt->pixelformat = V4L2_PIX_FMT_P010; fmt->num_planes = 2; fmt->plane_size[0] = 1843200; @@ -306,7 +307,7 @@ static void isp_test_p010_last_lease_requeue(struct kunit *test) isp->chan_bh = kunit_kzalloc(test, sizeof(*isp->chan_bh), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, isp->cmd_virt); KUNIT_ASSERT_NOT_NULL(test, isp->chan_bh); - for (unsigned int i = 0; i < isp_num_meta(isp); i++) { + for (unsigned int i = 0; i < ISP_MAX_BUFFERS; i++) { isp->meta_surfs[i] = kunit_kzalloc(test, sizeof(struct isp_surf), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, isp->meta_surfs[i]); isp->meta_surfs[i]->submitted = true; @@ -317,20 +318,25 @@ static void isp_test_p010_last_lease_requeue(struct kunit *test) set_bit(ISP_STATE_STREAMING, &isp->state); test->priv = &calls; kunit_activate_static_stub(test, ipc_chan_send, isp_test_buffer_ack); - for (unsigned int i = 1; i <= 10000; i++) { - /* Both lists empty, as after the final completion was dequeued. */ - isp_vb2_buf_queue(&buf->vb.vb2_buf); - KUNIT_ASSERT_EQ(test, calls, i); - KUNIT_ASSERT_EQ(test, buf->tag, (u64)i); - KUNIT_ASSERT_TRUE(test, list_empty(&isp->bufs_pending)); - submitted = list_first_entry(&isp->bufs_submitted, struct isp_buffer, link); - KUNIT_ASSERT_PTR_EQ(test, submitted, buf); - list_del(&buf->link); + for (unsigned int format = 0; format < 2; format++) { + fmt->pixelformat = format ? V4L2_PIX_FMT_NV12 : V4L2_PIX_FMT_P010; + calls = 0; + for (unsigned int i = 1; i <= 10000; i++) { + /* Both lists empty, as after the last completion was dequeued. */ + isp_vb2_buf_queue(&buf->vb.vb2_buf); + KUNIT_ASSERT_EQ(test, calls, i); + if (!format) + KUNIT_ASSERT_EQ(test, buf->tag, (u64)i); + KUNIT_ASSERT_TRUE(test, list_empty(&isp->bufs_pending)); + submitted = list_first_entry(&isp->bufs_submitted, struct isp_buffer, link); + KUNIT_ASSERT_PTR_EQ(test, submitted, buf); + list_del(&buf->link); + } } } static struct kunit_case isp_v4l2_test_cases[] = { - KUNIT_CASE(isp_test_p010_last_lease_requeue), + KUNIT_CASE(isp_test_imx958_last_lease_requeue), KUNIT_CASE(isp_test_p010_report_defers_completion), KUNIT_CASE(isp_test_p010_mode_selection), KUNIT_CASE(isp_test_p010_format), diff --git a/drivers/media/platform/apple/isp/isp-v4l2.c b/drivers/media/platform/apple/isp/isp-v4l2.c index 35c30582ab81d..4fcde29410d2c 100644 --- a/drivers/media/platform/apple/isp/isp-v4l2.c +++ b/drivers/media/platform/apple/isp/isp-v4l2.c @@ -452,9 +452,9 @@ static void isp_vb2_buf_queue(struct vb2_buffer *vb) list_add_tail(&buf->link, &isp->bufs_pending); spin_unlock_irqrestore(&isp->buf_lock, flags); - /* A returned last lease must restart an empty P010 queue immediately. */ + /* Restart an empty IMX958 queue when its last returned lease is queued. */ if (test_bit(ISP_STATE_STREAMING, &isp->state) && - (isp_is_p010(isp) || !empty)) { + (isp_supports_p010(isp) || !empty)) { if (isp_submit_buffers(isp, false) && isp_is_p010(isp)) { apple_isp_firmware_halt(isp); isp_vb2_release_buffers(isp, VB2_BUF_STATE_ERROR); From 7f571507eb1a8140721067d50321a24efe58d59d Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 13:23:46 +1100 Subject: [PATCH 048/184] docs: media: fix J616s ISP protocol table markup Size the command table columns for the full H17 record sizes and fix the document title underline. The standalone RST parser now accepts the document with warnings treated as errors. Assisted-by: LLM Signed-off-by: Ryan Murray --- .../media/drivers/apple-isp-j616s.rst | 58 ++++++++++--------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/Documentation/driver-api/media/drivers/apple-isp-j616s.rst b/Documentation/driver-api/media/drivers/apple-isp-j616s.rst index 7413c0e866460..707ac2c22e6e7 100644 --- a/Documentation/driver-api/media/drivers/apple-isp-j616s.rst +++ b/Documentation/driver-api/media/drivers/apple-isp-j616s.rst @@ -1,7 +1,7 @@ .. SPDX-License-Identifier: GPL-2.0-only J616s ISP modes and capture protocol -================================== +==================================== Sources and scope ----------------- @@ -122,32 +122,32 @@ Command stream IO commands have a zero/status u32 followed by the opcode u32, then the parameters below. Sizes include that eight-byte header. ``ch`` is zero. -================ =================================== ==== =================== -Opcode Command Size Parameters -================ =================================== ==== =================== -0x0003 CONFIG_GET 0x1c Camera channel count -0x3206 SET_DSID_CLR_MULTI_BC_REG_BASE 0x50 8 slots, range 0x2fc -0x0000 START 0x0c Proxy 0x11, Linux 0 -0x010d CH_INFO_GET 0x190 ch -0x0106 CH_CAMERA_CONFIG_GET 0x120 ch, index (all) -0x0024 FLICKER_SENSOR_SET 0x0c 0 -0x013b CH_SBS_ENABLE 0x10 ch, 1 -0x0107 CH_CAMERA_CONFIG_SELECT 0x10 ch, selected config -0x010e CH_BUFFER_RECYCLE_MODE_SET 0x10 ch, 1 (empty only) -0x010f CH_BUFFER_RECYCLE_START 0x0c ch -0x080c CH_CROP_SCL1_SET 0x1c ch, x, y, width, height -0x0b09 CH_OUTPUT_CONFIG_SCL1_SET 0x38 ch, output geometry -0x0208 CH_AE_FRAME_RATE_MAX_SET 0x10 ch, fps * 256 -0x020a CH_AE_FRAME_RATE_MIN_SET 0x10 ch, min_fps * 256 -0x0117 CH_BUFFER_POOL_CONFIG_SET (meta) 0x9c ch, pool 0, count 8 -0x0117 CH_BUFFER_POOL_CONFIG_SET (output) 0x9c ch, pool 9, count 2 -0x0125 CH_LOCAL_RAW_BUFFER_ENABLE 0x10 ch, 1 -0x0b0d CH_PREVIEW_STREAM_SET 0x10 ch, 1 -0x0138 CH_MASTER_SLAVE_SYNC_MODE_SET 0x10 ch, 0 -0x0100 CH_START 0x0c ch -0x0101 CH_STOP 0x0c ch -0x0104 CH_BUFFER_RETURN 0x0c ch -================ =================================== ==== =================== +====== ================================== ===== ======================= +Opcode Command Size Parameters +====== ================================== ===== ======================= +0x0003 CONFIG_GET 0x1c Camera channel count +0x3206 SET_DSID_CLR_MULTI_BC_REG_BASE 0x50 8 slots, range 0x2fc +0x0000 START 0x0c Proxy 0x11, Linux 0 +0x010d CH_INFO_GET 0x190 ch +0x0106 CH_CAMERA_CONFIG_GET 0x120 ch, index (all) +0x0024 FLICKER_SENSOR_SET 0x0c 0 +0x013b CH_SBS_ENABLE 0x10 ch, 1 +0x0107 CH_CAMERA_CONFIG_SELECT 0x10 ch, selected config +0x010e CH_BUFFER_RECYCLE_MODE_SET 0x10 ch, 1 (empty only) +0x010f CH_BUFFER_RECYCLE_START 0x0c ch +0x080c CH_CROP_SCL1_SET 0x1c ch, x, y, width, height +0x0b09 CH_OUTPUT_CONFIG_SCL1_SET 0x38 ch, output geometry +0x0208 CH_AE_FRAME_RATE_MAX_SET 0x10 ch, fps * 256 +0x020a CH_AE_FRAME_RATE_MIN_SET 0x10 ch, min_fps * 256 +0x0117 CH_BUFFER_POOL_CONFIG_SET (meta) 0x9c ch, pool 0, count 8 +0x0117 CH_BUFFER_POOL_CONFIG_SET (output) 0x9c ch, pool 9, count 2 +0x0125 CH_LOCAL_RAW_BUFFER_ENABLE 0x10 ch, 1 +0x0b0d CH_PREVIEW_STREAM_SET 0x10 ch, 1 +0x0138 CH_MASTER_SLAVE_SYNC_MODE_SET 0x10 ch, 0 +0x0100 CH_START 0x0c ch +0x0101 CH_STOP 0x0c ch +0x0104 CH_BUFFER_RETURN 0x0c ch +====== ================================== ===== ======================= The DSID bases are 0x2201d4000, 0x2221d4000, 0x2241d4000, 0x2261d4000, then four U64_MAX sentinels. Linux retains its existing boot-time PMP and @@ -205,7 +205,9 @@ is source-level ABI evidence, not a new live qualification of P010. selecting P010 would violate the proxy's guarded startup lifecycle. The new config query explicitly exchanges 0x120, rather than the unused legacy 0xdc current-config helper. Header/channel/ index checks and duplicated geometry checks guard decoding. The H17 - appended bytes remain uninterpreted. + appended bytes remain uninterpreted. The old preview validator also checks + CH_INFO_GET byte 0x138; the current Linux helper retains only the common + 0x118-byte prefix of its 0x190-byte reply and does not make that check. * Metadata: CH_INFO_GET reports 0x4d00 on both paths. The proxy rounds its metadata backing/pool to 0x8000; P010 does the same even on a 4 KiB host. NV12 retains the current driver's 0x4d00 pool and 16-buffer capacity. From 946458f3814ffd01ef9f2f3737e7c455363112f7 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 13:48:05 +1100 Subject: [PATCH 049/184] media: apple: isp: accept full-width P010 completion address pairs The buffer wire carries 64-bit addresses, while the proxy observed zero-extended low words on its firmware. Accept either complete canonical pair for a submitted lease and reject mixed or unrelated upper bits. Keep pool, plane and fresh-tag checks. Also assert that NV12 stays first in format enumeration. Assisted-by: LLM Signed-off-by: Ryan Murray --- .../driver-api/media/drivers/apple-isp-j616s.rst | 5 +++-- drivers/media/platform/apple/isp/isp-v4l2-test.c | 14 ++++++++++++-- drivers/media/platform/apple/isp/isp-v4l2.c | 11 +++++++---- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/Documentation/driver-api/media/drivers/apple-isp-j616s.rst b/Documentation/driver-api/media/drivers/apple-isp-j616s.rst index 707ac2c22e6e7..f92cecf30f3e5 100644 --- a/Documentation/driver-api/media/drivers/apple-isp-j616s.rst +++ b/Documentation/driver-api/media/drivers/apple-isp-j616s.rst @@ -81,8 +81,9 @@ Linux passes its actual contiguous extents to the rendered pool instead. P010 uses the proxy's eight metadata slots (0x8000 bytes each) and two rendered slots. The queue limits userspace to two rendered buffers, matching -the pool capacity. Completion matches both zero-extended low-32-bit plane -addresses, plane count, pool 9 and a fresh 64-bit submission tag; an old tag +the pool capacity. Completion matches either both full submitted addresses +or both zero-extended low-32-bit plane addresses, plus plane count, pool 9 +and a fresh 64-bit submission tag; an old tag cannot release a new lease. Buffer completion is published to videobuf2 only after the firmware report ACK and doorbell have been published. NV12 pool sizes and the T8140/Neo formats, presets and command path retain diff --git a/drivers/media/platform/apple/isp/isp-v4l2-test.c b/drivers/media/platform/apple/isp/isp-v4l2-test.c index 58af78581bf7e..6af295a7da23e 100644 --- a/drivers/media/platform/apple/isp/isp-v4l2-test.c +++ b/drivers/media/platform/apple/isp/isp-v4l2-test.c @@ -166,13 +166,16 @@ static void isp_test_p010_admission(struct kunit *test) struct apple_isp *isp = isp_test_alloc(test); struct isp_format *fmt = isp_get_current_format(isp); struct isp_preset preset = { .output_dim = {1280, 720}, .p010 = true }; - struct v4l2_fmtdesc desc = { .index = 1 }; + struct v4l2_fmtdesc desc = { .index = 0 }; isp->presets = &preset; isp->num_presets = 1; isp->hw = &isp_test_t6040; fmt->version = 0x958; KUNIT_ASSERT_EQ(test, isp_enum_pixelformat(isp, &desc), 0); + KUNIT_EXPECT_EQ(test, desc.pixelformat, V4L2_PIX_FMT_NV12); + desc.index = 1; + KUNIT_ASSERT_EQ(test, isp_enum_pixelformat(isp, &desc), 0); KUNIT_EXPECT_EQ(test, desc.pixelformat, V4L2_PIX_FMT_P010); desc.index = 2; KUNIT_EXPECT_EQ(test, isp_enum_pixelformat(isp, &desc), -EINVAL); @@ -203,8 +206,15 @@ static void isp_test_p010_completion_identity(struct kunit *test) KUNIT_EXPECT_FALSE(test, isp_p010_buffer_matches(&fmt, &buf, &desc)); desc.iovas[1]--; desc.iovas[0] |= BIT_ULL(40); + /* Reject mixed encodings, accept both exact full submitted addresses. */ + KUNIT_EXPECT_FALSE(test, isp_p010_buffer_matches(&fmt, &buf, &desc)); + desc.iovas[1] |= BIT_ULL(40); + KUNIT_EXPECT_TRUE(test, isp_p010_buffer_matches(&fmt, &buf, &desc)); + desc.iovas[0] |= BIT_ULL(41); + desc.iovas[1] |= BIT_ULL(41); KUNIT_EXPECT_FALSE(test, isp_p010_buffer_matches(&fmt, &buf, &desc)); - desc.iovas[0] &= ~BIT_ULL(40); + desc.iovas[0] = (u32)desc.iovas[0]; + desc.iovas[1] = (u32)desc.iovas[1]; desc.pool_type = CISP_POOL_TYPE_RENDERED; KUNIT_EXPECT_FALSE(test, isp_p010_buffer_matches(&fmt, &buf, &desc)); desc.pool_type = CISP_POOL_TYPE_RENDERED_SCL1; diff --git a/drivers/media/platform/apple/isp/isp-v4l2.c b/drivers/media/platform/apple/isp/isp-v4l2.c index 4fcde29410d2c..b5bfea73e2d82 100644 --- a/drivers/media/platform/apple/isp/isp-v4l2.c +++ b/drivers/media/platform/apple/isp/isp-v4l2.c @@ -59,11 +59,14 @@ static bool isp_p010_buffer_matches(const struct isp_format *fmt, const struct isp_buffer *buf, const struct isp_buflist_buffer *desc) { - /* H17 reports zero-extended low 32 bits, not the submitted high IOVA. */ + u64 y = buf->surfs[0].iova; + u64 uv = y + fmt->plane_size[0]; + bool full = desc->iovas[0] == y && desc->iovas[1] == uv; + bool low = desc->iovas[0] == (u32)y && desc->iovas[1] == (u32)uv; + + /* Accept the submitted pair or the zero-extended pair observed on H17. */ return desc->num_planes == 2 && - desc->pool_type == CISP_POOL_TYPE_RENDERED_SCL1 && - desc->iovas[0] == (u32)buf->surfs[0].iova && - desc->iovas[1] == (u32)(buf->surfs[0].iova + fmt->plane_size[0]) && + desc->pool_type == CISP_POOL_TYPE_RENDERED_SCL1 && (full || low) && !desc->iovas[2] && !desc->iovas[3] && desc->tag == buf->tag; } From 154afdbd8987d796fba232760e32d671da4b818c Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 13:52:36 +1100 Subject: [PATCH 050/184] drm/apple: derive J616 adaptive sync capability from its DT panel The J616 panel compatible identifies the ProMotion panel. Record that capability when reading the panel node and use it with the existing firmware, main-display, mode-range and failure checks. Remove the T6041 VRR module parameter so support follows the DT without a boot flag. Exercise capable and incapable panels and retain the fixed-refresh retry. Assisted-by: LLM Signed-off-by: Ryan Murray --- Documentation/gpu/apple-dcp-t6041-panel.rst | 7 ++++--- drivers/gpu/drm/apple/dcp-internal.h | 2 ++ drivers/gpu/drm/apple/dcp.c | 2 ++ drivers/gpu/drm/apple/iomfb.c | 7 +------ .../selftests/drivers/gpu/apple-dcp/display.py | 11 +++++------ 5 files changed, 14 insertions(+), 15 deletions(-) diff --git a/Documentation/gpu/apple-dcp-t6041-panel.rst b/Documentation/gpu/apple-dcp-t6041-panel.rst index 564d7e3bb38c9..c615dc32ac1e7 100644 --- a/Documentation/gpu/apple-dcp-t6041-panel.rst +++ b/Documentation/gpu/apple-dcp-t6041-panel.rst @@ -26,9 +26,10 @@ does not synthesize lower refresh modes from a 120 Hz mode. Fractional rates remain in 16.16 Hz until the DRM pixel clock is calculated. A ProMotion mode needs desktop presentation deadlines even with adaptive sync disabled. -Adaptive sync is experimental and disabled by default. To qualify the internal -panel, boot with ``appledrm.t6041_vrr=1``. This enables the connector capability -only for the internal main display and a valid adaptive-sync range. The +The DT panel compatible ``apple,panel-j616`` identifies the ProMotion panel and +enables adaptive sync support without a module parameter. The connector capability +requires the desktop firmware profile, the internal main display, and a valid +adaptive-sync range. Other panel compatibles do not acquire this capability. The existing internal ProMotion convention supplies 24 Hz to the mode rate when the firmware omits both range fields or reports both as zero placeholders; a partial, duplicate or invalid range diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 7a3a969fd7e76..b720cd80c11d6 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -186,6 +186,8 @@ struct dcp_panel { int height_mm; /// panel has a mini-LED backlight bool has_mini_led; + /* The DT panel compatible identifies an adaptive-sync capable panel. */ + bool has_vrr; }; enum dcp_iomfb_method_profile { diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 6a2eebc1cf057..1d0b81aa3ad65 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -4937,6 +4937,8 @@ static int dcp_comp_bind_pipe(struct device *dev, struct device *main, void *dat dev_err(dev, "Missing property 'apple,max-brightness'\n"); } + /* J616 ProMotion capability belongs to the panel, not a boot option. */ + dcp->panel.has_vrr = of_device_is_compatible(panel_np, "apple,panel-j616"); of_property_read_u32(panel_np, "width-mm", &dcp->panel.width_mm); /* use adjusted height as long as the notch is hidden */ of_property_read_u32(panel_np, height_prop[!dcp->notch_height], diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 6d38d96ed1365..b56d74a7ac19e 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -142,14 +142,9 @@ static inline u64 dcpep_ack(enum dcp_context_id id) return dcpep_msg(id, 0, 0) | IOMFB_MSG_ACK; } -/* Keep the established fixed-refresh default until panel qualification. */ -static bool t6041_vrr; -module_param(t6041_vrr, bool, 0444); -MODULE_PARM_DESC(t6041_vrr, "Enable experimental adaptive sync on the T6041 internal panel"); - bool dcp_desktop_vrr_supported(struct apple_dcp *dcp) { - return t6041_vrr && dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && + return dcp->panel.has_vrr && dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && dcp_has_panel(dcp) && dcp->main_display && !READ_ONCE(dcp->desktop->vrr_failed); } diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/display.py b/tools/testing/selftests/drivers/gpu/apple-dcp/display.py index 5ee71a0b20993..1236325213994 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/display.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/display.py @@ -226,14 +226,13 @@ def adaptive(root): struct dcp_wait_cookie { u32 status; bool done,replied; }; struct dcp_set_digital_out_mode_req {u32 color_mode_id,timing_mode_id;}; struct dcp_set_parameter_dcp { u32 param; u64 value[4]; u32 count; }; -struct apple_dcp {void *dev;int fw_compat;bool main_display,panel;struct {bool vrr_failed,modeset_callback_error;} *desktop;}; -static bool t6041_vrr; +struct apple_dcp {void *dev;int fw_compat;bool main_display,has_panel;struct {bool has_vrr;} panel;struct {bool vrr_failed,modeset_callback_error;} *desktop;}; struct drm_display_mode {int id;}; struct drm_crtc_state {bool vrr_enabled;struct drm_display_mode mode;}; struct dcp_display_mode {bool vrr;}; static bool lookup_mode(struct apple_dcp *d,struct drm_display_mode *m,struct dcp_display_mode *out) {out->vrr=m->id==120;return m->id==60 || m->id==120;} -static bool dcp_has_panel(struct apple_dcp *d) {return d->panel;} +static bool dcp_has_panel(struct apple_dcp *d) {return d->has_panel;} static u32 get_unaligned_le32(const void *p) {u32 v;memcpy(&v,p,4);return v;} typedef void (*callback)(struct apple_dcp *,void *,void *); static callback pending; @@ -262,18 +261,18 @@ def adaptive(root): {u32 zero=0;reply(d,2,&zero);assert(requested.color_mode_id==11);reply(d,3,&zero);assert(m->wait.done && m->wait.replied && !m->wait.status);} int main(void) { - struct apple_dcp d={.fw_compat=DCP_FIRMWARE_V_26_6_DESKTOP,.main_display=true,.panel=true}; + struct apple_dcp d={.fw_compat=DCP_FIRMWARE_V_26_6_DESKTOP,.main_display=true,.has_panel=true}; __typeof__(*d.desktop) desktop={0};d.desktop=&desktop; struct drm_crtc_state state={.vrr_enabled=true,.mode={120}}; assert(dcp_desktop_vrr_check(&d,&state)==-EINVAL); - assert(!dcp_desktop_vrr_supported(&d));t6041_vrr=true; + assert(!dcp_desktop_vrr_supported(&d));d.panel.has_vrr=true; assert(!dcp_desktop_vrr_check(&d,&state)); state.mode.id=60;assert(dcp_desktop_vrr_check(&d,&state)==-EINVAL); state.vrr_enabled=false;assert(!dcp_desktop_vrr_check(&d,&state)); state.vrr_enabled=true;state.mode.id=120; assert(dcp_desktop_vrr_supported(&d)); d.main_display=false;assert(!dcp_desktop_vrr_supported(&d));d.main_display=true; - d.panel=false;assert(!dcp_desktop_vrr_supported(&d));d.panel=true; + d.has_panel=false;assert(!dcp_desktop_vrr_supported(&d));d.has_panel=true; d.fw_compat=3;assert(!dcp_desktop_vrr_supported(&d));d.fw_compat=DCP_FIRMWARE_V_26_6_DESKTOP; struct dcp_modeset_cookie m;u32 zero=0,error=1; initialize(&m);dcp_modeset_sync(&d,&m); From 0e173632391454447506ea7c8259d47cb696a625 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 13:52:37 +1100 Subject: [PATCH 051/184] drm/apple: use the existing KMS debug category for identity inspection Remove the panel_identity_debug module parameter. The existing DRM KMS debug category gates structural inspection and its bounded per-callback budget. Disabled inspection consumes no records, and no values are logged. Update the host fixture to exercise the existing debug gate. Assisted-by: LLM Signed-off-by: Ryan Murray --- Documentation/gpu/apple-dcp-t6041-panel.rst | 7 ++++--- drivers/gpu/drm/apple/iomfb.c | 8 ++------ tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py | 6 ++++-- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/Documentation/gpu/apple-dcp-t6041-panel.rst b/Documentation/gpu/apple-dcp-t6041-panel.rst index c615dc32ac1e7..350d22e927e13 100644 --- a/Documentation/gpu/apple-dcp-t6041-panel.rst +++ b/Documentation/gpu/apple-dcp-t6041-panel.rst @@ -77,8 +77,8 @@ updates for the same vendor/product retain known optional identity fields. Desktop identity diagnostics and fallback ----------------------------------------- -Enable ``appledrm.panel_identity_debug=1`` for a diagnostic boot (or set the -module's ``panel_identity_debug`` parameter before a modeset). D552, D561, +Identity inspection uses the existing DRM KMS debug category (``drm.debug`` +bit 2), with no driver-specific switch. When that category is enabled, D552, D561, D114 and chunked property delivery report the outer key, serialized key names, value type numbers and lengths. D000 identifies chunked delivery. Type numbers are dictionary 1, array 2, integer 4, string 9, blob 10 and boolean 11. Values @@ -86,7 +86,8 @@ are never printed. Each property walk stops at depth 4 or 64 objects, and the DCP stops after 128 diagnostic records per callback class (512 total), with bounded inspection-status lines in addition. Atomic reservations include framing records. D552, D561, D114 and chunked delivery have separate budgets so periodic statistics cannot hide the panel setters. -The switch defaults off. Malformed serialization only stops inspection. +When KMS debugging is disabled no inspection budget is consumed. Malformed +serialization only stops inspection. A real EDID with a usable identity takes priority. It may arrive as raw bytes, a serialized blob, or an ``EDID``, ``edid`` or ``IODisplayEDID`` blob nested diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index b56d74a7ac19e..fb6d435787cae 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include "dcp.h" @@ -40,11 +41,6 @@ #include "parser.h" #include "trace.h" -/* Enable for one diagnostic boot with appledrm.panel_identity_debug=1. */ -static bool panel_identity_debug; -module_param(panel_identity_debug, bool, 0600); -MODULE_PARM_DESC(panel_identity_debug, "Bounded desktop panel property keys and types (no values)"); - struct dcp_identity_trace { struct apple_dcp *dcp; atomic_t *count; @@ -74,7 +70,7 @@ void dcp_trace_panel_property(struct apple_dcp *dcp, int tag, const char *key, u32 slot = tag == 552 ? 0 : tag == 561 ? 1 : tag == 114 ? 2 : 3; int ret; - if (!READ_ONCE(panel_identity_debug) || + if (!drm_debug_enabled(DRM_UT_KMS) || dcp->fw_compat != DCP_FIRMWARE_V_26_6_DESKTOP || !dcp_has_panel(dcp)) return; trace.dcp = dcp; diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py index 5ba5d5e05e550..b7125295bcc9d 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py @@ -863,7 +863,9 @@ def identity_trace_budget(root): #define min_t(t,a,b) ((t)(a)<(t)(b)?(t)(a):(t)(b)) #define DCP_FIRMWARE_V_26_6_DESKTOP 5 struct apple_dcp {int fw_compat;bool has_panel;void *dev;struct {atomic_t identity_log_count[4];} *desktop;}; -static bool panel_identity_debug; +static bool kms_debug; +#define DRM_UT_KMS 4 +static bool drm_debug_enabled(int category) {assert(category==DRM_UT_KMS);return kms_debug;} static unsigned messages; static char output[256]; static bool dcp_has_panel(struct apple_dcp *d) {return d->has_panel;} @@ -889,7 +891,7 @@ def identity_trace_budget(root): __typeof__(*d.desktop) desktop={0};d.desktop=&desktop; const char secret[]="SYNTHETIC_VALUE_NOT_FOR_LOGS"; dcp_trace_panel_property(&d,561,"Property",8,secret,sizeof(secret));assert(!messages); - panel_identity_debug=true;d.fw_compat=1; + kms_debug=true;d.fw_compat=1; dcp_trace_panel_property(&d,561,"Property",8,secret,sizeof(secret));assert(!messages); d.fw_compat=5;d.has_panel=false; dcp_trace_panel_property(&d,561,"Property",8,secret,sizeof(secret));assert(!messages); From a18546bbc60eef5835242babc1a9ad26831951af Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 13:53:08 +1100 Subject: [PATCH 052/184] docs: explain DT-driven T6041 capability and loader handoff contract Record that the kernel already adopts published handoff properties without a chosen opt-in. Removing the current Stage 2 switch requires a producer change in m1n1, keeping its transactional validation and the kernel's resource, firmware and session checks. Describe the required producer and test behavior without adding a policy flag or weakening the consumer. Assisted-by: LLM Signed-off-by: Ryan Murray --- Documentation/gpu/apple-dcp-t6041-panel.rst | 30 +++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/Documentation/gpu/apple-dcp-t6041-panel.rst b/Documentation/gpu/apple-dcp-t6041-panel.rst index 350d22e927e13..aa15af0f59913 100644 --- a/Documentation/gpu/apple-dcp-t6041-panel.rst +++ b/Documentation/gpu/apple-dcp-t6041-panel.rst @@ -131,3 +131,33 @@ both ``brightness`` and ``actual_brightness``. The work runs outside the receive path's locks and is drained during shutdown. Takeover sends the same event when it updates the inherited level. Requested brightness remains the user's target through the fade; notifications do not replace it with an intermediate level. + +DT capability and boot-loader handoff +----------------------------------- + +The J616 panel's existing DT compatible supplies adaptive-sync capability; +there is no T6041 VRR module parameter. Firmware and mode validation, the main +panel restriction, and the fixed-refresh failure latch still apply. EDID and +backlight notifications have no driver-specific tunables. + +The kernel does not consume ``chosen.asahi,t6041-dcp``. Its early T6041 gate +already follows the handoff published by m1n1: ``apple,t6041-handoff = <1>`` on +DCP, display and PIODMA, firmware compatibility 26.6.2, the expected DART stream +and mailbox relationships, initially disabled nodes, and a running DCP. +The DCP driver separately validates register resources and the firmware memory +handoff. Missing or invalid handoff data leaves the boot framebuffer in place. +These checks must remain when the loader's policy changes. + +The current m1n1 producer uses that chosen property to decide whether to attempt +publication. Removing it cannot be completed by a kernel change: a loader with +no published handoff provides nothing for the kernel to adopt. The producer +needs to select the T6041 handoff from its existing chip/board match and the +supported DCP/display/panel DT compatibles, then run the existing ADT, power, +running-session, firmware-segment, reservation and live DART mapping checks +without a chosen opt-in. Only a successful transaction may publish the existing +markers, memory regions and firmware properties. Keeping nodes disabled until +the kernel accepts that contract preserves compatibility with older kernels. +A failed transaction must publish no partial handoff. Loader host tests should +cover supported DT without the chosen property, an unsupported board/DT, and +each failed validation, as well as unchanged other-SoC policy. No new policy +flag or kernel bypass is needed. This kernel series does not replace Stage 2. From 6741e1e8703b823edbfb2cf0e067bc51da15797a Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 11:48:36 +1100 Subject: [PATCH 053/184] arm64: dts: apple: repair the J616s PCI Wi-Fi schema description Carry the independent #458 prerequisite for complete T6040 DT validation. Keep the PCI identity and family fallback and omit the redundant root-port compatible. Its diff can disappear once #458 lands. Link: https://github.com/aurora-silicon/linux/pull/458 Assisted-by: LLM Signed-off-by: Ryan Murray --- .../devicetree/bindings/net/wireless/brcm,bcm4329-fmac.yaml | 1 + arch/arm64/boot/dts/apple/t6040-j616s.dts | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/net/wireless/brcm,bcm4329-fmac.yaml b/Documentation/devicetree/bindings/net/wireless/brcm,bcm4329-fmac.yaml index 81fd3e37452a6..8f935a2d2b320 100644 --- a/Documentation/devicetree/bindings/net/wireless/brcm,bcm4329-fmac.yaml +++ b/Documentation/devicetree/bindings/net/wireless/brcm,bcm4329-fmac.yaml @@ -46,6 +46,7 @@ properties: - cypress,cyw4373-fmac - cypress,cyw43012-fmac - infineon,cyw43439-fmac + - pci14e4,4434 # BCM4388 - const: brcm,bcm4329-fmac - enum: - brcm,bcm4329-fmac diff --git a/arch/arm64/boot/dts/apple/t6040-j616s.dts b/arch/arm64/boot/dts/apple/t6040-j616s.dts index a40a3fa1e1dc0..65ee34b8b362f 100644 --- a/arch/arm64/boot/dts/apple/t6040-j616s.dts +++ b/arch/arm64/boot/dts/apple/t6040-j616s.dts @@ -569,7 +569,6 @@ device_type = "pci"; port00: pci@0,0 { - compatible = "pciclass,0604"; reg = <0 0 0 0 0>; ranges; interrupt-map = <0 0 0 1 &port00 0 0 0 0 0 0 0 2 &port00 0 0 0 1 0 0 0 3 &port00 0 0 0 2 0 0 0 4 &port00 0 0 0 3>; @@ -592,7 +591,7 @@ }; wifi@0,0 { - compatible = "brcm,bcm4329-fmac"; + compatible = "pci14e4,4434", "brcm,bcm4329-fmac"; reg = <0 0 0 0 0>; brcm,ccode-map-trivial; brcm,board-type = "apple,hostomel"; From 804bba86ca55cb0598e90c99b3dafc3e48b40c78 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 11:48:37 +1100 Subject: [PATCH 054/184] drm/asahi: use the loaded image to select identified firmware Treat the OS version as a candidate-order hint. Continue to require an exact admitted loaded TEXT image and preserve #453's images list. This extends Jonathan Goyvaerts' shared identity work. Originally-by: Jonathan Goyvaerts Link: https://github.com/aurora-silicon/linux/pull/428 Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/gpu/drm/asahi/g17/fw/variant.rs | 10 ++++------ drivers/gpu/drm/asahi/g17/mod.rs | 20 +++++++++++--------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/drivers/gpu/drm/asahi/g17/fw/variant.rs b/drivers/gpu/drm/asahi/g17/fw/variant.rs index d2d6d2404dfc8..78951f6d10a7e 100644 --- a/drivers/gpu/drm/asahi/g17/fw/variant.rs +++ b/drivers/gpu/drm/asahi/g17/fw/variant.rs @@ -64,10 +64,9 @@ pub(crate) struct Variant { pub(crate) programs: super::render::RegisterPrograms, } -/// A firmware build a SoC supports, selected by the macOS release it ships with and proven by -/// its loaded TEXT. +/// A firmware build a SoC supports, identified by its loaded TEXT. pub(crate) struct Build { - /// macOS version (major, minor, patch). + /// macOS version (major, minor, patch), used to order image candidates. pub(crate) os_version: [u32; 3], /// Name, for the log. pub(crate) name: &'static str, @@ -81,9 +80,8 @@ pub(crate) struct Build { pub(crate) enum Firmware { /// The SoC runs a single build, which is not identified (A18 Pro). Fixed(&'static Variant), - /// One of these builds, selected by the macOS version that the boot loader reports in - /// `apple,firmware-version` (the release whose boot firmware loaded the GPU firmware) and - /// then proven by the loaded TEXT. Any other version or image is refused. + /// One of these exact loaded images. The loader's `apple,firmware-version` is only + /// a candidate-ordering hint because multiple OS releases can share an iBoot string. Identified(&'static [Build]), } diff --git a/drivers/gpu/drm/asahi/g17/mod.rs b/drivers/gpu/drm/asahi/g17/mod.rs index ae78efaad1a95..10891ce09babf 100644 --- a/drivers/gpu/drm/asahi/g17/mod.rs +++ b/drivers/gpu/drm/asahi/g17/mod.rs @@ -1101,24 +1101,26 @@ impl Gpu { fw::variant::Firmware::Identified(builds) => builds, }; let version = Self::os_firmware_version(dev); - if let Some(build) = builds.iter().find(|build| Some(build.os_version) == version) { - dev_info!(dev.as_ref(), "GPU firmware: {}\n", build.name); - Self::identify_image(dev, build).map_err(|_| ENODEV)?; - return Ok(build.variant); + // iBoot strings can be shared by OS releases. Prefer the reported release's + // candidate, but admit only an exact loaded image, regardless of that hint. + let preferred = builds.iter().filter(|build| Some(build.os_version) == version); + let others = builds.iter().filter(|build| Some(build.os_version) != version); + for build in Iterator::chain(preferred, others) { + if Self::identify_image(dev, build).is_ok() { + dev_info!(dev.as_ref(), "GPU firmware: {}\n", build.name); + return Ok(build.variant); + } } dev_err!( dev.as_ref(), - "Unsupported GPU firmware: macOS version {:?} (apple,firmware-version)\n", + "Unsupported GPU firmware image (reported macOS version {:?})\n", version ); - for build in builds.iter() { - dev_err!(dev.as_ref(), "Supported GPU firmware: {}\n", build.name); - } Err(ENODEV) } /// Checks that the loaded TEXT (the `fw-text` region) is one of `build`'s images, with the DATA size - /// of the `fw-data` region. The version property only says which build to expect. + /// of the `fw-data` region. The version property is only a selection hint. fn identify_image(dev: &AsahiDrmDevice, build: &'static fw::variant::Build) -> Result { let node = dev.as_ref().as_ref().of_node().ok_or(EINVAL)?; let text = node.reserved_mem_region_to_resource_byname(c_str!("fw-text"))?; From a8fb1380b41456833b30014e6e354fba31551cd3 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 11:48:37 +1100 Subject: [PATCH 055/184] drm/asahi: introduce the complete T6040 calibration reader Validate version 2 of the per-chip handoff, sixteen raw OPPs and selector slots, eleven populated selectors, zero tails and base state 1. There is no single-unit fallback. The initdata patch wires the reader into probe and firmware construction so this patch adds no unused compiled items. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/gpu/drm/asahi/g17/calibration.rs | 231 +++++++++++++++++++++++ 1 file changed, 231 insertions(+) create mode 100644 drivers/gpu/drm/asahi/g17/calibration.rs diff --git a/drivers/gpu/drm/asahi/g17/calibration.rs b/drivers/gpu/drm/asahi/g17/calibration.rs new file mode 100644 index 0000000000000..d973349d35d40 --- /dev/null +++ b/drivers/gpu/drm/asahi/g17/calibration.rs @@ -0,0 +1,231 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! Per-chip T6040 power data supplied by the boot loader. + +use super::fw::initdata::{Opp, PerfConfig, PERF_STATES}; +use crate::driver::AsahiDrmDevice; +use kernel::{c_str, of, prelude::*, str::CStr}; + +/// Validated calibration of the running chip. There is no built-in fallback. +pub(super) struct Calibration { + pub(super) perf: PerfConfig, + pub(super) leakage: [u32; 56], + pub(super) max_power_mw: u32, + core_mask: u32, + base: usize, +} + +fn words(node: &of::Node, name: &CStr) -> Result<[u32; N]> { + let values: KVec = node.get_property(name)?; + values.as_slice().try_into().map_err(|_| EINVAL) +} + +// Raw ADT OPP slots and SGX register slots number 16. The supported firmware consumes +// the 11 populated selectors; the remaining handoff entries must all be zero. +const RAW_STATES: usize = 16; + +fn active(values: [u32; RAW_STATES]) -> Result<[u32; PERF_STATES]> { + if values[PERF_STATES..].iter().any(|&v| v != 0) { + return Err(EINVAL); + } + values[..PERF_STATES].try_into().map_err(|_| EINVAL) +} + +fn map(values: [u32; RAW_STATES]) -> Result<[u8; PERF_STATES]> { + let values = active(values)?; + if values[0] != 0 + || values[PERF_STATES - 1] != RAW_STATES as u32 - 1 + || values.iter().any(|&v| v >= RAW_STATES as u32) + || values.windows(2).any(|p| p[0] >= p[1]) + { + return Err(EINVAL); + } + Ok(values.map(|v| v as u8)) +} + +fn relative(values: &[u32; PERF_STATES]) -> bool { + values[0] == 0 + && values[PERF_STATES - 1] == 100 + && values.windows(2).all(|p| p[0] <= p[1] && p[1] <= 100) +} + +impl Calibration { + pub(super) fn read(dev: &AsahiDrmDevice) -> Result { + let node = dev.as_ref().as_ref().of_node().ok_or(EIO)?; + if node.get_property::(c_str!("apple,t6040-handoff-version"))? != 2 { + return Err(EINVAL); + } + let core_mask = node.get_property(c_str!("apple,core-mask"))?; + let max_power_mw = node.get_property(c_str!("apple,max-power-mw"))?; + let base = node.get_property::(c_str!("apple,perf-base-pstate"))? as usize; + if core_mask == 0 + || core_mask & !0xfffff != 0 + || !(1..=200_000).contains(&max_power_mw) + || base != 1 + { + return Err(EINVAL); + } + let perf = PerfConfig { + states_a: map(words(&node, c_str!("apple,perf-map-a"))?)?, + states_b: map(words(&node, c_str!("apple,perf-map-b"))?)?, + relative_a: active(words(&node, c_str!("apple,perf-relative-a"))?)?, + relative_b: active(words(&node, c_str!("apple,perf-relative-b"))?)?, + sram_min_mv: 810, + }; + if !relative(&perf.relative_a) + || !relative(&perf.relative_b) + || perf.relative_b[..=base].iter().any(|&v| v != 0) + { + return Err(EINVAL); + } + let core = words::<2>(&node, c_str!("apple,core-leak-coef"))?; + let sram = words::<2>(&node, c_str!("apple,sram-leak-coef"))?; + let cs = words::<1>(&node, c_str!("apple,cs-leak-coef"))?; + let afr = words::<1>(&node, c_str!("apple,afr-leak-coef"))?; + // Positive finite binary32 values. Integer comparisons preserve their ordering. + if Iterator::chain( + Iterator::chain(Iterator::chain(core.iter(), &sram), &cs), + &afr, + ) + .any(|&v| !(0x3f80_0000..0x4780_0000).contains(&v)) + { + return Err(EINVAL); + } + let mut leakage = [0; 56]; + leakage[..2].copy_from_slice(&core); + leakage[32..34].copy_from_slice(&sram); + leakage[52] = cs[0]; + leakage[54] = afr[0]; + Ok(Self { + perf, + leakage, + max_power_mw, + core_mask, + base, + }) + } + + /// Check the loader's mask against the GPU before starting its firmware. + pub(super) fn check_cores(&self, masks: &[u32]) -> Result { + if masks != [self.core_mask & 0x3ff, self.core_mask >> 10] { + return Err(EINVAL); + } + Ok(()) + } + + /// The performance maps index OPPs in ADT order, rather than sorted frequency order. + pub(super) fn check_opps(&self, opps: &[Opp]) -> Result { + if opps.len() != 16 || opps[0].freq_hz != 0 || opps.iter().any(|opp| opp.cells != 2) { + return Err(EINVAL); + } + for map in [&self.perf.states_a, &self.perf.states_b] { + let mut previous = 0; + for &index in &map[1..] { + let opp = &opps[usize::from(index)]; + let mhz = opp.freq_hz / 1_000_000; + if mhz <= previous + || opp.volt_uv[..2] + .iter() + .any(|v| !(400_000..=1_300_000).contains(v)) + { + return Err(EINVAL); + } + previous = mhz; + } + } + let base = opps[usize::from(self.perf.states_a[self.base])].freq_hz / 1_000_000; + let top = opps[usize::from(self.perf.states_a[PERF_STATES - 1])].freq_hz / 1_000_000; + for (i, &index) in self.perf.states_a.iter().enumerate() { + let freq = opps[usize::from(index)].freq_hz / 1_000_000; + let expected = if i < self.base { + 0 + } else { + 100 * (freq - base) / (top - base) + }; + if u64::from(self.perf.relative_b[i]) != expected { + return Err(EINVAL); + } + } + Ok(()) + } +} + +#[kunit_tests(asahi_t6040_calibration)] +mod tests { + use super::*; + + fn chip() -> Calibration { + Calibration { + perf: PerfConfig { + states_a: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + states_b: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + relative_a: [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100], + relative_b: [0, 0, 11, 22, 33, 44, 55, 66, 77, 88, 100], + sram_min_mv: 810, + }, + leakage: [0; 56], + max_power_mw: 70_000, + core_mask: 0xfffff, + base: 1, + } + } + + #[test] + fn operating_points_and_mask_must_match_the_handoff() { + let chip = chip(); + let mut opps = [Opp { + freq_hz: 0, + volt_uv: [800_000; super::super::fw::initdata::OPP_CELLS], + cells: 2, + }; 16]; + for (i, opp) in opps.iter_mut().enumerate() { + opp.freq_hz = i as u64 * 100_000_000; + } + assert!(chip.check_opps(&opps).is_ok()); + opps[0].freq_hz = 100_000_000; + assert!(chip.check_opps(&opps).is_err()); + opps[0].freq_hz = 0; + assert!(chip.check_cores(&[0x3ff, 0x3ff]).is_ok()); + assert!(chip.check_cores(&[0x3ff, 0x1ff]).is_err()); + assert!(chip.check_cores(&[0xfffff]).is_err()); + opps[5].volt_uv[1] = 0; + assert!(chip.check_opps(&opps).is_err()); + opps[5].volt_uv[1] = 800_000; + opps[5].freq_hz = opps[4].freq_hz; + assert!(chip.check_opps(&opps).is_err()); + opps[5].freq_hz = 501_000_000; + assert!(chip.check_opps(&opps).is_ok()); + opps[5].freq_hz = 511_000_000; + assert!(chip.check_opps(&opps).is_err()); + } + + #[test] + fn maps_refuse_off_repeats_and_out_of_range() { + let valid = [0, 1, 2, 3, 4, 6, 8, 10, 12, 14, 15, 0, 0, 0, 0, 0]; + assert!(map(valid).is_ok()); + for (index, value) in [ + (0, 1), + (1, 0), + (5, 4), + (10, 16), + (10, u32::MAX), + (10, 14), + (11, 1), + (15, 15), + ] { + let mut bad = valid; + bad[index] = value; + assert!(map(bad).is_err()); + } + } + + #[test] + fn relative_tables_are_bounded_and_monotonic() { + let mut values = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]; + assert!(relative(&values)); + values[5] = 101; + assert!(!relative(&values)); + values[5] = 39; + assert!(!relative(&values)); + } +} From 53877eaebd4b9d4d41ef9e1ce56c00d038192cff Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 11:48:39 +1100 Subject: [PATCH 056/184] drm/asahi: build T6040 initdata from validated per-chip calibration Wire the reader into probe and firmware construction, validating OPPs and enabled cores before firmware starts. Use the maps, leakage and power ceiling in the 25G83 objects, replacing bin-specific output hashes with layout and host encoder checks. Keep the Neo path unchanged. The old ceiling declaration remains only for the hardware-default reference removed by the next patch. Firmware uses the calibration. Retain GPL-2.0-only and the Gravity Linux encoder attribution to Cody Ho and Niklas Sheth. The original T6040 work is by Jonathan Goyvaerts. Originally-by: Jonathan Goyvaerts Link: https://github.com/aurora-silicon/linux/pull/428 Link: https://github.com/GravityLinux/linux Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/gpu/drm/asahi/float.rs | 5 + .../gpu/drm/asahi/g17/fw/initdata/v25g83.rs | 111 ++++-------------- drivers/gpu/drm/asahi/g17/initdata.rs | 6 +- drivers/gpu/drm/asahi/g17/initdata/v25g83.rs | 44 +++---- drivers/gpu/drm/asahi/g17/mod.rs | 12 ++ 5 files changed, 66 insertions(+), 112 deletions(-) diff --git a/drivers/gpu/drm/asahi/float.rs b/drivers/gpu/drm/asahi/float.rs index 46c05699d7722..224e92c6d97e2 100644 --- a/drivers/gpu/drm/asahi/float.rs +++ b/drivers/gpu/drm/asahi/float.rs @@ -44,6 +44,11 @@ struct F32U { } impl F32 { + /// Return the IEEE-754 binary32 representation. + pub(crate) const fn to_bits(self) -> u32 { + self.0 + } + /// Convert a raw 32-bit representation into an F32 pub(crate) const fn from_bits(u: u32) -> F32 { F32(u) diff --git a/drivers/gpu/drm/asahi/g17/fw/initdata/v25g83.rs b/drivers/gpu/drm/asahi/g17/fw/initdata/v25g83.rs index c237896a49fb1..db554b0d9b8fc 100644 --- a/drivers/gpu/drm/asahi/g17/fw/initdata/v25g83.rs +++ b/drivers/gpu/drm/asahi/g17/fw/initdata/v25g83.rs @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0-only OR MIT +// SPDX-License-Identifier: GPL-2.0-only //! Initialization data of the 25G83 firmware build (macOS 26.6, `mBoot-18000.161.10`). //! @@ -17,9 +17,9 @@ //! firmware on the M4 Pro, by evaluating them for this SoC; see the commit that added them. use super::{ - fill_words, perf_states, CompletionRingDescriptor, HwDataArgs, Opp, PerfGroup, RegisterMapEntry, - Words, COMPLETION_RING_ENTRIES, COMPLETION_RING_ENTRY_SIZE, COMPLETION_RING_INDEX, PERF_STATES, - REGISTER_MAP_SLOTS, + fill_words, perf_states, CompletionRingDescriptor, HwDataArgs, Opp, PerfGroup, + RegisterMapEntry, Words, COMPLETION_RING_ENTRIES, COMPLETION_RING_ENTRY_SIZE, + COMPLETION_RING_INDEX, PERF_STATES, REGISTER_MAP_SLOTS, }; use kernel::prelude::*; @@ -379,28 +379,15 @@ impl HwData25G83 { } } +/// Transitional hardware default, removed with the inert T6040 defaults. +pub(crate) const MAX_POWER_MW: u32 = 69_116; + /// Power configuration control period, in 24 MHz clocks (8 ms). const PERIOD_CLOCKS: u32 = 200_000; -/// Highest GPU power, in mW. -pub(crate) const MAX_POWER_MW: u32 = 69_116; -/// [`MAX_POWER_MW`] as an IEEE-754 single. -const MAX_POWER_MW_F32: u32 = 0x4786_fe00; /// Temperature sensors read by the die and MTR temperature limiters. const TEMP_SENSOR_MASK: u32 = 0x2a22_2aa2; -/// Leakage coefficients, IEEE-754 singles: three arrays of sixteen clusters, then four arrays -/// of two dies. Not found in the ADT; constant for the SoC. -const LEAKAGE: [u32; 56] = { - let mut words = [0; 56]; - words[0] = 0x44f0_3000; // 1921.5 - words[1] = 0x44f3_0000; // 1944.0 - words[32] = 0x4373_0000; // 243.0 - words[33] = 0x4373_0000; // 243.0 - words[52] = 0x437b_0000; // 251.0 - words[54] = 0x43bd_c000; // 379.5 - words -}; -/// Offset of [`LEAKAGE`] in the power configuration. +/// Offset of the per-chip leakage coefficients in the power configuration. const LEAKAGE_OFFSET: usize = 0x642c; /// Offset in the power configuration of the GPU address of the configuration's status (the /// configuration named by the root, `+0x80`). @@ -433,12 +420,10 @@ const POWER_CONFIG_WORDS: Words = &[ (0x9c4, 0x3f7f2e9f, 1), (0x9cc, 0x3b516154, 1), (0x9d4, 0x3ca59586, 1), - (0x9e0, MAX_POWER_MW_F32, 1), (0x9e4, 0x40a90fdb, 1), (0x9ec, 0xbc6d0cfe, 1), (0x9f0, 0x64, 1), (0x9f4, 0x3e8, 2), - (0xa04, MAX_POWER_MW, 1), (0xa38, 0x3e8, 1), (0xa70, 0x8, 1), (0xa7c, 0x3f000000, 1), @@ -448,7 +433,6 @@ const POWER_CONFIG_WORDS: Words = &[ (0xa9c, 0x3ecf2e49, 1), (0xaa8, 0x64, 1), (0xaac, 0x3e8, 1), - (0xabc, MAX_POWER_MW, 1), (0xac8, 0x10, 1), (0xad0, 0x5dc00, 1), (0xb28, 0x50, 1), @@ -508,7 +492,6 @@ const POWER_CONFIG_WORDS: Words = &[ (0x27b8, 0xc80000, 1), (0x2870, 0x3e8, 1), (0x28c0, 0x1, 1), - (0x28c8, MAX_POWER_MW, 3), (0x28e0, 0x3f800000, 1), (0x28e4, 0x4, 1), (0x28e8, 0x8, 1), @@ -521,8 +504,6 @@ const POWER_CONFIG_WORDS: Words = &[ (0x29e8, 0x3f4f2e49, 1), (0x29f4, 0x28, 1), (0x29f8, 0x3e8, 2), - (0x2a04, MAX_POWER_MW_F32, 1), - (0x2a08, MAX_POWER_MW, 1), (0x2a14, 0xf8, 1), (0x2a1c, 0x5ad200, 1), (0x2a3c, 0x3e8, 1), @@ -591,27 +572,34 @@ unsafe impl Zeroable for PowerConfig25G83 {} static_assert!(LEAKAGE_OFFSET + size_of::<[u32; 56]>() <= size_of::()); impl PowerConfig25G83 { - /// Pointer and random words, which [`Self::hash`] leaves out. - const RUNTIME: [(usize, usize); 3] = - [(CONFIG_STATUS_POINTER, 8), (AON_POINTER, 8), (ENTROPY, 4)]; - /// Initializes the power configuration. - pub(crate) fn init(&mut self, config_va: u64, aon_va: u64, entropy: u32) { + pub(crate) fn init( + &mut self, + config_va: u64, + aon_va: u64, + entropy: u32, + max_power_mw: u32, + leakage: &[u32; 56], + ) { + self.0.fill(0); fill_words(&mut self.0, POWER_CONFIG_WORDS); - for (index, word) in LEAKAGE.iter().enumerate() { + for (index, word) in leakage.iter().enumerate() { let at = LEAKAGE_OFFSET + 4 * index; self.0[at..at + 4].copy_from_slice(&word.to_le_bytes()); } + // All copies of the power ceiling must describe the same chip. + for offset in [0xa04, 0xabc, 0x28c8, 0x28cc, 0x28d0, 0x2a08] { + self.0[offset..offset + 4].copy_from_slice(&max_power_mw.to_le_bytes()); + } + let power = crate::float::F32::from(max_power_mw).to_bits(); + for offset in [0x9e0, 0x2a04] { + self.0[offset..offset + 4].copy_from_slice(&power.to_le_bytes()); + } self.0[CONFIG_STATUS_POINTER..CONFIG_STATUS_POINTER + 8] .copy_from_slice(&(config_va + 0x80).to_le_bytes()); self.0[AON_POINTER..AON_POINTER + 8].copy_from_slice(&aon_va.to_le_bytes()); self.0[ENTROPY..ENTROPY + 4].copy_from_slice(&entropy.to_le_bytes()); } - - /// Hash of the contents without the pointers and the random word. - pub(crate) fn hash(&self) -> u64 { - hash_without(&self.0, &Self::RUNTIME) - } } /// The AON temperature-source rows the power configuration names: the GPU cluster 0, GPU @@ -632,50 +620,3 @@ pub(crate) const AON_TABLE: [u32; 9] = [ /// (`+0xc8`, 101.00 °C) and release (`+0xcc`, 96.00 °C) temperatures in 0.01 °C; left at zero /// the limit is 0 °C and the GPU stays at its lowest state whenever the PMP runs. pub(crate) const POWER_STATE_WORDS: Words = &[(0xc8, 10_100, 1), (0xcc, 9_600, 1)]; - -impl HwData25G83 { - /// What [`Self::hash`] leaves out: the runtime words (completion descriptors, QoS pointer) - /// and what comes from the device: the performance tables built from its operating points, - /// which are binned per device, and its enabled core count. - const UNHASHED: [(usize, usize); 10] = [ - (COMPLETION_DESCRIPTORS, 0x80), - (QOS_POINTER, 8), - (core::mem::offset_of!(Self, unk_f88), size_of::()), - (core::mem::offset_of!(Self, unk_fc0), size_of::()), - (core::mem::offset_of!(Self, perf_a), size_of::()), - (core::mem::offset_of!(Self, perf_b_freq_mhz), size_of::<[u32; PERF_STATES]>()), - (core::mem::offset_of!(Self, cs_perf), size_of::()), - (core::mem::offset_of!(Self, afr_perf), size_of::()), - (core::mem::offset_of!(Self, perf_b), size_of::()), - (core::mem::offset_of!(Self, unk_2570), size_of::()), - ]; - - /// Hash of the constant contents (everything but [`Self::UNHASHED`]). - pub(crate) fn hash(&self) -> u64 { - // SAFETY: `HwData25G83` consists of integers only and has no padding. - let bytes = unsafe { - core::slice::from_raw_parts((self as *const Self).cast::(), size_of::()) - }; - hash_without(bytes, &Self::UNHASHED) - } -} - -/// FNV-1a 64 of `bytes` with the `(offset, length)` ranges in `skip` left out. -pub(crate) fn hash_without(bytes: &[u8], skip: &[(usize, usize)]) -> u64 { - let mut hash = 0xcbf2_9ce4_8422_2325u64; - for (offset, byte) in bytes.iter().enumerate() { - if skip.iter().any(|&(at, len)| (at..at + len).contains(&offset)) { - continue; - } - hash = (hash ^ u64::from(*byte)).wrapping_mul(0x100_0000_01b3); - } - hash -} - -/// Hashes ([`hash_without`]) of the hardware data, power configuration and region C that the -/// Gravity Linux G16 encoders produce for the J616s, which this build's objects must equal. -pub(crate) mod expected { - pub(crate) const HW_DATA: u64 = 0xfd3b_a26f_570f_57cd; - pub(crate) const POWER_CONFIG: u64 = 0xb669_5b84_2ad2_36fe; - pub(crate) const REGION_C: u64 = 0xd2db_fa6b_8976_47b4; -} diff --git a/drivers/gpu/drm/asahi/g17/initdata.rs b/drivers/gpu/drm/asahi/g17/initdata.rs index 10af261aeb3ac..83a7bbc29dd05 100644 --- a/drivers/gpu/drm/asahi/g17/initdata.rs +++ b/drivers/gpu/drm/asahi/g17/initdata.rs @@ -1223,7 +1223,11 @@ impl InitData { /// The GPU's operating points: `count` of them, each with one voltage cell for the whole /// GPU or one per cluster. - fn opps(dev: &AsahiDrmDevice, count: usize, clusters: u32) -> Result> { + pub(super) fn opps( + dev: &AsahiDrmDevice, + count: usize, + clusters: u32, + ) -> Result> { let opps = Self::opp_table(dev, c_str!("operating-points-v2"))?; if opps.len() != count { return Err(EINVAL); diff --git a/drivers/gpu/drm/asahi/g17/initdata/v25g83.rs b/drivers/gpu/drm/asahi/g17/initdata/v25g83.rs index 85aa6468fa311..670f4b96dcb76 100644 --- a/drivers/gpu/drm/asahi/g17/initdata/v25g83.rs +++ b/drivers/gpu/drm/asahi/g17/initdata/v25g83.rs @@ -8,7 +8,7 @@ //! ([`abi::v25g83`]); free lists, pools, descriptor tables, rings and regions are the A18 Pro's. use super::{abi, low, BuildEnv, BundleObjects, CpuMap, DescriptorTables, InitData, KernelObject}; -use crate::g17::{Config, Role}; +use crate::g17::{calibration::Calibration, Config, Role}; use crate::mmu; use abi::v25g83 as v; use core::sync::atomic::{fence, Ordering}; @@ -74,7 +74,7 @@ impl InitData { }) } - /// Writes the 25G83 firmware objects, checks them against the expected contents, then + /// Writes the 25G83 firmware objects using the chip calibration, then /// stages the opening control-ring state. fn initialize_25g83( &mut self, @@ -101,12 +101,14 @@ impl InitData { let opps = Self::opps(env.dev, cfg.opps, env.clusters)?; let cs_opps = Self::opp_table(env.dev, c_str!("apple,cs-opp"))?; let afr_opps = Self::opp_table(env.dev, c_str!("apple,afr-opp"))?; + let calibration = Calibration::read(env.dev)?; + calibration.check_opps(&opps)?; let hw_args = abi::HwDataArgs { chip_id: cfg.chip_id, windows: cfg.windows, reserved_slots: cfg.reserved_slots, opps: &opps, - perf: cfg.perf, + perf: &calibration.perf, completion_rings: bundle.completion_rings, qos_va: self.object(bundle.qos)?.gpu_va(), }; @@ -117,41 +119,28 @@ impl InitData { }; // SAFETY: Plain C entropy source. let entropy = unsafe { kernel::bindings::get_random_u32() }; - let mut hashes = [0; 3]; self.object_mut(self.bundle)? .initialize::(0, |hw| { hw.init(&hw_args, &hw_args_25g83)?; - hashes[0] = hw.hash(); Ok(()) })?; self.object_mut(self.bundle)? .initialize::(v::POWER_CONFIG, |power| { - power.init(config_va, aon_va, entropy); - hashes[1] = power.hash(); + power.init( + config_va, + aon_va, + entropy, + calibration.max_power_mw, + &calibration.leakage, + ); Ok(()) })?; self.object_mut(self.region_c)? .initialize::(0, |region| { region.init(cfg.region_c); + region.max_power_mw = calibration.max_power_mw; Ok(()) })?; - hashes[2] = v::hash_without(self.object_mut(self.region_c)?.bytes_mut(), &[]); - // The constant contents must equal what the Gravity Linux encoders produce on the proven - // setup; the operating points and the core count come from the device and are not hashed. - let expected = [ - v::expected::HW_DATA, - v::expected::POWER_CONFIG, - v::expected::REGION_C, - ]; - if hashes != expected { - dev_err!( - env.dev.as_ref(), - "25G83 initialization data differs: {:#x?}, expected {:#x?}\n", - hashes, - expected - ); - return Err(EINVAL); - } for (index, word) in v::AON_TABLE.iter().enumerate() { self.object_mut(objects.aon)?.write(index * 4, *word)?; } @@ -167,7 +156,9 @@ impl InitData { v::private::STATUS_B + core::mem::offset_of!(abi::PrimaryStatusB, fwctl_state_va); let cluster = self.object(self.cluster)?; cluster.dword(fwctl)?.store(fwctl_va, Ordering::Relaxed); - cluster.dword(fwctl + 8)?.store(fwctl_va + abi::FWCTL_RING, Ordering::Relaxed); + cluster + .dword(fwctl + 8)? + .store(fwctl_va + abi::FWCTL_RING, Ordering::Relaxed); // One main configuration carrying both roles. let main_offset = abi::bundle::MAIN_CONFIG[Role::Primary as usize]; @@ -218,7 +209,8 @@ impl InitData { )?; for &(offset, value, repeat) in v::POWER_STATE_WORDS { for index in 0..repeat { - self.object_mut(objects.power_state)?.write(offset + 4 * index, value)?; + self.object_mut(objects.power_state)? + .write(offset + 4 * index, value)?; } } diff --git a/drivers/gpu/drm/asahi/g17/mod.rs b/drivers/gpu/drm/asahi/g17/mod.rs index 10891ce09babf..eab0ca15f21f8 100644 --- a/drivers/gpu/drm/asahi/g17/mod.rs +++ b/drivers/gpu/drm/asahi/g17/mod.rs @@ -18,6 +18,7 @@ //! -> VM exec lock -> GEM reservation. `RoleState::ack` nests inside nothing. mod buffer; +mod calibration; mod channel; mod command; pub(crate) use command::Attachments; @@ -1002,8 +1003,19 @@ impl Gpu { cfg: &'static Config, regs: regs::G17Resources, ) -> Result> { + let calibration = if cfg.chip_id == crate::hw::t6040::CHIP_ID { + Some(calibration::Calibration::read(dev).inspect_err(|_| { + dev_err!(dev.as_ref(), "Missing or invalid T6040 GPU calibration handoff\n"); + })?) + } else { + None + }; let variant = Self::firmware_variant(dev, &cfg.firmware)?; let id = regs.get_g17_id()?; + if let Some(calibration) = calibration { + calibration.check_cores(&id.core_masks)?; + calibration.check_opps(&initdata::InitData::opps(dev, cfg.opps, id.num_clusters)?)?; + } if id.family != cfg.identity.family || id.variant != cfg.identity.variant || id.num_dies != cfg.identity.num_dies From 415fd2e6ee2d5175dd4bf4148c391f7daf575328 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 11:48:40 +1100 Subject: [PATCH 057/184] drm/asahi: remove single-unit T6040 power defaults Keep the structural hardware description while requiring the loader maps and power ceiling. Remove the last single-unit ceiling reference and its declaration together, keeping this commit buildable independently. Assisted-by: LLM Signed-off-by: Ryan Murray --- .../gpu/drm/asahi/g17/fw/initdata/v25g83.rs | 3 --- drivers/gpu/drm/asahi/hw/t6040.rs | 24 ++++++------------- 2 files changed, 7 insertions(+), 20 deletions(-) diff --git a/drivers/gpu/drm/asahi/g17/fw/initdata/v25g83.rs b/drivers/gpu/drm/asahi/g17/fw/initdata/v25g83.rs index db554b0d9b8fc..f4c3ee5db22e2 100644 --- a/drivers/gpu/drm/asahi/g17/fw/initdata/v25g83.rs +++ b/drivers/gpu/drm/asahi/g17/fw/initdata/v25g83.rs @@ -379,9 +379,6 @@ impl HwData25G83 { } } -/// Transitional hardware default, removed with the inert T6040 defaults. -pub(crate) const MAX_POWER_MW: u32 = 69_116; - /// Power configuration control period, in 24 MHz clocks (8 ms). const PERIOD_CLOCKS: u32 = 200_000; /// Temperature sensors read by the die and MTR temperature limiters. diff --git a/drivers/gpu/drm/asahi/hw/t6040.rs b/drivers/gpu/drm/asahi/hw/t6040.rs index 0007fdf132949..fa3e51efacadc 100644 --- a/drivers/gpu/drm/asahi/hw/t6040.rs +++ b/drivers/gpu/drm/asahi/hw/t6040.rs @@ -7,7 +7,7 @@ use crate::g17::fw::{ self, - initdata::{v25g83, PerfConfig, RegionCConfig, RegisterWindow, PERF_STATES}, + initdata::{PerfConfig, RegionCConfig, RegisterWindow}, }; use crate::g17::{Config, CoreLayout, CrashBuffers, FixedBuffer, Identity}; use crate::hw::{GpuGen, GpuVariant}; @@ -169,33 +169,23 @@ pub(crate) const REGISTER_WINDOWS: [RegisterWindow; 25] = [ window(43, 0x2_88e6_0000, 0x47_4000, 0x58, 0xe6_0000, 2), ]; -/// Performance-table configuration. The device tree lists the sixteen operating points in -/// performance-state order, with one voltage per cluster. +/// Inert defaults. The 25G83 builder requires the loader's per-chip performance maps. pub(crate) const PERF: PerfConfig = PerfConfig { - states_a: [0, 1, 2, 3, 4, 6, 8, 10, 12, 14, 15], - states_b: [0, 1, 2, 3, 4, 6, 7, 9, 11, 13, 15], - relative_a: [0, 9, 16, 23, 29, 38, 49, 60, 69, 79, 100], - relative_b: [0, 0, 22, 36, 47, 57, 68, 78, 84, 91, 100], + states_a: [0; 11], + states_b: [0; 11], + relative_a: [0; 11], + relative_b: [0; 11], sram_min_mv: 810, }; /// Operating points the performance tables reference. pub(crate) const OPPS: usize = 16; -const _: () = { - let mut index = 0; - while index < PERF_STATES { - assert!((PERF.states_a[index] as usize) < OPPS); - assert!((PERF.states_b[index] as usize) < OPPS); - index += 1; - } -}; - /// Region C. const REGION_C: RegionCConfig = RegionCConfig { unk_54: 0x0fa0, unk_78: [1, 0, 0], - max_power_mw: v25g83::MAX_POWER_MW, + max_power_mw: 0, unk_9c: 0, unk_a0: 0, unk_7c4: 1500, From cf9ac97063729e81ea4fa82a64f63206cd2de804 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 11:48:41 +1100 Subject: [PATCH 058/184] arm64: dts: apple: enable the calibrated GPU on J616s Describe the calibration binding and use loader-filled regions and OPPs. Enable the mailbox and keep the firmware domains powered while the image lives. The GPU consumer owns the PMP report vote. Booting requires the matching T6040 m1n1 handoff. Assisted-by: LLM Signed-off-by: Ryan Murray --- .../devicetree/bindings/gpu/apple,agx.yaml | 107 +++++++++- arch/arm64/boot/dts/apple/t6040-j616s.dts | 185 ++++++++++-------- arch/arm64/boot/dts/apple/t6040.dtsi | 6 +- 3 files changed, 208 insertions(+), 90 deletions(-) diff --git a/Documentation/devicetree/bindings/gpu/apple,agx.yaml b/Documentation/devicetree/bindings/gpu/apple,agx.yaml index c0a32c3badd07..0cd4dc1d6fd78 100644 --- a/Documentation/devicetree/bindings/gpu/apple,agx.yaml +++ b/Documentation/devicetree/bindings/gpu/apple,agx.yaml @@ -83,9 +83,9 @@ properties: minItems: 3 maxItems: 3 description: - macOS version (major, minor, patch) whose boot firmware loaded the GPU - firmware, which identifies the firmware build. Filled in by the boot - loader. + Reported macOS version (major, minor, patch) whose boot firmware loaded + the GPU firmware. Filled in by the boot loader. This is a selection + hint; the T6040 driver requires the exact loaded image identity. apple,cs-opp: $ref: /schemas/types.yaml#/definitions/phandle @@ -99,6 +99,75 @@ properties: Operating-point table of the GPU's AFR clock domain, one voltage cell per state. + apple,t6040-handoff-version: + $ref: /schemas/types.yaml#/definitions/uint32 + const: 2 + description: + Written last by the boot loader after it has filled the T6040 operating + points, memory regions and per-chip calibration. Absent in the source DT. + + apple,core-mask: + $ref: /schemas/types.yaml#/definitions/uint32 + description: Enabled cores in the two ten-core T6040 clusters, low cluster first + + apple,max-power-mw: + $ref: /schemas/types.yaml#/definitions/uint32 + description: Per-chip maximum GPU power in milliwatts, calculated by the loader + + apple,perf-base-pstate: + $ref: /schemas/types.yaml#/definitions/uint32 + description: + Logical base state of the frequency-relative performance table. T6040 + supports the first active state, also used when its ADT omits the property. + + apple,perf-map-a: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: + All 16 A selector register fields, indexing raw ADT operating points. + Eleven populated entries, including off, followed by five zeros. + + apple,perf-map-b: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: + All 16 B selector register fields, indexing raw ADT operating points. + Eleven populated entries, including off, followed by five zeros. + + apple,perf-relative-a: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: + Per-chip relative power of each populated A selector, in percent, + followed by zeros for the five unused selectors. + + apple,perf-relative-b: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: + Relative frequency of the populated A ladder above the base state, + in percent, followed by zeros for the five unused selectors. + + apple,core-leak-coef: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: + GPU core leakage, one coefficient per cluster. Per-chip efuse values decoded by the boot loader, + stored as IEEE-754 binary32 bit patterns in big-endian cells. + + apple,sram-leak-coef: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: + GPU SRAM leakage, one coefficient per cluster. Per-chip efuse values decoded by the boot loader, + stored as IEEE-754 binary32 bit patterns in big-endian cells. + + apple,cs-leak-coef: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: + CS leakage. Per-chip efuse values decoded by the boot loader, + stored as IEEE-754 binary32 bit patterns in big-endian cells. + + apple,afr-leak-coef: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: + AFR leakage. Per-chip efuse values decoded by the boot loader, + stored as IEEE-754 binary32 bit patterns in big-endian cells. + apple,firmware-abi: $ref: /schemas/types.yaml#/definitions/uint32-array minItems: 3 @@ -190,6 +259,38 @@ allOf: const: apple,agx-t6040 then: properties: + apple,core-mask: + minimum: 1 + maximum: 1048575 + apple,max-power-mw: + minimum: 1 + maximum: 200000 + apple,perf-base-pstate: + const: 1 + apple,perf-map-a: + minItems: 16 + maxItems: 16 + apple,perf-map-b: + minItems: 16 + maxItems: 16 + apple,perf-relative-a: + minItems: 16 + maxItems: 16 + apple,perf-relative-b: + minItems: 16 + maxItems: 16 + apple,core-leak-coef: + minItems: 2 + maxItems: 2 + apple,sram-leak-coef: + minItems: 2 + maxItems: 2 + apple,cs-leak-coef: + minItems: 1 + maxItems: 1 + apple,afr-leak-coef: + minItems: 1 + maxItems: 1 reg: items: - description: GFX coprocessor control registers diff --git a/arch/arm64/boot/dts/apple/t6040-j616s.dts b/arch/arm64/boot/dts/apple/t6040-j616s.dts index 65ee34b8b362f..5707ec4a632d2 100644 --- a/arch/arm64/boot/dts/apple/t6040-j616s.dts +++ b/arch/arm64/boot/dts/apple/t6040-j616s.dts @@ -79,8 +79,9 @@ * Text segment of the loaded GPU firmware (ADT gfx-asc * segment-ranges[0]); the driver identifies the build from it. */ - gpu_fw_text: gpu-fw-text@10000be4000 { - reg = <0x100 0x00be4000 0x0 0x60000>; + gpu_fw_text: gpu-fw-text { + reg = <0 0 0 0>; + status = "disabled"; no-map; }; @@ -88,8 +89,9 @@ * Data segment of the loaded GPU firmware (ADT sgx gfx-data-*); it * places its crash log here. */ - gpu_fw_data: gpu-fw-data@10001d44000 { - reg = <0x100 0x01d44000 0x0 0x138000>; + gpu_fw_data: gpu-fw-data { + reg = <0 0 0 0>; + status = "disabled"; no-map; }; @@ -98,23 +100,27 @@ * gfx-shared-l2-region-*, gfx-handoff-*). They stay no-map until * the boot loader adds them to /memory. */ - uat_ttbs: uat-ttbs@10bfffb8000 { - reg = <0x10b 0xfffb8000 0x0 0x4000>; + uat_ttbs: uat-ttbs { + reg = <0 0 0 0>; + status = "disabled"; no-map; }; - uat_pagetables: uat-pagetables@10bfff78000 { - reg = <0x10b 0xfff78000 0x0 0x40000>; + uat_pagetables: uat-pagetables { + reg = <0 0 0 0>; + status = "disabled"; no-map; }; - uat_l2_pagetables: uat-l2-pagetables@10bfff74000 { - reg = <0x10b 0xfff74000 0x0 0x4000>; + uat_l2_pagetables: uat-l2-pagetables { + reg = <0 0 0 0>; + status = "disabled"; no-map; }; - uat_handoff: uat-handoff@10bfff70000 { - reg = <0x10b 0xfff70000 0x0 0x4000>; + uat_handoff: uat-handoff { + reg = <0 0 0 0>; + status = "disabled"; no-map; }; }; @@ -139,76 +145,76 @@ /* * GPU performance states, one opp-microvolt cell per cluster (ADT * /arm-io/sgx perf-states and perf-states1). The operating points are - * binned per device: these are this unit's values until the boot - * loader fills them in. Entries keep the order of the GPU performance - * states, which is not sorted by frequency. + * binned per device: the loader fills these placeholders. + * The driver requires a completed calibration handoff. Entries keep the + * order of the GPU performance states, which is not sorted by frequency. */ gpu_opp: opp-table-gpu { compatible = "operating-points-v2"; opp00 { opp-hz = /bits/ 64 <0>; - opp-microvolt = <125000 125000>; + opp-microvolt = <0 0>; }; opp01 { - opp-hz = /bits/ 64 <338000000>; - opp-microvolt = <605000 585000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; }; opp02 { - opp-hz = /bits/ 64 <618000000>; - opp-microvolt = <655000 640000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; }; opp03 { - opp-hz = /bits/ 64 <796000000>; - opp-microvolt = <690000 690000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; }; opp04 { - opp-hz = /bits/ 64 <924000000>; - opp-microvolt = <715000 725000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; }; opp05 { - opp-hz = /bits/ 64 <952000000>; - opp-microvolt = <755000 770000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; }; opp06 { - opp-hz = /bits/ 64 <1056000000>; - opp-microvolt = <755000 770000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; }; opp07 { - opp-hz = /bits/ 64 <1062000000>; - opp-microvolt = <795000 810000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; }; opp08 { - opp-hz = /bits/ 64 <1182000000>; - opp-microvolt = <795000 810000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; }; opp09 { - opp-hz = /bits/ 64 <1182000000>; - opp-microvolt = <845000 860000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; }; opp10 { - opp-hz = /bits/ 64 <1312000000>; - opp-microvolt = <845000 860000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; }; opp11 { - opp-hz = /bits/ 64 <1242000000>; - opp-microvolt = <900000 915000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; }; opp12 { - opp-hz = /bits/ 64 <1380000000>; - opp-microvolt = <900000 915000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; }; opp13 { - opp-hz = /bits/ 64 <1326000000>; - opp-microvolt = <945000 960000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; }; opp14 { - opp-hz = /bits/ 64 <1470000000>; - opp-microvolt = <945000 960000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; }; opp15 { - opp-hz = /bits/ 64 <1578000000>; - opp-microvolt = <1030000 1045000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; }; }; @@ -217,36 +223,36 @@ compatible = "operating-points-v2"; opp00 { - opp-hz = /bits/ 64 <24000000>; - opp-microvolt = <555000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; }; opp01 { - opp-hz = /bits/ 64 <338000000>; - opp-microvolt = <555000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; }; opp02 { - opp-hz = /bits/ 64 <618000000>; - opp-microvolt = <620000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; }; opp03 { - opp-hz = /bits/ 64 <796000000>; - opp-microvolt = <660000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; }; opp04 { - opp-hz = /bits/ 64 <992000000>; - opp-microvolt = <715000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; }; opp05 { - opp-hz = /bits/ 64 <1188000000>; - opp-microvolt = <780000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; }; opp06 { - opp-hz = /bits/ 64 <1380000000>; - opp-microvolt = <870000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; }; opp07 { - opp-hz = /bits/ 64 <1470000000>; - opp-microvolt = <910000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; }; }; @@ -255,36 +261,36 @@ compatible = "operating-points-v2"; opp00 { - opp-hz = /bits/ 64 <24000000>; - opp-microvolt = <555000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; }; opp01 { - opp-hz = /bits/ 64 <338000000>; - opp-microvolt = <555000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; }; opp02 { - opp-hz = /bits/ 64 <618000000>; - opp-microvolt = <620000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; }; opp03 { - opp-hz = /bits/ 64 <796000000>; - opp-microvolt = <660000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; }; opp04 { - opp-hz = /bits/ 64 <992000000>; - opp-microvolt = <715000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; }; opp05 { - opp-hz = /bits/ 64 <1188000000>; - opp-microvolt = <780000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; }; opp06 { - opp-hz = /bits/ 64 <1380000000>; - opp-microvolt = <870000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; }; opp07 { - opp-hz = /bits/ 64 <1470000000>; - opp-microvolt = <910000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; }; }; @@ -821,12 +827,23 @@ }; /* - * The GPU stays disabled until a boot loader fills in this unit's operating - * points, leakage and regions from the ADT; the values here are one unit's. - * Enabling it also takes gfx_mbox, keeps ps_afr and ps_gfx always on and - * claims pmp_gfx always-on, so that the PMP sees the GPU on exactly while it - * is powered. + * The loaded GPU firmware keeps running across system sleep. Like the Neo's + * GFX domain, these domains must not be switched off while that image lives. + * The PMP report vote is still owned by the GPU consumer. */ +&ps_afr { + apple,always-on; +}; + +&ps_gfx { + apple,always-on; +}; + +/* The loader fills the operating points, calibration and all six memory regions. */ +&gfx_mbox { + status = "okay"; +}; + &gpu { memory-region = <&uat_ttbs>, <&uat_pagetables>, <&uat_handoff>, <&gpu_fw_data>, <&uat_l2_pagetables>, <&gpu_fw_text>; @@ -835,7 +852,7 @@ operating-points-v2 = <&gpu_opp>; apple,cs-opp = <&gpu_cs_opp>; apple,afr-opp = <&gpu_afr_opp>; - status = "disabled"; + status = "okay"; }; /* diff --git a/arch/arm64/boot/dts/apple/t6040.dtsi b/arch/arm64/boot/dts/apple/t6040.dtsi index 213723f3c34b2..ad87eaf38ac31 100644 --- a/arch/arm64/boot/dts/apple/t6040.dtsi +++ b/arch/arm64/boot/dts/apple/t6040.dtsi @@ -616,7 +616,7 @@ <0x2 0x88000000 0x0 0x3758000>; reg-names = "asc", "sgx"; mboxes = <&gfx_mbox>; - power-domains = <&ps_gfx>; + power-domains = <&pmp_gfx>; status = "disabled"; }; @@ -909,8 +909,8 @@ * the GPU as off and holds it at its lowest state, but claiming * it while nothing powers the GPU crashes the PMP firmware * (SError) right after it starts. It sits on the GPU's own - * power state, and a board that powers the GPU (ps_gfx stays on - * while the GPU firmware runs) claims it always-on. + * power state. The GPU consumer takes this domain, so the request + * follows the GFX domain's power lifetime. */ pmp_gfx: report@d { compatible = "apple,t6041-pmp-v2-report-entry", From b8f8724bff9c7fd29e2d1162e5fae471f111e1f9 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 11:48:44 +1100 Subject: [PATCH 059/184] selftests: asahi-m4: follow shared firmware and MMU interfaces Use the current shared firmware type and extracted MMU helpers. Compare the immutable ABI prefix while independently checking the additive tail size and offsets. Keep the golden fixture bytes unchanged. Assisted-by: LLM Signed-off-by: Ryan Murray --- tools/testing/selftests/drivers/gpu/asahi-m4/README | 5 +++++ tools/testing/selftests/drivers/gpu/asahi-m4/abi.py | 12 ++++++++++++ .../selftests/drivers/gpu/asahi-m4/check-channels.rs | 3 +++ .../drivers/gpu/asahi-m4/run-golden-tests.py | 4 ++++ .../drivers/gpu/asahi-m4/test-mmu-retention.py | 11 +++++++---- .../selftests/drivers/gpu/asahi-m4/test-uapi.py | 2 +- .../drivers/gpu/asahi-m4/test-vm-registry.py | 2 +- 7 files changed, 33 insertions(+), 6 deletions(-) diff --git a/tools/testing/selftests/drivers/gpu/asahi-m4/README b/tools/testing/selftests/drivers/gpu/asahi-m4/README index 9f7859015a19e..c88abd122428e 100644 --- a/tools/testing/selftests/drivers/gpu/asahi-m4/README +++ b/tools/testing/selftests/drivers/gpu/asahi-m4/README @@ -39,6 +39,11 @@ queue/context/notifier records and TTBAT generation words. Comparisons include are compared against the actual kernel header by compiling complete C and Rust definitions and measuring sizes, alignments, member offsets and constants. Comments and documentation can change without invalidating that ABI check. +The current global-parameters reply has a documented additive capacity tail. +The frozen 600-byte prefix is compared unchanged; independent C assertions +require the full reply to be exactly 616 bytes, with two eight-byte fields at +600 and 608. Older Mesa headers can still match the shared prefix. Firmware +encoder golden data and its fixture manifest are never regenerated. No Mesa headers are vendored. Set ASAHI_M4_MESA=/path/to/mesa, or run `test-uapi.py --mesa /path/to/mesa`, for a compatibility check against the real userspace source; a default host run reports that Mesa comparison was omitted. diff --git a/tools/testing/selftests/drivers/gpu/asahi-m4/abi.py b/tools/testing/selftests/drivers/gpu/asahi-m4/abi.py index dd4391a686a14..38c6d7806eabd 100644 --- a/tools/testing/selftests/drivers/gpu/asahi-m4/abi.py +++ b/tools/testing/selftests/drivers/gpu/asahi-m4/abi.py @@ -61,12 +61,16 @@ def run_measurements(command, source, binary): def c_layout(header, bindings, output, tag): keys, _ = expressions(bindings) + capacity_tail = 'client_memory_limit;' in header.read_text() # Linux 7.2 folds the historical extension structs into the base payloads. # Compare the same wire prefixes and offsets while keeping the C oracle frozen. if 'struct drm_asahi_cmd_render_with_prior {' not in header.read_text(): translated = [] for key, expr in keys: kind, _, field = key.partition(':') + if capacity_tail and key == 'drm_asahi_params_global:size': + # Compare the frozen reply prefix; validate the entire known additive tail below. + expr = 'offsetof(struct drm_asahi_params_global, memory_limit)' if key == "drm_asahi_vm_create:pad": expr = "offsetof(struct drm_asahi_vm_create, flags)" base = None @@ -89,6 +93,14 @@ def c_layout(header, bindings, output, tag): source = output / (tag + '.c') lines = ['#include ', '#include ', '#include ' + json.dumps(str(header)), 'int main(void) {'] + if capacity_tail: + lines[3:3] = [ + '_Static_assert(sizeof(struct drm_asahi_params_global) == 616, "capacity reply size");', + '_Static_assert(offsetof(struct drm_asahi_params_global, memory_limit) == 600, "capacity prefix");', + '_Static_assert(offsetof(struct drm_asahi_params_global, client_memory_limit) == 608, "client capacity offset");', + '_Static_assert(sizeof(((struct drm_asahi_params_global *)0)->memory_limit) == 8, "capacity width");', + '_Static_assert(sizeof(((struct drm_asahi_params_global *)0)->client_memory_limit) == 8, "client capacity width");', + ] lines += [f'printf("{key}=%llu\\n", (unsigned long long)({expr}));' for key, expr in keys] source.write_text('\n'.join(lines + ['return 0;', '}']) + '\n') return run_measurements(shlex.split(os.environ.get('CC', 'cc')) + diff --git a/tools/testing/selftests/drivers/gpu/asahi-m4/check-channels.rs b/tools/testing/selftests/drivers/gpu/asahi-m4/check-channels.rs index 52461a122b89d..925fb72a49931 100644 --- a/tools/testing/selftests/drivers/gpu/asahi-m4/check-channels.rs +++ b/tools/testing/selftests/drivers/gpu/asahi-m4/check-channels.rs @@ -4,6 +4,9 @@ mod channels; #[path = "@KERNEL@/drivers/gpu/drm/asahi/g16/fw/graph.rs"] mod graph; +mod fwid { +@FWID_BUILD@ +} mod hw {pub enum GpuVariant {G}} struct Config { chip_id:u32,gpu_variant:hw::GpuVariant,gpu_revision:u32,uat_ias:u32,uat_oas:u32, diff --git a/tools/testing/selftests/drivers/gpu/asahi-m4/run-golden-tests.py b/tools/testing/selftests/drivers/gpu/asahi-m4/run-golden-tests.py index 7d3a06dac82c0..962713941338b 100644 --- a/tools/testing/selftests/drivers/gpu/asahi-m4/run-golden-tests.py +++ b/tools/testing/selftests/drivers/gpu/asahi-m4/run-golden-tests.py @@ -15,10 +15,14 @@ def main(): manifest = unpack_fixtures(golden) (out / 'public-uapi.rs').write_bytes(frozen_bindings().read_bytes()) backend = root / 'drivers/gpu/drm/asahi/g16' + identity = (root / 'drivers/gpu/drm/asahi/fwid.rs').read_text() + first = identity.index('pub(crate) struct FirmwareBuild {') + build_type = identity[first:identity.index('\n}', first) + 2] logs = [] for name in ['initdata', 'compute', 'render', 'channels', 'initdata-bounds']: source = (HERE / f'check-{name}.rs').read_text() source = source.replace('@KERNEL@', str(root)).replace('@OUTPUT@', str(out)) + source = source.replace('@FWID_BUILD@', build_type) test = out / f'check-{name}.rs' test.write_text(source) binary = out / f'check-{name}' diff --git a/tools/testing/selftests/drivers/gpu/asahi-m4/test-mmu-retention.py b/tools/testing/selftests/drivers/gpu/asahi-m4/test-mmu-retention.py index 03184a58211cf..787992e9b4c24 100755 --- a/tools/testing/selftests/drivers/gpu/asahi-m4/test-mmu-retention.py +++ b/tools/testing/selftests/drivers/gpu/asahi-m4/test-mmu-retention.py @@ -19,7 +19,7 @@ def body(source,marker): mm=(ROOT/'drivers/gpu/drm/asahi/mmu.rs').read_text();pt=(ROOT/'drivers/gpu/drm/asahi/pgtable.rs').read_text() pre=r''' #![allow(dead_code,unused_variables)] -use std::{cell::RefCell,rc::Rc,mem::ManuallyDrop,ops::{Deref,DerefMut,Range},num::NonZeroUsize,sync::{Mutex as StdMutex,atomic::{AtomicBool as StdAtomicBool,AtomicU8,AtomicU32,AtomicU64,AtomicUsize,Ordering,fence}}}; +use std::{cell::RefCell,rc::Rc,mem::{ManuallyDrop,size_of},ops::{Deref,DerefMut,Range},num::NonZeroUsize,sync::{Mutex as StdMutex,atomic::{AtomicBool as StdAtomicBool,AtomicU8,AtomicU32,AtomicU64,AtomicUsize,Ordering,fence}}}; type Result = std::result::Result;const EIO:i32=5;const ETIMEDOUT:i32=110;const EOVERFLOW:i32=75; static TRIP_LOAD:AtomicUsize=AtomicUsize::new(0); struct AtomicBool(StdAtomicBool);impl AtomicBool{fn new(v:bool)->Self{Self(StdAtomicBool::new(v))}fn store(&self,v:bool,o:Ordering){self.0.store(v,o);}fn load(&self,o:Ordering)->bool{let n=TRIP_LOAD.load(Ordering::Relaxed);if n!=0&&TRIP_LOAD.fetch_sub(1,Ordering::Relaxed)==1{self.0.store(true,Ordering::Release);}self.0.load(o)}} @@ -29,6 +29,7 @@ def body(source,marker): struct MutexBackend;struct Mutex(StdMutex);implMutex{fn new(v:T)->Self{Self(StdMutex::new(v))}fn lock(&self)->Guard<'_,T,MutexBackend>{Guard(self.0.lock().unwrap(),std::marker::PhantomData)}} struct Guard<'a,T,M>(std::sync::MutexGuard<'a,T>,std::marker::PhantomData);implDeref for Guard<'_,T,M>{type Target=T;fn deref(&self)->&T{&self.0}}implDerefMut for Guard<'_,T,M>{fn deref_mut(&mut self)->&mut T{&mut self.0}} struct Monotonic;struct Delta;impl Delta{fn from_millis(n:u64)->u64{n}}struct Instant{n:std::cell::Cell,p:std::marker::PhantomData}implInstant{fn now()->Self{Self{n:std::cell::Cell::new(0),p:std::marker::PhantomData}}fn elapsed(&self)->u64{let n=self.n.get();self.n.set(n+1);n}} +mod pgtable{pub fn clean_poc(_p:*const u8,_n:usize){super::BARRIERS.fetch_add(1,super::Ordering::Relaxed);}pub fn poc_barrier(){super::BARRIERS.fetch_add(1,super::Ordering::Relaxed);}} mod mem{pub fn sync(){super::BARRIERS.fetch_add(1,super::Ordering::Relaxed);}pub fn tlbi_asid(_:u8){}} #[derive(Clone,Copy,PartialEq,Eq)]enum UatFirmware{Handoff,PublishedRoots} struct Handoff{lock_ap:AtomicU8,lock_fw:AtomicU8,turn:AtomicU32,cur_slot:AtomicU32}impl Handoff{fn new()->Self{Self{lock_ap:AtomicU8::new(0),lock_fw:AtomicU8::new(0),turn:AtomicU32::new(0),cur_slot:AtomicU32::new(0xffff)}} @@ -36,17 +37,19 @@ def body(source,marker): handoff='\n'.join(body(mm,m) for m in ['fn lock(&self) {\n self.lock_ap', 'fn unlock(&self) {\n self.turn', 'fn current_slot(']) mid=r''' } -struct SlotTTBS{ttb0:AtomicU64,ttb1:AtomicU64}struct UatShared{handoff:Handoff,ttbs:[SlotTTBS;64],map_kernel_to_user:bool,kernel_ttb1:u64,binding_generations:[u64;64],binding_published:[bool;64],owned_user_roots:[u64;64]} +struct Quirks{handoff_lock:bool,clean_tables:bool} +struct SlotTTBS{ttb0:AtomicU64,ttb1:AtomicU64}struct UatShared{handoff:Handoff,ttbs:[SlotTTBS;64],map_kernel_to_user:bool,kernel_ttb1:u64,binding_generations:[u64;64],binding_published:[bool;64],owned_user_roots:[u64;64],quirks:Quirks} impl UatShared{fn handoff(&self)->&Handoff{&self.handoff}fn ttbs(&self)->&[SlotTTBS;64]{&self.ttbs}} struct UatInner{firmware:UatFirmware,m4_adopted:bool,failed:AtomicBool,shared:Mutex} ''' +mid=mid.replace('impl UatShared{','impl UatShared{'+''.join(body(mm,m) for m in ['fn handoff_lock(', 'fn handoff_unlock(', 'fn publish_slot('])) raii='#[path="'+str(ROOT/'drivers/gpu/drm/asahi/mmu/ppl.rs')+'"]mod ppl;\n'+'\n'.join(body(mm,m) for m in [ 'struct M4Transport<', 'impl ppl::Memory for M4Transport', 'enum HandoffGuard<', 'impl HandoffGuard<', 'impl Drop for HandoffGuard<' ]) inner='impl UatInner{\n'+'\n'.join(body(mm,m) for m in ['fn lock(&self) -> Guard', 'fn check_m4(', 'fn lock_handoff(', 'fn lock_m4_upper('])+'\n}\n' models=r''' -fn uat(m4:bool,firmware:UatFirmware)->Rc{Rc::new(UatInner{firmware,m4_adopted:m4,failed:AtomicBool::new(false),shared:Mutex::new(UatShared{handoff:Handoff::new(),ttbs:std::array::from_fn(|_|SlotTTBS{ttb0:AtomicU64::new(0),ttb1:AtomicU64::new(0)}),map_kernel_to_user:!m4,kernel_ttb1:0x800000000,binding_generations:[0;64],binding_published:[false;64],owned_user_roots:[0;64]})})} +fn uat(m4:bool,firmware:UatFirmware)->Rc{Rc::new(UatInner{firmware,m4_adopted:m4,failed:AtomicBool::new(false),shared:Mutex::new(UatShared{handoff:Handoff::new(),ttbs:std::array::from_fn(|_|SlotTTBS{ttb0:AtomicU64::new(0),ttb1:AtomicU64::new(0)}),map_kernel_to_user:!m4,kernel_ttb1:0x800000000,binding_generations:[0;64],binding_published:[false;64],owned_user_roots:[0;64],quirks:Quirks{handoff_lock:true,clean_tables:false}})})} #[derive(Clone,Copy)]struct Prot{cached:bool}impl Prot{fn is_cached_noncoherent(&self)->bool{self.cached}} struct Mutation;impl Drop for Mutation{fn drop(&mut self){MUTATIONS.fetch_sub(1,Ordering::Relaxed);}} struct MapTable{fail:bool}impl MapTable{fn unmap_pages(&mut self,_r:Range)->Result{LEAF_CLEARS.fetch_add(1,Ordering::Relaxed);if self.fail{Err(EIO)}else{Ok(())}}} @@ -72,7 +75,7 @@ def body(source,marker): #[derive(Clone)]struct Vm{id:u64,binding:Rc>} struct VmBind(Vm,u32); struct Uat{inner:Rc,slots:Allocator} -mod module_parameters{pub struct Param;impl Param{pub fn value(&self)->&u32{&0}}pub static robust_isolation:Param=Param;} +mod module_parameters{pub struct Param;impl Param{pub fn value(&self)->u32{0}}pub static robust_isolation:Param=Param;} ''' helpers='\n'.join(body(mm,m) for m in ['const fn binding_wire_generation(', 'const fn application_ttb1(']) bind='impl Uat{'+body(mm,'pub(crate) fn bind(&self, vm: &Vm)')+'}\n' diff --git a/tools/testing/selftests/drivers/gpu/asahi-m4/test-uapi.py b/tools/testing/selftests/drivers/gpu/asahi-m4/test-uapi.py index 39d2500f60b19..a6c936f3c0b4a 100755 --- a/tools/testing/selftests/drivers/gpu/asahi-m4/test-uapi.py +++ b/tools/testing/selftests/drivers/gpu/asahi-m4/test-uapi.py @@ -27,7 +27,7 @@ def main(): if args.mesa: mesa = args.mesa.resolve() / 'include/drm-uapi/asahi_drm.h' errors += differences(kernel, c_layout(mesa, bindings, out, 'mesa-layout')) - report = {'passed': not errors, 'scope': 'Compiled public UAPI structures, field offsets and constants; no kernel parser or feature-semantic coverage', + report = {'passed': not errors, 'scope': 'Compiled public UAPI structures, field offsets and constants; frozen params prefix plus exact known capacity-tail assertions; no parser or feature-semantic coverage', 'kernel': str(header), 'bindings': str(bindings), 'comparisons': len(kernel), 'mesa': str(mesa) if mesa else None, 'mesa_comparison': 'performed' if mesa else 'not requested; set ASAHI_M4_MESA or --mesa', diff --git a/tools/testing/selftests/drivers/gpu/asahi-m4/test-vm-registry.py b/tools/testing/selftests/drivers/gpu/asahi-m4/test-vm-registry.py index d7750aab9e147..add96b48bb335 100644 --- a/tools/testing/selftests/drivers/gpu/asahi-m4/test-vm-registry.py +++ b/tools/testing/selftests/drivers/gpu/asahi-m4/test-vm-registry.py @@ -20,7 +20,7 @@ def item(marker): implstd::ops::Deref for KVec{type Target=Vec;fn deref(&self)->&Vec{&self.0}} implstd::ops::DerefMut for KVec{fn deref_mut(&mut self)->&mut Vec{&mut self.0}} mod hw{pub mod sizes{pub const MAX_VMS:u32=63;}} -mod module_parameters{pub struct Param;impl Param{pub fn value(&self)->&u32{&0}}pub static robust_isolation:Param=Param;} +mod module_parameters{pub struct Param;impl Param{pub fn value(&self)->u32{0}}pub static robust_isolation:Param=Param;} mod types{pub struct ScopeGuard(Option);implScopeGuard{pub fn new(f:F)->Self{Self(Some(f))}pub fn dismiss(mut self){self.0.take();}}implDrop for ScopeGuard{fn drop(&mut self){if let Some(f)=self.0.take(){f()}}}} struct Handle;impl Handle{fn is_closed(&self)->bool{false}} struct VmState{id:u64,vm:Handle,drops:Arc}impl Drop for VmState{fn drop(&mut self){self.drops.fetch_add(1,Ordering::AcqRel);}} From a0a9485847f8ba37c4fc8de581c9856118ab7371 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 11:48:45 +1100 Subject: [PATCH 060/184] tools/asahi: test T6040 calibration and loaded-image selection Exercise the production reader with synthetic properties, invalid tails and OPPs. Cover candidate ordering and exact image refusal, and update G17 fixtures for current queue ownership. Assisted-by: LLM Signed-off-by: Ryan Murray --- tools/asahi/g17-admission-deadline-tests.py | 7 +- tools/asahi/g17-firmware-selection-tests.py | 103 ++++++++++ tools/asahi/g17-owner-release-order-tests.py | 4 +- tools/asahi/g17-resource-tests.py | 13 +- tools/asahi/t6040-calibration-tests.py | 191 +++++++++++++++++++ 5 files changed, 309 insertions(+), 9 deletions(-) create mode 100644 tools/asahi/g17-firmware-selection-tests.py create mode 100644 tools/asahi/t6040-calibration-tests.py diff --git a/tools/asahi/g17-admission-deadline-tests.py b/tools/asahi/g17-admission-deadline-tests.py index 5002f70997307..b58715f6fcc58 100644 --- a/tools/asahi/g17-admission-deadline-tests.py +++ b/tools/asahi/g17-admission-deadline-tests.py @@ -40,7 +40,7 @@ def fixture(): const QUARANTINE_WAIT_MS:u32=1000; #[derive(Clone,Copy,PartialEq)] enum Plan {Any,Conflict} #[derive(Clone,Copy)] struct Request<'a>(&'a ()); -struct State {owners:[u64;2],quarantined:u64,next_ticket:u64,waiters:usize,conflict:bool,ready:bool} +struct State {owners:[u64;2],quarantined:u64,spared:u64,next_ticket:u64,waiters:usize,conflict:bool,ready:bool} impl State { fn exhausted(&self)->bool {false} fn refusal(&self,_:u64)->Result {Ok(())} @@ -69,7 +69,7 @@ def fixture(): ''' + item(source, 'fn wait_slice_ms(') + '\nimpl Pool {\n' + item(source, 'pub(crate) fn acquire_render(') + r''' } fn new_pool(conflict:bool,quarantined:u64)->Arc { - Arc::new(Pool{state:Lock(Mutex::new(State{owners:[0,1],quarantined,next_ticket:1,waiters:0,conflict,ready:false})),released:CondVar}) + Arc::new(Pool{state:Lock(Mutex::new(State{owners:[0,1],quarantined,spared:0,next_ticket:1,waiters:0,conflict,ready:false})),released:CondVar}) } fn reset(mode:usize){NOW.store(0,Ordering::SeqCst);WAITS.store(0,Ordering::SeqCst);MODE.store(mode,Ordering::SeqCst);} fn main(){ @@ -89,6 +89,9 @@ def fixture(): reset(mode);let pool=new_pool(false,2); assert!(matches!(pool.acquire_render(1,None,2_000_000_000,||Ok(())),Err(EIO))); assert!(NOW.load(Ordering::SeqCst)<=1_010_000_000,"quarantine budget extended"); + reset(mode);let pool=new_pool(false,2);pool.state.lock().spared=2; + assert!(matches!(pool.acquire_render(1,None,2_000_000_000,||Ok(())),Err(EBUSY))); + assert!(NOW.load(Ordering::SeqCst)<=1_010_000_000,"spared quarantine budget extended"); } reset(2);let pool=new_pool(true,0); assert!(matches!(pool.acquire_render(1,Some(Request(&())),2_000_000_000,||Ok(())),Err(ERESTARTSYS))); diff --git a/tools/asahi/g17-firmware-selection-tests.py b/tools/asahi/g17-firmware-selection-tests.py new file mode 100644 index 0000000000000..771c9c19c6e1e --- /dev/null +++ b/tools/asahi/g17-firmware-selection-tests.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only OR MIT +"""Compile the G17 build selector with a modelled image-identity result. + +The shared fwid KUnit tests check the actual SHA-256 normalization. This host +check ensures OS metadata cannot admit a different image or veto a known one. +""" + +from pathlib import Path +import subprocess +import tempfile + +DRIVER = Path(__file__).resolve().parents[2] / "drivers/gpu/drm/asahi" + + +def method(source, signature): + start = source.index(signature) + end = source.index("{", start) + 1 + depth = 1 + while depth: + depth += (source[end] == "{") - (source[end] == "}") + end += 1 + return source[start:end] + + +HEADER = r''' +#![allow(dead_code)] +type Result = std::result::Result; +const ENODEV: i32 = -19; +macro_rules! dev_info { ($($t:tt)*) => { () }; } +macro_rules! dev_err { ($($t:tt)*) => { () }; } +struct AsahiDrmDevice { + version: Option<[u32; 3]>, + image: u32, + checked: std::cell::RefCell>, +} +mod fw { + pub struct Variant(pub u32); + pub mod variant { + use super::Variant; + pub struct Build { + pub os_version: [u32; 3], + pub name: &'static str, + pub image: u32, + pub variant: &'static Variant, + } + pub enum Firmware { Fixed(&'static Variant), Identified(&'static [Build]) } + } +} +struct Gpu; +impl Gpu { + fn os_firmware_version(dev: &AsahiDrmDevice) -> Option<[u32; 3]> { dev.version } + fn identify_image(dev: &AsahiDrmDevice, build: &'static fw::variant::Build) -> Result { + dev.checked.borrow_mut().push(build.image); + if dev.image == build.image { Ok(()) } else { Err(ENODEV) } + } +''' + +TESTS = r''' +} +static FIRST: fw::Variant = fw::Variant(1); +static SECOND: fw::Variant = fw::Variant(2); +static BUILDS: [fw::variant::Build; 2] = [ + fw::variant::Build { os_version: [26,6,2], name: "first", image: 1, variant: &FIRST }, + fw::variant::Build { os_version: [27,0,0], name: "second", image: 2, variant: &SECOND }, +]; +fn device(version: Option<[u32; 3]>, image: u32) -> AsahiDrmDevice { + AsahiDrmDevice { version, image, checked: std::cell::RefCell::new(Vec::new()) } +} +fn main() { + use fw::variant::Firmware; + for version in [None, Some([26,6,2]), Some([26,7,0]), Some([99,0,0])] { + let dev = device(version, 1); + assert_eq!(Gpu::firmware_variant(&dev, &Firmware::Identified(&BUILDS)).unwrap().0, 1); + } + let dev = device(Some([27,0,0]), 1); + assert_eq!(Gpu::firmware_variant(&dev, &Firmware::Identified(&BUILDS)).unwrap().0, 1); + assert_eq!(*dev.checked.borrow(), [2,1], "the OS version only orders the candidates"); + let dev = device(Some([26,6,2]), 9); + assert!(Gpu::firmware_variant(&dev, &Firmware::Identified(&BUILDS)).is_err()); + assert_eq!(*dev.checked.borrow(), [1,2]); + let dev = device(None, 9); + assert_eq!(Gpu::firmware_variant(&dev, &Firmware::Fixed(&SECOND)).unwrap().0, 2); + assert!(dev.checked.borrow().is_empty(), "fixed Neo admission is unchanged"); + println!("PASS G17 selection: exact image required, OS hint optional, fixed Neo unchanged"); +} +''' + + +def main(): + source = (DRIVER / "g17/mod.rs").read_text() + selector = method(source, " fn firmware_variant(") + with tempfile.TemporaryDirectory(prefix="asahi-firmware-selection-") as tmp: + root = Path(tmp) + (root / "main.rs").write_text(HEADER + selector + TESTS) + binary = root / "test" + subprocess.run(["rustc", "--edition=2021", "-Dwarnings", str(root / "main.rs"), + "-o", str(binary)], check=True) + subprocess.run([str(binary)], check=True) + + +if __name__ == "__main__": + main() diff --git a/tools/asahi/g17-owner-release-order-tests.py b/tools/asahi/g17-owner-release-order-tests.py index 201f525718950..fd0e2c4a80936 100755 --- a/tools/asahi/g17-owner-release-order-tests.py +++ b/tools/asahi/g17-owner-release-order-tests.py @@ -118,7 +118,7 @@ def fixture(): struct Ticket { ordinal: u64 } struct Active { ticket: Ticket } struct Packet { context: Arc } -struct Pair { executed: u64, switch: Option<(u64, u64)>, replay: Option<()>, active: Vec, idle: bool } +struct Pair { executed: u64, switch: Option<(u64, u64)>, replay: Vec<()>, active: Vec, idle: bool } impl Pair { SWITCH /// Host stand-in for a successful publication of `ordinal` (stand-in, not production). @@ -177,7 +177,7 @@ def fixture(): assert!(!table.releasing(&quiet)); // Switch tracking across an owner change of one pair. - let mut pair = Pair { executed: 0, switch: None, replay: None, active: Vec::new(), idle: true }; + let mut pair = Pair { executed: 0, switch: None, replay: Vec::new(), active: Vec::new(), idle: true }; assert_eq!(pair.owner_switch_from(&c), None, "a fresh graph has no previous scheduler"); pair.publish(&Packet { context: d.clone() }, 0); pair.active.clear(); diff --git a/tools/asahi/g17-resource-tests.py b/tools/asahi/g17-resource-tests.py index 35f970799b086..5f87d2ea9e0f5 100644 --- a/tools/asahi/g17-resource-tests.py +++ b/tools/asahi/g17-resource-tests.py @@ -150,14 +150,17 @@ def item(s,start): #[derive(Clone,Copy)] struct Identity {id:Id,published:bool} '''+constants+'\n'+item(s,'pub(crate) struct QueueIds')+'\nimpl QueueIds {\n'+''.join(item(s,'pub(crate) fn '+name+'(')+'\n' for name in ['reserve_initial_render','reserve','publish','can_cancel_unpublished','cancel_unpublished'])+'}\n' test=''' - fn fresh()->QueueIds {QueueIds {entries:[None;QID_COUNT],initial_render:[None;2],compute_publication:0,leased:0}} + fn fresh()->QueueIds {QueueIds {entries:[None;QID_COUNT],initial_render:[None;2],publication:0,leased:0,floor:0}} fn main(){ - for render in [0,2,16,32,112,126] { - let mut q=fresh(); + for floor in [0,32] { + for render in [0,2,16,32,80,96,112,126].into_iter().filter(|render|*render<=QID_COUNT-floor) { + let mut q=fresh();q.floor=floor; for i in 0..render {q.reserve(i as u64+1,if i%2==0 {DataMaster::Tiling}else{DataMaster::Fragment},None).unwrap();} let mut compute=0; while q.reserve(1000+compute,DataMaster::Compute,None).is_ok(){compute+=1;} - assert_eq!(compute as usize,QID_COUNT-render.max(COMPUTE_RENDER_RESERVE)); + assert_eq!(compute as usize,QID_COUNT-floor-render.max(COMPUTE_RENDER_RESERVE-floor)); + assert!(q.entries[..floor].iter().all(Option::is_none)); + } } let mut q=fresh();let first=q.reserve(1,DataMaster::Tiling,None).unwrap(); assert!(q.can_cancel_unpublished(first));q.publish(first).unwrap(); @@ -165,7 +168,7 @@ def item(s,start): let unused=q.reserve(2,DataMaster::Fragment,None).unwrap();q.cancel_unpublished(unused).unwrap();assert_eq!(q.leased,1); let next=q.reserve(3,DataMaster::Fragment,None).unwrap();assert_eq!(next.qid,unused.qid); assert!(!q.can_cancel_unpublished(unused));assert_eq!(q.cancel_unpublished(unused),Err(EINVAL)); - println!("queue IDs: six render/compute capacity boundaries; compute-only render reserve retained; published and stale identities protected"); + println!("queue IDs: render/compute capacity boundaries at zero and reserved floors; compute-only render reserve retained; published and stale identities protected"); // Reproduce the hardware snapshot: 124 installed render identities, two // installed compute identities, and two initial reservations fill128 IDs. let mut q=fresh();q.reserve_initial_render().unwrap(); diff --git a/tools/asahi/t6040-calibration-tests.py b/tools/asahi/t6040-calibration-tests.py new file mode 100644 index 0000000000000..d802013175b97 --- /dev/null +++ b/tools/asahi/t6040-calibration-tests.py @@ -0,0 +1,191 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only +"""Compile the T6040 wire builders and calibration checks without a GPU.""" + +from pathlib import Path +import re +import subprocess +import tempfile + +DRIVER = Path(__file__).resolve().parents[2] / "drivers/gpu/drm/asahi" + + + +HEADER = r''' +#![allow(dead_code)] +extern crate self as kernel; +mod prelude { + pub type KVec = Vec; + pub use std::mem::size_of; + pub type Result = std::result::Result; + #[derive(Debug)] + pub struct Error; + impl From for Error { + fn from(_: std::num::TryFromIntError) -> Self { Self } + } + pub const EINVAL: Error = Error; + pub const EIO: Error = Error; + pub const EOVERFLOW: Error = Error; + pub unsafe trait Zeroable {} +} +macro_rules! static_assert { ($e:expr) => { const _: () = assert!($e); } } +fn clear(value: &mut T) { + unsafe { std::ptr::write_bytes(value, 0, 1); } +} +#[macro_export] +macro_rules! c_str { + ($s:literal) => { std::ffi::CStr::from_bytes_with_nul(concat!($s, "\0").as_bytes()).unwrap() }; +} +pub mod str { pub use std::ffi::CStr; } +mod of { + use crate::prelude::*; + #[derive(Clone)] + pub struct Node(pub std::collections::BTreeMap>); + pub trait Property: Sized { fn decode(value: &[u32]) -> Result; } + impl Property for u32 { + fn decode(value: &[u32]) -> Result { + if value.len() == 1 { Ok(value[0]) } else { Err(EINVAL) } + } + } + impl Property for Vec { fn decode(value: &[u32]) -> Result { Ok(value.to_vec()) } } + impl Node { + pub fn get_property(&self, name: &crate::str::CStr) -> Result { + T::decode(self.0.get(name.to_str().unwrap()).ok_or(EINVAL)?) + } + } +} +mod driver { + pub struct AsahiDrmDevice(pub DrmDevice); + pub struct DrmDevice(pub Device); + pub struct Device(pub crate::of::Node); + impl AsRef for AsahiDrmDevice { fn as_ref(&self) -> &DrmDevice { &self.0 } } + impl AsRef for DrmDevice { fn as_ref(&self) -> &Device { &self.0 } } + impl Device { pub fn of_node(&self) -> Option { Some(self.0.clone()) } } +} +mod float; +mod initdata; +mod g17 { + pub mod fw { pub(crate) use crate::initdata; } + mod calibration; + pub fn check_property_adapter() { + use crate::{driver::*, of::Node}; + let fields: &[(&str, &[u32])] = &[ + ("apple,t6040-handoff-version", &[2]), + ("apple,core-mask", &[0xfffff]), + ("apple,max-power-mw", &[70000]), + ("apple,perf-base-pstate", &[1]), + ("apple,perf-map-a", &[0,1,2,3,4,5,6,7,8,9,15,0,0,0,0,0]), + ("apple,perf-map-b", &[0,1,2,3,4,5,6,7,8,9,15,0,0,0,0,0]), + ("apple,perf-relative-a", &[0,10,20,30,40,50,60,70,80,90,100,0,0,0,0,0]), + ("apple,perf-relative-b", &[0,0,11,22,33,44,55,66,77,88,100,0,0,0,0,0]), + ("apple,core-leak-coef", &[0x44480000, 0x44800000]), + ("apple,sram-leak-coef", &[0x43000000, 0x43000000]), + ("apple,cs-leak-coef", &[0x43800000]), + ("apple,afr-leak-coef", &[0x44000000]), + ]; + let node = Node(fields.iter().map(|(k,v)| (k.to_string(), v.to_vec())).collect()); + let read = |n| calibration::Calibration::read(&AsahiDrmDevice(DrmDevice(Device(n)))); + let chip = read(node.clone()).unwrap(); + assert_eq!(chip.max_power_mw, 70000); + assert_eq!(chip.leakage[0], 0x44480000); + assert_eq!(chip.leakage[1], 0x44800000); + assert_eq!(chip.leakage[32..34], [0x43000000; 2]); + assert_eq!(chip.leakage[52], 0x43800000); + assert_eq!(chip.leakage[54], 0x44000000); + for (key, _) in fields { + let mut missing = node.clone(); missing.0.remove(*key); + assert!(read(missing).is_err(), "accepted missing {key}"); + let mut short = node.clone(); short.0.get_mut(*key).unwrap().pop(); + assert!(read(short).is_err(), "accepted short {key}"); + } + for key in ["apple,perf-map-a", "apple,perf-map-b", "apple,perf-relative-a", "apple,perf-relative-b"] { + for index in 11..16 { + let mut bad = node.clone(); bad.0.get_mut(key).unwrap()[index] = 1; + assert!(read(bad).is_err(), "accepted nonzero tail {key}[{index}]"); + } + } + for (key, value) in [ + ("apple,t6040-handoff-version", 1), + ("apple,core-mask", 0x100000), + ("apple,max-power-mw", 0), + ("apple,perf-base-pstate", 0), + ("apple,perf-base-pstate", 2), + ("apple,cs-leak-coef", 0x7fc00000), + ("apple,afr-leak-coef", 0xbf800000), + ] { + let mut bad = node.clone(); bad.0.insert(key.to_string(), vec![value]); + assert!(read(bad).is_err(), "accepted invalid {key}"); + } + } +} +''' + +TEST = r''' +#[test] +fn property_adapter_requires_complete_chip_data() { g17::check_property_adapter(); } + +#[test] +fn power_configuration_uses_only_its_inputs() { + use initdata::v25g83::PowerConfig25G83; + let mut first = PowerConfig25G83([0xcc; 0x6510]); + let mut leakage = [0; 56]; + leakage[0] = 0x44480000; + leakage[1] = 0x44800000; + leakage[32] = 0x43000000; + leakage[33] = 0x43000000; + leakage[52] = 0x43800000; + leakage[54] = 0x44000000; + first.init(0x12340000, 0x56780000, 0xabcdef, 76543, &leakage); + let word = |bytes: &[u8], off| u32::from_le_bytes(bytes[off..off + 4].try_into().unwrap()); + for off in [0xa04, 0xabc, 0x28c8, 0x28cc, 0x28d0, 0x2a08] { + assert_eq!(word(&first.0, off), 76543); + } + for off in [0x9e0, 0x2a04] { + assert_eq!(word(&first.0, off), (76543f32).to_bits()); + } + for (i, value) in leakage.iter().enumerate() { + assert_eq!(word(&first.0, 0x642c + 4 * i), *value); + } + assert_eq!(u64::from_le_bytes(first.0[0x3b50..0x3b58].try_into().unwrap()), 0x12340080); + assert_eq!(u64::from_le_bytes(first.0[0x6318..0x6320].try_into().unwrap()), 0x56780000); + assert_eq!(word(&first.0, 0x63d4), 0xabcdef); + let mut second = PowerConfig25G83([0; 0x6510]); + second.init(0x12340000, 0x56780000, 0xabcdef, 76543, &leakage); + assert_eq!(first.0, second.0); + second.init(0x12340000, 0x56780000, 0xabcdef, 80000, &[0; 56]); + assert_eq!(word(&second.0, 0xa04), 80000); + assert_eq!(word(&second.0, 0x9e0), (80000f32).to_bits()); + for i in 0..56 { + assert_eq!(word(&second.0, 0x642c + 4 * i), 0); + } +} +''' + + +def main(): + with tempfile.TemporaryDirectory(prefix="asahi-t6040-") as tmp: + root = Path(tmp) + (root / "initdata").mkdir() + (root / "g17").mkdir() + for source, target in [ + ("g17/fw/initdata.rs", "initdata.rs"), + ("g17/fw/initdata/v25g83.rs", "initdata/v25g83.rs"), + ]: + text = (DRIVER / source).read_text() + (root / target).write_text(text.replace("use kernel::prelude::*;", "use crate::prelude::*;")) + text = (DRIVER / "float.rs").read_text().split("impl<'a> TryFrom Date: Mon, 26 Oct 2026 14:03:02 +1100 Subject: [PATCH 061/184] media: apple: isp: remove parameter-controlled format exposure Remove the inherited multiplanar module parameter. T6040/IMX958 supports normal V4L2 negotiation for contiguous NV12 and P010 through either API, with one memory plane containing the two ISP components. Keep NV12 first and initial; other sensors retain their shipped single-planar API. Do not advertise the old non-contiguous NV12M alias. Cache complete sensor configuration replies with the video state and print them on the standard log-status ioctl, so collecting the firmware list needs no driver or boot parameter. Free the cache on probe failure and final video release, and test failed discovery without publishing partial state. No module/kernel parameters or platform Kconfig options are added. Assisted-by: LLM Signed-off-by: Ryan Murray --- .../media/drivers/apple-isp-j616s.rst | 25 ++++--- .../media/platform/apple/isp/isp-cam-test.c | 41 +++++++++++ drivers/media/platform/apple/isp/isp-cam.c | 20 ++++-- drivers/media/platform/apple/isp/isp-drv.c | 1 + drivers/media/platform/apple/isp/isp-drv.h | 5 +- .../media/platform/apple/isp/isp-v4l2-test.c | 14 +++- drivers/media/platform/apple/isp/isp-v4l2.c | 70 ++++++------------- 7 files changed, 111 insertions(+), 65 deletions(-) diff --git a/Documentation/driver-api/media/drivers/apple-isp-j616s.rst b/Documentation/driver-api/media/drivers/apple-isp-j616s.rst index f92cecf30f3e5..d5342c1d796af 100644 --- a/Documentation/driver-api/media/drivers/apple-isp-j616s.rst +++ b/Documentation/driver-api/media/drivers/apple-isp-j616s.rst @@ -46,9 +46,9 @@ Mode and format selection On T6040/IMX958, probe queries every advertised configuration, bounded at 64, using the 0x120-byte H17 ``CH_CAMERA_CONFIG_GET`` exchange. The complete list -is available through dynamic debug at probe and cached for -``VIDIOC_LOG_STATUS`` (``v4l2-ctl --log-status``), without sending commands -during a stream. The log includes +is available through dynamic debug at probe and cached in full for +``VIDIOC_LOG_STATUS`` (``v4l2-ctl --log-status``), without sending firmware +commands. Collect this diagnostic dump before starting a stress run. It includes input/output dimensions and the sensor clock word; that word is not FPS. The board's existing config-5 1920x1080 and 1280x720 presets stay first and @@ -68,9 +68,9 @@ the runtime input geometry matches the source profile. An unspecified P010 size selects 1280x720 first. Larger P010 modes require a consistent firmware output reply. P010 uses two little-endian 16-bit component planes with ten useful MSB-aligned bits, and equal 64-byte-aligned Y/UV strides. UV immediately -follows Y, as required for contiguous P010. The optional multi-planar V4L2 -API also reports P010 as **one memory plane** containing both component -planes. The firmware still receives two component addresses. +follows Y, as required for contiguous P010. The standard multi-planar V4L2 +API on T6040/IMX958 reports both NV12 and P010 as **one memory plane** +containing both component planes. The firmware still receives two component addresses. At 1920x1080, stride is 3840, Y is 4147200 bytes, UV is 2073600 bytes and ``sizeimage`` is 6220800. At 1280x720, stride is 2560 and ``sizeimage`` is @@ -86,8 +86,17 @@ or both zero-extended low-32-bit plane addresses, plus plane count, pool 9 and a fresh 64-bit submission tag; an old tag cannot release a new lease. Buffer completion is published to videobuf2 only after the firmware report ACK and doorbell have been published. -NV12 pool sizes and the T8140/Neo formats, presets and command path retain -their existing behavior. +NV12 pool sizes and the T8140/Neo default formats, presets and command path +retain their existing behavior. + +The inherited ``multiplanar`` module parameter is removed. T6040/IMX958 +supports standard single-planar and multi-planar V4L2 negotiation for these +contiguous formats without a parameter; the non-contiguous NV12M alias is not +advertised. Other sensors retain their shipped single-planar API. No driver +module/kernel parameter, platform flag or per-platform Kconfig option selects +support. NV12 stays first and is the initial format because browsers/WebRTC +and many applications do not support P010 or simply choose the first format. +Applications can select P010 explicitly with ``VIDIOC_S_FMT``. Endpoints --------- diff --git a/drivers/media/platform/apple/isp/isp-cam-test.c b/drivers/media/platform/apple/isp/isp-cam-test.c index aef545b27c051..27c9e23f348aa 100644 --- a/drivers/media/platform/apple/isp/isp-cam-test.c +++ b/drivers/media/platform/apple/isp/isp-cam-test.c @@ -186,9 +186,15 @@ static int isp_test_config_reply(struct apple_isp *isp, void *args, u32 insize, config->out_height2 = config->out_height; if (config->preset == 2) config->out_height2 = 2160; + reply->unknown[sizeof(reply->unknown) - 1] = 0x5a; return 0; } +static void isp_test_free_configs(void *ptr) +{ + kfree(ptr); +} + static void isp_test_sensor_config_list(struct kunit *test) { struct apple_isp *isp = isp_test_capture_alloc(test); @@ -199,6 +205,7 @@ static void isp_test_sensor_config_list(struct kunit *test) .output_dim = {1280, 720}, .crop_size = {1280, 720} }, }; u32 count = 0; + int err; isp->cmd_virt = kunit_kzalloc(test, 0x120, GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, isp->cmd_virt); @@ -207,6 +214,10 @@ static void isp_test_sensor_config_list(struct kunit *test) test->priv = &count; kunit_activate_static_stub(test, isp_cmd_send, isp_test_config_reply); KUNIT_ASSERT_EQ(test, isp_ch_cache_sensor_configs(isp, 0, 6), 0); + err = kunit_add_action_or_reset(test, isp_test_free_configs, isp->sensor_configs); + KUNIT_ASSERT_EQ(test, err, 0); + KUNIT_EXPECT_EQ(test, get_unaligned_le32(isp->sensor_configs[5].reply + 0xc), 5); + KUNIT_EXPECT_EQ(test, isp->sensor_configs[5].reply[ISP_SENSOR_CONFIG_SIZE - 1], 0x5a); KUNIT_EXPECT_EQ(test, count, 6); KUNIT_EXPECT_EQ(test, isp->num_sensor_configs, 6); KUNIT_EXPECT_EQ(test, isp->num_presets, 3); @@ -223,6 +234,35 @@ static void isp_test_sensor_config_list(struct kunit *test) KUNIT_EXPECT_EQ(test, count, 6); } +static int isp_test_bad_config_reply(struct apple_isp *isp, void *args, u32 insize, + u32 outsize, int timeout) +{ + struct cmd_ch_camera_config_h17 *reply = isp->cmd_virt; + + isp_test_config_reply(isp, args, insize, outsize, timeout); + reply->config.preset = 65; + return 0; +} + +static void isp_test_sensor_config_failure(struct kunit *test) +{ + struct apple_isp *isp = isp_test_capture_alloc(test); + struct isp_preset preset = {}; + u32 count = 0; + + isp->cmd_virt = kunit_kzalloc(test, ISP_SENSOR_CONFIG_SIZE, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, isp->cmd_virt); + isp->presets = &preset; + isp->num_presets = 1; + test->priv = &count; + kunit_activate_static_stub(test, isp_cmd_send, isp_test_bad_config_reply); + KUNIT_EXPECT_EQ(test, isp_ch_cache_sensor_configs(isp, 0, 6), -EIO); + KUNIT_EXPECT_EQ(test, count, 1); + KUNIT_EXPECT_EQ(test, isp->num_sensor_configs, 0); + KUNIT_EXPECT_PTR_EQ(test, isp->sensor_configs, NULL); + KUNIT_EXPECT_PTR_EQ(test, isp->presets, &preset); +} + struct isp_test_ack_state { unsigned int fault, calls; }; @@ -282,6 +322,7 @@ static struct kunit_case isp_cam_test_cases[] = { KUNIT_CASE(isp_test_command_ack_failure_latches), KUNIT_CASE(isp_test_sensor_geometry), KUNIT_CASE(isp_test_sensor_config_list), + KUNIT_CASE(isp_test_sensor_config_failure), KUNIT_CASE(isp_test_preview_stream), KUNIT_CASE(isp_test_preview_command_failures), {} diff --git a/drivers/media/platform/apple/isp/isp-cam.c b/drivers/media/platform/apple/isp/isp-cam.c index 59f19e571bb64..a5cbb2b4d8eb0 100644 --- a/drivers/media/platform/apple/isp/isp-cam.c +++ b/drivers/media/platform/apple/isp/isp-cam.c @@ -233,6 +233,7 @@ static bool isp_sensor_config_usable(const struct cmd_ch_camera_config *config) static int isp_ch_cache_sensor_configs(struct apple_isp *isp, u32 ch, u32 count) { struct cmd_ch_camera_config_h17 args; + struct isp_sensor_config *configs; struct isp_preset *presets; unsigned int num = isp->num_presets; int err; @@ -247,19 +248,25 @@ static int isp_ch_cache_sensor_configs(struct apple_isp *isp, u32 ch, u32 count) if (!presets) return -ENOMEM; memcpy(presets, isp->presets, num * sizeof(*presets)); + configs = kcalloc(count, sizeof(*configs), GFP_KERNEL); + if (!configs) + return -ENOMEM; for (u32 i = 0; i < count; i++) { struct cmd_ch_camera_config *config = &args.config; - struct isp_sensor_config *cached = &isp->sensor_configs[i]; + struct isp_sensor_config *cached = &configs[i]; bool duplicate = false; err = isp_cmd_ch_camera_config_get(isp, ch, i, &args); if (err) - return err; + goto free_configs; if (config->opcode != (u64)CISP_CMD_CH_CAMERA_CONFIG_GET << 32 || - config->chan != ch || config->preset != i) - return -EIO; + config->chan != ch || config->preset != i) { + err = -EIO; + goto free_configs; + } + memcpy(cached->reply, &args, sizeof(args)); cached->input_dim = (struct coord){config->in_width, config->in_height}; cached->output_dim = (struct coord){config->out_width, config->out_height}; cached->sensor_clk = config->sensor_clk; @@ -313,10 +320,15 @@ static int isp_ch_cache_sensor_configs(struct apple_isp *isp, u32 ch, u32 count) .p010 = true, }; } + isp->sensor_configs = configs; isp->num_sensor_configs = count; isp->presets = presets; isp->num_presets = num; return 0; + +free_configs: + kfree(configs); + return err; } static int isp_ch_cache_sensor_info(struct apple_isp *isp, u32 ch) diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c index 04c50011abda1..084cca4dc3707 100644 --- a/drivers/media/platform/apple/isp/isp-drv.c +++ b/drivers/media/platform/apple/isp/isp-drv.c @@ -586,6 +586,7 @@ static int apple_isp_probe(struct platform_device *pdev) detach_genpd: apple_isp_detach_genpd(isp); free_isp: + kfree(isp->sensor_configs); /* Nothing else can reach it before the video device is registered. */ kfree(isp); return err; diff --git a/drivers/media/platform/apple/isp/isp-drv.h b/drivers/media/platform/apple/isp/isp-drv.h index db042f94aed47..dbe6b6ed5a2c4 100644 --- a/drivers/media/platform/apple/isp/isp-drv.h +++ b/drivers/media/platform/apple/isp/isp-drv.h @@ -31,6 +31,7 @@ /* used to limit the user space buffers to the buffer_pool_config */ #define ISP_MAX_BUFFERS 16 #define ISP_MAX_SENSOR_CONFIGS 64 +#define ISP_SENSOR_CONFIG_SIZE 0x120 /* capture metadata buffers, for firmware that has that pool */ #define ISP_CAPMETA_BUFFERS 8 @@ -265,6 +266,7 @@ struct isp_sensor_config { struct coord output_dim; u32 sensor_clk; bool usable; + u8 reply[ISP_SENSOR_CONFIG_SIZE]; }; struct isp_format { @@ -289,7 +291,7 @@ struct apple_isp { int num_presets; /* Read-only sensor configuration replies, cached before video registration. */ unsigned int num_sensor_configs; - struct isp_sensor_config sensor_configs[ISP_MAX_SENSOR_CONFIGS]; + struct isp_sensor_config *sensor_configs; int num_channels; struct isp_format fmts[ISP_MAX_CHANNELS]; @@ -303,7 +305,6 @@ struct apple_isp { struct vb2_queue vbq; struct mutex video_lock; unsigned int sequence; - bool multiplanar; /* capture rate in frames per second, changes under video_lock */ unsigned int frame_rate; diff --git a/drivers/media/platform/apple/isp/isp-v4l2-test.c b/drivers/media/platform/apple/isp/isp-v4l2-test.c index 6af295a7da23e..b3c8d26dfd4aa 100644 --- a/drivers/media/platform/apple/isp/isp-v4l2-test.c +++ b/drivers/media/platform/apple/isp/isp-v4l2-test.c @@ -102,6 +102,12 @@ static void isp_test_create_bufs_limit(struct kunit *test) KUNIT_EXPECT_EQ(test, isp_test_setup(test, ISP_MAX_BUFFERS, &nbuffers, &num_planes, sizes), -ENOBUFS); + /* The contiguous API must refuse a second independent memory plane. */ + nbuffers = 1; + num_planes = 2; + KUNIT_EXPECT_EQ(test, isp_test_setup(test, 0, &nbuffers, &num_planes, + sizes), -EINVAL); + /* A plane smaller than a frame is still refused. */ nbuffers = 1; num_planes = 1; @@ -158,6 +164,10 @@ static void isp_test_p010_format(struct kunit *test) KUNIT_EXPECT_EQ(test, fmt->total_size, (size_t)1382400); KUNIT_EXPECT_EQ(test, isp_num_rendered(isp), ISP_MAX_BUFFERS); KUNIT_EXPECT_EQ(test, isp_num_meta(isp), ISP_MAX_BUFFERS); + isp_get_mp_pix_format(isp, &f, fmt); + KUNIT_EXPECT_EQ(test, f.fmt.pix_mp.pixelformat, V4L2_PIX_FMT_NV12); + KUNIT_EXPECT_EQ(test, f.fmt.pix_mp.num_planes, 1); + KUNIT_EXPECT_EQ(test, f.fmt.pix_mp.plane_fmt[0].sizeimage, 1382400); } static void isp_test_p010_admission(struct kunit *test) @@ -179,9 +189,7 @@ static void isp_test_p010_admission(struct kunit *test) KUNIT_EXPECT_EQ(test, desc.pixelformat, V4L2_PIX_FMT_P010); desc.index = 2; KUNIT_EXPECT_EQ(test, isp_enum_pixelformat(isp, &desc), -EINVAL); - isp->multiplanar = true; - KUNIT_ASSERT_EQ(test, isp_enum_pixelformat(isp, &desc), 0); - KUNIT_EXPECT_EQ(test, desc.pixelformat, V4L2_PIX_FMT_P010); + desc.index = 1; isp->hw = &neo; KUNIT_EXPECT_EQ(test, isp_enum_pixelformat(isp, &desc), -EINVAL); KUNIT_ASSERT_EQ(test, isp_set_preset(isp, fmt, &preset, V4L2_PIX_FMT_P010), 0); diff --git a/drivers/media/platform/apple/isp/isp-v4l2.c b/drivers/media/platform/apple/isp/isp-v4l2.c index b5bfea73e2d82..bc270e06be21f 100644 --- a/drivers/media/platform/apple/isp/isp-v4l2.c +++ b/drivers/media/platform/apple/isp/isp-v4l2.c @@ -27,10 +27,6 @@ /* Capture rates offered to userspace, in frames per second */ static const unsigned int isp_frame_rates[] = { 30, 25, 24, 20, 15 }; -static bool multiplanar = false; -module_param(multiplanar, bool, 0644); -MODULE_PARM_DESC(multiplanar, "Enable multiplanar API"); - struct isp_buflist_buffer { u64 iovas[ISP_MAX_PLANES]; u32 flags[ISP_MAX_PLANES]; @@ -362,7 +358,7 @@ static int isp_vb2_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers, isp_num_rendered(isp) - q_num_bufs); if (*num_planes) { - if (sizes[0] < fmt->total_size) + if (*num_planes != 1 || sizes[0] < fmt->total_size) return -EINVAL; return 0; @@ -693,25 +689,13 @@ static int isp_vidioc_querycap(struct file *file, void *priv, static int isp_enum_pixelformat(struct apple_isp *isp, struct v4l2_fmtdesc *f) { - switch (f->index) { - case 0: + /* Browser/WebRTC compatibility: the shipped first format remains NV12. */ + if (!f->index) f->pixelformat = V4L2_PIX_FMT_NV12; - break; - case 1: - if (isp->multiplanar) { - f->pixelformat = V4L2_PIX_FMT_NV12M; - break; - } - fallthrough; - case 2: - if (!isp_has_p010(isp) || f->index != (isp->multiplanar ? 2 : 1)) - return -EINVAL; + else if (f->index == 1 && isp_has_p010(isp)) f->pixelformat = V4L2_PIX_FMT_P010; - break; - default: + else return -EINVAL; - } - return 0; } @@ -729,7 +713,6 @@ static int isp_vidioc_enum_framesizes(struct file *file, void *fh, u32 index = 0; if (f->pixel_format != V4L2_PIX_FMT_NV12 && - f->pixel_format != V4L2_PIX_FMT_NV12M && !(f->pixel_format == V4L2_PIX_FMT_P010 && isp_has_p010(isp))) return -EINVAL; @@ -760,7 +743,6 @@ static int isp_vidioc_enum_frameintervals(struct file *file, void *fh, return -EINVAL; if (interval->pixel_format != V4L2_PIX_FMT_NV12 && - interval->pixel_format != V4L2_PIX_FMT_NV12M && !(interval->pixel_format == V4L2_PIX_FMT_P010 && isp_has_p010(isp))) return -EINVAL; @@ -827,27 +809,17 @@ static inline void isp_get_mp_pix_format(struct apple_isp *isp, { f->fmt.pix_mp.width = fmt->preset->output_dim.x; f->fmt.pix_mp.height = fmt->preset->output_dim.y; - f->fmt.pix_mp.num_planes = fmt->num_planes; - for (int i = 0; i < fmt->num_planes; i++) { - f->fmt.pix_mp.plane_fmt[i].sizeimage = fmt->plane_size[i]; - f->fmt.pix_mp.plane_fmt[i].bytesperline = fmt->strides[i]; - } - + /* Both contiguous formats have one memory plane and two ISP components. */ + f->fmt.pix_mp.num_planes = 1; + memset(f->fmt.pix_mp.plane_fmt, 0, sizeof(f->fmt.pix_mp.plane_fmt)); + f->fmt.pix_mp.plane_fmt[0].sizeimage = fmt->total_size; + f->fmt.pix_mp.plane_fmt[0].bytesperline = fmt->strides[0]; f->fmt.pix_mp.field = V4L2_FIELD_NONE; - f->fmt.pix_mp.pixelformat = V4L2_PIX_FMT_NV12M; - /* P010 is contiguous, including through the multi-planar API. */ - if (fmt->pixelformat == V4L2_PIX_FMT_P010) { - f->fmt.pix_mp.pixelformat = V4L2_PIX_FMT_P010; - f->fmt.pix_mp.num_planes = 1; - f->fmt.pix_mp.plane_fmt[0].sizeimage = fmt->total_size; - memset(&f->fmt.pix_mp.plane_fmt[1], 0, - sizeof(f->fmt.pix_mp.plane_fmt[1])); - } + f->fmt.pix_mp.pixelformat = fmt->pixelformat; f->fmt.pix_mp.colorspace = V4L2_COLORSPACE_REC709; f->fmt.pix_mp.ycbcr_enc = V4L2_YCBCR_ENC_709; f->fmt.pix_mp.xfer_func = V4L2_XFER_FUNC_709; - if (fmt->pixelformat == V4L2_PIX_FMT_P010) - f->fmt.pix_mp.quantization = V4L2_QUANTIZATION_LIM_RANGE; + f->fmt.pix_mp.quantization = V4L2_QUANTIZATION_LIM_RANGE; } static int isp_vidioc_get_format(struct file *file, void *fh, @@ -911,7 +883,7 @@ static int isp_vidioc_get_format_mplane(struct file *file, void *fh, struct apple_isp *isp = video_drvdata(file); struct isp_format *fmt = isp_get_current_format(isp); - if (!isp->multiplanar) + if (!isp_supports_p010(isp)) return -ENOTTY; isp_get_mp_pix_format(isp, f, fmt); @@ -927,7 +899,7 @@ static int isp_vidioc_set_format_mplane(struct file *file, void *fh, struct isp_preset *preset; int err; - if (!isp->multiplanar) + if (!isp_supports_p010(isp)) return -ENOTTY; if (vb2_is_busy(&isp->vbq)) @@ -954,7 +926,7 @@ static int isp_vidioc_try_format_mplane(struct file *file, void *fh, struct isp_preset *preset; int err; - if (!isp->multiplanar) + if (!isp_supports_p010(isp)) return -ENOTTY; preset = isp_select_preset(isp, f->fmt.pix_mp.width, f->fmt.pix_mp.height, @@ -1001,7 +973,7 @@ static int isp_vidioc_get_param(struct file *file, void *fh, struct apple_isp *isp = video_drvdata(file); if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && - (!isp->multiplanar || + (!isp_supports_p010(isp) || a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)) return -EINVAL; @@ -1019,7 +991,7 @@ static int isp_vidioc_set_param(struct file *file, void *fh, struct v4l2_fract *tpf = &a->parm.capture.timeperframe; if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && - (!isp->multiplanar || + (!isp_supports_p010(isp) || a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)) return -EINVAL; @@ -1055,6 +1027,9 @@ static int isp_vidioc_log_status(struct file *file, void *fh) config->output_dim.x, config->output_dim.y, config->sensor_clk, config->usable ? "" : " (unverified geometry)"); + /* This explicit diagnostic ioctl replaces a probe-time parameter. */ + print_hex_dump(KERN_INFO, "apple-isp config: ", DUMP_PREFIX_OFFSET, + 16, 4, config->reply, sizeof(config->reply), false); } return 0; } @@ -1120,6 +1095,7 @@ static void apple_isp_release(struct v4l2_device *v4l2_dev) v4l2_ctrl_handler_free(&isp->ctrl_handler); media_device_cleanup(&isp->mdev); + kfree(isp->sensor_configs); kfree(isp); } @@ -1206,8 +1182,6 @@ int apple_isp_setup_video(struct apple_isp *isp) goto media_cleanup; } - isp->multiplanar = multiplanar; - /* * Auto exposure is always on. By default it holds the frame rate, as * V4L2 specifies; with exposure priority it may slow down in low light. @@ -1239,7 +1213,7 @@ int apple_isp_setup_video(struct apple_isp *isp) vdev->fops = &isp_v4l2_fops; vdev->ioctl_ops = &isp_v4l2_ioctl_ops; vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; - if (isp->multiplanar) + if (isp_supports_p010(isp)) vdev->device_caps |= V4L2_CAP_VIDEO_CAPTURE_MPLANE; vdev->entity.flags = MEDIA_ENT_FL_DEFAULT; vdev->v4l2_dev = &isp->v4l2_dev; From 905dff77456191245d1aff3d093c2f41947983ff Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 14:06:00 +1100 Subject: [PATCH 062/184] drm/apple: inspect desktop scalar and service panel property setters Inspect the remaining public property setter wire shapes at callback ingress, without printing scalar values or changing acknowledgements. Use the existing miscellaneous-property budget and KMS debug category. This also catches identity keys delivered outside the dictionary setters. Receive service-prefixed D413 dictionaries through the same checked panel identity and EDID path as D552/D561. Validate their full envelope and exercise the service offset, scalar types, null values and short records with the production parser and host dispatcher fixtures. Assisted-by: LLM Signed-off-by: Ryan Murray --- Documentation/gpu/apple-dcp-t6041-panel.rst | 7 +- drivers/gpu/drm/apple/iomfb.c | 66 +++++++++++++++++++ drivers/gpu/drm/apple/iomfb_v26_6_desktop.c | 7 +- .../drivers/gpu/apple-dcp/desktop.py | 22 ++++++- .../drivers/gpu/apple-dcp/lifecycle.py | 1 + 5 files changed, 99 insertions(+), 4 deletions(-) diff --git a/Documentation/gpu/apple-dcp-t6041-panel.rst b/Documentation/gpu/apple-dcp-t6041-panel.rst index aa15af0f59913..2566c3fd36a94 100644 --- a/Documentation/gpu/apple-dcp-t6041-panel.rst +++ b/Documentation/gpu/apple-dcp-t6041-panel.rst @@ -85,7 +85,12 @@ are dictionary 1, array 2, integer 4, string 9, blob 10 and boolean 11. Values are never printed. Each property walk stops at depth 4 or 64 objects, and the DCP stops after 128 diagnostic records per callback class (512 total), with bounded inspection-status lines in addition. Atomic reservations include framing records. D552, D561, D114 and chunked delivery have -separate budgets so periodic statistics cannot hide the panel setters. +separate budgets so periodic statistics cannot hide the panel setters. Service +dictionary setter D413 is inspected and can supply identity or a raw EDID too. +Scalar setters D102/D104/D414/D415/D563/D565/D567 report keys and types at +ingress when their public wire lengths match. They share the +fourth budget with chunked delivery. Null scalar values report size zero; no +scalar value or service handle is printed, and acknowledgements are unchanged. When KMS debugging is disabled no inspection budget is consumed. Malformed serialization only stops inspection. diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index fb6d435787cae..67a6ff3dfedb1 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -88,6 +88,70 @@ void dcp_trace_panel_property(struct apple_dcp *dcp, int tag, const char *key, dev_info(dcp->dev, "panel identity inspection stopped: %d\n", ret); } +/* Other property setters use fixed scalar records, not serialization. */ +static void dcp_trace_panel_setter(struct apple_dcp *dcp, int tag, + const void *data, size_t len) +{ + struct dcp_identity_trace trace = { .dcp = dcp }; + const char *in = data; + u32 offset = 0, type, size, expected; + bool is_null = false; + + if (!drm_debug_enabled(DRM_UT_KMS) || + dcp->fw_compat != DCP_FIRMWARE_V_26_6_DESKTOP || !dcp_has_panel(dcp)) + return; + switch (tag) { + case 414: + offset = 4; + fallthrough; + case 563: + type = 4; + size = 8; + expected = offset + 76; + break; + case 415: + offset = 4; + fallthrough; + case 565: + type = 11; + size = 4; + expected = offset + 72; + break; + case 567: + type = 9; + size = 64; + expected = 128; + break; + case 102: + type = 4; + size = 4; + expected = 68; + break; + case 104: + type = 11; + size = 1; + expected = 68; + break; + default: + return; + } + /* Inspect only the public wire shape, without changing its handler. */ + if (len != expected) + return; + if (tag == 414 || tag == 415 || tag == 563 || tag == 565) + is_null = in[offset + 64 + size] & 1; + if (tag == 567) + size = strnlen(in + 64, 64); + /* Scalar/service setters share the bounded miscellaneous-property budget. */ + trace.count = &dcp->desktop->identity_log_count[3]; + if (!atomic_add_unless(trace.count, 1, 128)) + return; + dev_info(dcp->dev, "panel identity callback=D%03d bytes=%zu null=%d\n", + tag, len, is_null); + dcp_identity_key_log(&trace, in + offset, strnlen(in + offset, 64), + type, is_null ? 0 : size, 0); +} + static int dcp_tx_offset(enum dcp_context_id id) { switch (id) { @@ -1379,6 +1443,8 @@ static void dcpep_handle_cb(struct apple_dcp *dcp, enum dcp_context_id context, in = data + sizeof(*hdr); out = in + hdr->in_len; + dcp_trace_panel_setter(dcp, tag, in, hdr->in_len); + if (!handled) { zero_reply = dcp_external_zero_reply(dcp, tag, context, hdr->in_len, hdr->out_len); diff --git a/drivers/gpu/drm/apple/iomfb_v26_6_desktop.c b/drivers/gpu/drm/apple/iomfb_v26_6_desktop.c index b9fba6f5656d1..de8783f3a3694 100644 --- a/drivers/gpu/drm/apple/iomfb_v26_6_desktop.c +++ b/drivers/gpu/drm/apple/iomfb_v26_6_desktop.c @@ -100,7 +100,8 @@ static bool trampoline_passthrough_dict_v26_6(struct apple_dcp *dcp, int tag, static bool trampoline_panel_dictionary_v26_6(struct apple_dcp *dcp, int tag, void *out, void *in) { - const struct dcp_dict_passthrough_req_v26_6 *req = in; + /* Service dictionary setters prefix the same record with a handle. */ + const struct dcp_dict_passthrough_req_v26_6 *req = tag == 413 ? in + 4 : in; const char *key = (const char *)req->key; struct dcp_display_metadata metadata; struct dcp_parse_ctx ctx; @@ -144,6 +145,8 @@ bool iomfb_check_callback_v26_6_desktop(int tag, u32 in_len, u32 out_len) return in_len == 24 && out_len == 20; case 454: return in_len == 4 && out_len == 4; + case 413: + return in_len == 4168 && (out_len == 1 || out_len == 4); case 552: case 561: return in_len == 4164 && (out_len == 1 || out_len == 4); @@ -235,7 +238,7 @@ static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = { [406] = trampoline_nop, /* set_fx_prop: a property notification */ [408] = trampoline_get_frequency, [411] = trampoline_map_reg, - [413] = trampoline_true, /* sr_set_property_dict */ + [413] = trampoline_panel_dictionary_v26_6, /* sr_set_property_dict */ [414] = trampoline_sr_set_property_int, [415] = trampoline_true, /* sr_set_property_bool */ [451] = trampoline_allocate_buffer, diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py index b7125295bcc9d..c5e655ee46da7 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py @@ -391,7 +391,7 @@ def panel_identity(root, output=None): dictionary += block(backend, 'static bool trampoline_panel_dictionary_v26_6(') dictionary += block(backend, 'bool iomfb_check_callback_v26_6_desktop(') bindings = '\n'.join(line for line in block(backend, 'static const iomfb_cb_handler cb_handlers[').splitlines() - if '[552]' in line or '[561]' in line) + if '[413]' in line or '[552]' in line or '[561]' in line) unit = code + r''' #define DCP_FIRMWARE_V_26_6_DESKTOP 5 #define trace_iomfb_callback(...) ((void)0) @@ -453,6 +453,10 @@ def panel_identity(root, output=None): assert(handlers[tag](&d,tag,short_reply,&wire)); assert(short_reply[0]==1 && short_reply[1]==0xa5); assert(handlers[tag](&d,tag,&result,&wire) && result==1); + u8 service[sizeof(wire)+4]={0};memcpy(service+4,&wire,sizeof(wire)); + assert(iomfb_check_callback_v26_6_desktop(413,sizeof(service),4)); + assert(!iomfb_check_callback_v26_6_desktop(413,sizeof(service)-1,4)); + assert(handlers[413](&d,413,&result,service) && result==1); assert(desktop.panel_metadata_valid && notifications==1); assert(desktop.panel_metadata.serial==(form==2?0:metadata.serial)); assert(!strcmp(desktop.panel_metadata.manufacturer,"TST")); @@ -865,6 +869,8 @@ def identity_trace_budget(root): struct apple_dcp {int fw_compat;bool has_panel;void *dev;struct {atomic_t identity_log_count[4];} *desktop;}; static bool kms_debug; #define DRM_UT_KMS 4 +#define fallthrough __attribute__((fallthrough)) +typedef uint8_t u8; static bool drm_debug_enabled(int category) {assert(category==DRM_UT_KMS);return kms_debug;} static unsigned messages; static char output[256]; @@ -884,6 +890,7 @@ def identity_trace_budget(root): code+=block(source,'struct dcp_identity_trace {')+';\n' code+=block(source,'static void dcp_identity_key_log(') code+=block(source,'void dcp_trace_panel_property(') + code+=block(source,'static void dcp_trace_panel_setter(') return code+r''' int main(void) { @@ -891,11 +898,24 @@ def identity_trace_budget(root): __typeof__(*d.desktop) desktop={0};d.desktop=&desktop; const char secret[]="SYNTHETIC_VALUE_NOT_FOR_LOGS"; dcp_trace_panel_property(&d,561,"Property",8,secret,sizeof(secret));assert(!messages); + dcp_trace_panel_setter(&d,563,NULL,76);assert(!messages); kms_debug=true;d.fw_compat=1; dcp_trace_panel_property(&d,561,"Property",8,secret,sizeof(secret));assert(!messages); d.fw_compat=5;d.has_panel=false; dcp_trace_panel_property(&d,561,"Property",8,secret,sizeof(secret));assert(!messages); d.has_panel=true; + u8 setter[4168]={0};strcpy((char *)setter,"ScalarKey"); + memcpy(setter+64,secret,sizeof(secret));setter[72]=0; + dcp_trace_panel_setter(&d,563,setter,75);assert(!messages); + dcp_trace_panel_setter(&d,563,setter,76); + assert(strstr(output,"ScalarKey type=4 size=8") && !strstr(output,secret)); + dcp_trace_panel_setter(&d,565,setter,72);assert(strstr(output,"type=11")); + dcp_trace_panel_setter(&d,567,setter,128);assert(strstr(output,"type=9")); + setter[72]=1;dcp_trace_panel_setter(&d,563,setter,76);assert(strstr(output,"size=0")); + memset(setter,0,sizeof(setter));strcpy((char *)setter+4,"ServiceKey"); + dcp_trace_panel_setter(&d,414,setter,80);assert(strstr(output,"ServiceKey type=4")); + dcp_trace_panel_setter(&d,415,setter,76);assert(strstr(output,"ServiceKey type=11")); + dcp_trace_panel_setter(&d,102,setter,68);dcp_trace_panel_setter(&d,104,setter,68); for(unsigned i=0;i<100;i++)dcp_trace_panel_property(&d,114,"Statistics",10,secret,sizeof(secret)); assert(atomic_read(&desktop.identity_log_count[2])==128 && !atomic_read(&desktop.identity_log_count[0]) && !atomic_read(&desktop.identity_log_count[1])); unsigned before=messages; diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py b/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py index cc82ade333fb3..9569b89844fb6 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py @@ -1850,6 +1850,7 @@ def dispatcher(root): #define IOMFB_MAX_CB 600 #define DCP_PACKET_ALIGNMENT 64 #define ALIGN(n, a) (((n) + (a) - 1) & ~((a) - 1)) +#define dcp_trace_panel_setter(...) ((void)0) #define dev_err(...) ((void)dev) #define dev_info(...) ((void)dev) #define dev_warn(...) ((void)dev) From f57db613d65dd826437a64f190e57135063f2080 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 14:59:51 +1100 Subject: [PATCH 063/184] media: apple: isp: copy complete IPC messages with FORTIFY enabled Acquire the validity word, copy the complete message object after the DMA read barrier and restore that captured word. Copying seven payload words through the arg1 field violates FORTIFY field bounds. Cover all eight copied words and keep the ownership ordering unchanged. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/media/platform/apple/isp/isp-ipc-test.c | 16 ++++++++++++++++ drivers/media/platform/apple/isp/isp-ipc.c | 5 +++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/drivers/media/platform/apple/isp/isp-ipc-test.c b/drivers/media/platform/apple/isp/isp-ipc-test.c index 8b6decd63f274..caa9e92f735f1 100644 --- a/drivers/media/platform/apple/isp/isp-ipc-test.c +++ b/drivers/media/platform/apple/isp/isp-ipc-test.c @@ -188,7 +188,23 @@ static void isp_test_late_ack_keeps_cursor(struct kunit *test) KUNIT_EXPECT_EQ(test, t->chan->cursor, 1); } +static void isp_test_message_payload_copy(struct kunit *test) +{ + struct isp_sm_test *t = isp_sm_test_alloc(test); + struct isp_message response = { + .arg0 = 0x5000001, .arg1 = 1, .arg2 = 2, .arg3 = 3, + .arg4 = 4, .arg5 = 5, .arg6 = 6, .arg7 = 7, + }; + struct isp_message copied = {}; + + isp_get_current_format(t->isp)->pixelformat = V4L2_PIX_FMT_P010; + t->chan->virt = &response; + chan_read_msg_index(t->isp, t->chan, &copied, 0); + KUNIT_EXPECT_MEMEQ(test, &copied, &response, sizeof(response)); +} + static struct kunit_case isp_ipc_test_cases[] = { + KUNIT_CASE(isp_test_message_payload_copy), KUNIT_CASE(isp_test_late_ack_keeps_cursor), KUNIT_CASE(isp_test_command_ring_wrap), KUNIT_CASE(isp_test_sm_free_releases), diff --git a/drivers/media/platform/apple/isp/isp-ipc.c b/drivers/media/platform/apple/isp/isp-ipc.c index 92cfc6ab35f5b..74cc717eadc07 100644 --- a/drivers/media/platform/apple/isp/isp-ipc.c +++ b/drivers/media/platform/apple/isp/isp-ipc.c @@ -72,11 +72,12 @@ static inline void chan_read_msg_index(struct apple_isp *isp, { if (isp_is_p010(isp)) { u64 *slot = chan_msg_virt(chan, index); + u64 arg0 = READ_ONCE(*slot); /* Acquire validity before consuming the firmware-owned payload. */ - msg->arg0 = READ_ONCE(*slot); dma_rmb(); - memcpy(&msg->arg1, slot + 1, sizeof(*msg) - sizeof(msg->arg0)); + memcpy(msg, slot, sizeof(*msg)); + msg->arg0 = arg0; } else { memcpy(msg, chan_msg_virt(chan, index), sizeof(*msg)); } From 1bd911c107446e95e7d4423e0f37a9afe674ebc0 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 15:03:19 +1100 Subject: [PATCH 064/184] media: apple: isp: use the contiguous single-planar capture API Remove the parameter-gated multi-planar hooks, which changed the queue type without reinitialising its cached plane semantics. Both contiguous NV12 and P010 use the existing single-planar queue. Keep format negotiation explicit through S_FMT. Assisted-by: LLM Signed-off-by: Ryan Murray --- .../media/drivers/apple-isp-j616s.rst | 13 ++- .../media/platform/apple/isp/isp-v4l2-test.c | 11 +-- drivers/media/platform/apple/isp/isp-v4l2.c | 95 +------------------ 3 files changed, 11 insertions(+), 108 deletions(-) diff --git a/Documentation/driver-api/media/drivers/apple-isp-j616s.rst b/Documentation/driver-api/media/drivers/apple-isp-j616s.rst index d5342c1d796af..bb0b9433a6119 100644 --- a/Documentation/driver-api/media/drivers/apple-isp-j616s.rst +++ b/Documentation/driver-api/media/drivers/apple-isp-j616s.rst @@ -68,9 +68,9 @@ the runtime input geometry matches the source profile. An unspecified P010 size selects 1280x720 first. Larger P010 modes require a consistent firmware output reply. P010 uses two little-endian 16-bit component planes with ten useful MSB-aligned bits, and equal 64-byte-aligned Y/UV strides. UV immediately -follows Y, as required for contiguous P010. The standard multi-planar V4L2 -API on T6040/IMX958 reports both NV12 and P010 as **one memory plane** -containing both component planes. The firmware still receives two component addresses. +follows Y, as required for contiguous P010. Both NV12 and P010 use the +standard single-planar V4L2 API with one memory plane containing both +component planes. The firmware still receives two component addresses. At 1920x1080, stride is 3840, Y is 4147200 bytes, UV is 2073600 bytes and ``sizeimage`` is 6220800. At 1280x720, stride is 2560 and ``sizeimage`` is @@ -89,10 +89,9 @@ only after the firmware report ACK and doorbell have been published. NV12 pool sizes and the T8140/Neo default formats, presets and command path retain their existing behavior. -The inherited ``multiplanar`` module parameter is removed. T6040/IMX958 -supports standard single-planar and multi-planar V4L2 negotiation for these -contiguous formats without a parameter; the non-contiguous NV12M alias is not -advertised. Other sensors retain their shipped single-planar API. No driver +The inherited ``multiplanar`` module parameter and its inconsistent queue-type +hooks are removed. All sensors use the shipped single-planar V4L2 API; the +non-contiguous NV12M alias is not advertised. No driver module/kernel parameter, platform flag or per-platform Kconfig option selects support. NV12 stays first and is the initial format because browsers/WebRTC and many applications do not support P010 or simply choose the first format. diff --git a/drivers/media/platform/apple/isp/isp-v4l2-test.c b/drivers/media/platform/apple/isp/isp-v4l2-test.c index b3c8d26dfd4aa..398e8e92356ab 100644 --- a/drivers/media/platform/apple/isp/isp-v4l2-test.c +++ b/drivers/media/platform/apple/isp/isp-v4l2-test.c @@ -37,6 +37,8 @@ static void isp_test_queue_init(struct kunit *test) KUNIT_ASSERT_EQ(test, isp_vb2_queue_init(isp), 0); KUNIT_EXPECT_GE(test, isp->vbq.max_num_buffers, VB2_MAX_FRAME); + KUNIT_EXPECT_EQ(test, isp->vbq.type, V4L2_BUF_TYPE_VIDEO_CAPTURE); + KUNIT_EXPECT_FALSE(test, isp->vbq.is_multiplanar); vb2_queue_release(&isp->vbq); } @@ -140,10 +142,6 @@ static void isp_test_p010_format(struct kunit *test) KUNIT_EXPECT_EQ(test, fmt->plane_size[0], (size_t)4147200); KUNIT_EXPECT_EQ(test, fmt->plane_size[1], (size_t)2073600); KUNIT_EXPECT_EQ(test, f.fmt.pix.quantization, V4L2_QUANTIZATION_LIM_RANGE); - isp_get_mp_pix_format(isp, &f, fmt); - KUNIT_EXPECT_EQ(test, f.fmt.pix_mp.pixelformat, V4L2_PIX_FMT_P010); - KUNIT_EXPECT_EQ(test, f.fmt.pix_mp.num_planes, 1); - KUNIT_EXPECT_EQ(test, f.fmt.pix_mp.plane_fmt[0].sizeimage, 6220800); isp->vbq.drv_priv = isp; isp_test_set_num_buffers(test, &isp->vbq, 0); @@ -164,10 +162,7 @@ static void isp_test_p010_format(struct kunit *test) KUNIT_EXPECT_EQ(test, fmt->total_size, (size_t)1382400); KUNIT_EXPECT_EQ(test, isp_num_rendered(isp), ISP_MAX_BUFFERS); KUNIT_EXPECT_EQ(test, isp_num_meta(isp), ISP_MAX_BUFFERS); - isp_get_mp_pix_format(isp, &f, fmt); - KUNIT_EXPECT_EQ(test, f.fmt.pix_mp.pixelformat, V4L2_PIX_FMT_NV12); - KUNIT_EXPECT_EQ(test, f.fmt.pix_mp.num_planes, 1); - KUNIT_EXPECT_EQ(test, f.fmt.pix_mp.plane_fmt[0].sizeimage, 1382400); + } static void isp_test_p010_admission(struct kunit *test) diff --git a/drivers/media/platform/apple/isp/isp-v4l2.c b/drivers/media/platform/apple/isp/isp-v4l2.c index bc270e06be21f..ff0133e34dee5 100644 --- a/drivers/media/platform/apple/isp/isp-v4l2.c +++ b/drivers/media/platform/apple/isp/isp-v4l2.c @@ -803,25 +803,6 @@ static inline void isp_get_sp_pix_format(struct apple_isp *isp, f->fmt.pix.quantization = V4L2_QUANTIZATION_LIM_RANGE; } -static inline void isp_get_mp_pix_format(struct apple_isp *isp, - struct v4l2_format *f, - struct isp_format *fmt) -{ - f->fmt.pix_mp.width = fmt->preset->output_dim.x; - f->fmt.pix_mp.height = fmt->preset->output_dim.y; - /* Both contiguous formats have one memory plane and two ISP components. */ - f->fmt.pix_mp.num_planes = 1; - memset(f->fmt.pix_mp.plane_fmt, 0, sizeof(f->fmt.pix_mp.plane_fmt)); - f->fmt.pix_mp.plane_fmt[0].sizeimage = fmt->total_size; - f->fmt.pix_mp.plane_fmt[0].bytesperline = fmt->strides[0]; - f->fmt.pix_mp.field = V4L2_FIELD_NONE; - f->fmt.pix_mp.pixelformat = fmt->pixelformat; - f->fmt.pix_mp.colorspace = V4L2_COLORSPACE_REC709; - f->fmt.pix_mp.ycbcr_enc = V4L2_YCBCR_ENC_709; - f->fmt.pix_mp.xfer_func = V4L2_XFER_FUNC_709; - f->fmt.pix_mp.quantization = V4L2_QUANTIZATION_LIM_RANGE; -} - static int isp_vidioc_get_format(struct file *file, void *fh, struct v4l2_format *f) { @@ -877,69 +858,6 @@ static int isp_vidioc_try_format(struct file *file, void *fh, return 0; } -static int isp_vidioc_get_format_mplane(struct file *file, void *fh, - struct v4l2_format *f) -{ - struct apple_isp *isp = video_drvdata(file); - struct isp_format *fmt = isp_get_current_format(isp); - - if (!isp_supports_p010(isp)) - return -ENOTTY; - - isp_get_mp_pix_format(isp, f, fmt); - - return 0; -} - -static int isp_vidioc_set_format_mplane(struct file *file, void *fh, - struct v4l2_format *f) -{ - struct apple_isp *isp = video_drvdata(file); - struct isp_format *fmt = isp_get_current_format(isp); - struct isp_preset *preset; - int err; - - if (!isp_supports_p010(isp)) - return -ENOTTY; - - if (vb2_is_busy(&isp->vbq)) - return -EBUSY; - - preset = isp_select_preset(isp, f->fmt.pix_mp.width, f->fmt.pix_mp.height, - f->fmt.pix_mp.pixelformat); - err = isp_set_preset(isp, fmt, preset, f->fmt.pix_mp.pixelformat); - if (err) - return err; - - isp_get_mp_pix_format(isp, f, fmt); - - isp->vbq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; - - return 0; -} - -static int isp_vidioc_try_format_mplane(struct file *file, void *fh, - struct v4l2_format *f) -{ - struct apple_isp *isp = video_drvdata(file); - struct isp_format fmt = *isp_get_current_format(isp); - struct isp_preset *preset; - int err; - - if (!isp_supports_p010(isp)) - return -ENOTTY; - - preset = isp_select_preset(isp, f->fmt.pix_mp.width, f->fmt.pix_mp.height, - f->fmt.pix_mp.pixelformat); - err = isp_set_preset(isp, &fmt, preset, f->fmt.pix_mp.pixelformat); - if (err) - return err; - - isp_get_mp_pix_format(isp, f, &fmt); - - return 0; -} - static int isp_vidioc_enum_input(struct file *file, void *fh, struct v4l2_input *inp) { @@ -972,9 +890,7 @@ static int isp_vidioc_get_param(struct file *file, void *fh, { struct apple_isp *isp = video_drvdata(file); - if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && - (!isp_supports_p010(isp) || - a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)) + if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) return -EINVAL; a->parm.capture.capability = V4L2_CAP_TIMEPERFRAME; @@ -990,9 +906,7 @@ static int isp_vidioc_set_param(struct file *file, void *fh, struct apple_isp *isp = video_drvdata(file); struct v4l2_fract *tpf = &a->parm.capture.timeperframe; - if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && - (!isp_supports_p010(isp) || - a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)) + if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) return -EINVAL; /* The rate is configured when the stream starts. */ @@ -1041,9 +955,6 @@ static const struct v4l2_ioctl_ops isp_v4l2_ioctl_ops = { .vidioc_g_fmt_vid_cap = isp_vidioc_get_format, .vidioc_s_fmt_vid_cap = isp_vidioc_set_format, .vidioc_try_fmt_vid_cap = isp_vidioc_try_format, - .vidioc_g_fmt_vid_cap_mplane = isp_vidioc_get_format_mplane, - .vidioc_s_fmt_vid_cap_mplane = isp_vidioc_set_format_mplane, - .vidioc_try_fmt_vid_cap_mplane = isp_vidioc_try_format_mplane, .vidioc_enum_framesizes = isp_vidioc_enum_framesizes, .vidioc_enum_frameintervals = isp_vidioc_enum_frameintervals, @@ -1213,8 +1124,6 @@ int apple_isp_setup_video(struct apple_isp *isp) vdev->fops = &isp_v4l2_fops; vdev->ioctl_ops = &isp_v4l2_ioctl_ops; vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; - if (isp_supports_p010(isp)) - vdev->device_caps |= V4L2_CAP_VIDEO_CAPTURE_MPLANE; vdev->entity.flags = MEDIA_ENT_FL_DEFAULT; vdev->v4l2_dev = &isp->v4l2_dev; vdev->vfl_type = VFL_TYPE_VIDEO; From 7fc12d0823e95d8e8ecd44e504f1f6a59a4689a6 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 15:51:14 +1100 Subject: [PATCH 065/184] docs: media: audit the public J616s capture evidence and AOP map Credit Ryan\x27s m1n1 PR #20, https://github.com/aurora-silicon/m1n1/pull/20, at 9be380c8a5a94609fdbe62d693c7b2121478ad8f for the processed preview protocol. Record what each requested file establishes, the diagnostic RAW snapshot limitation and the missing per-config timing records. Distinguish the separate AOP audio sequence from ISP camera configuration. Assisted-by: LLM Signed-off-by: Ryan Murray --- .../media/drivers/apple-isp-j616s.rst | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/Documentation/driver-api/media/drivers/apple-isp-j616s.rst b/Documentation/driver-api/media/drivers/apple-isp-j616s.rst index bb0b9433a6119..650a109cc7ad1 100644 --- a/Documentation/driver-api/media/drivers/apple-isp-j616s.rst +++ b/Documentation/driver-api/media/drivers/apple-isp-j616s.rst @@ -41,6 +41,59 @@ firmware-derived output modes still need capture validation on 26.6.2. Only our source and synthetic protocol tests are included; no Apple binary, setfile contents, calibration table or firmware image is added. +Public source audit +------------------- + +The pinned PR and its discussion were checked again for the RAW layout and +per-configuration timing. The reusable code establishes the processed path; +it does not contain a RAW Bayer packing/CFA contract or the 13 reply records: + +* ``isp_capture.py`` accepts only 1280x720 and 1920x1080 processed geometry. + Its generic pool-3 builder describes a single address, not the pixel packing, + CFA order or RAW receiver command sequence. The qualified preview uses pool 9. +* ``isp_pixels.py`` contains only an MSB-aligned P010 decoder, with no Bayer decoder. +* ``isp_sensor.py`` reads the sensor ID/count and queries an index at runtime. + It has neither saved per-index replies nor a frame-rate decoder. +* ``isp_profile.py`` and ``fixtures/isp_t6040_25g76.json`` describe boot segments, + allocation geometry and channels. The fixture has no camera-info or config replies. +* ``isp_vid.py`` and ``experiments/isp.py`` use the older fixed 720p NV12 receiver. + ``isp_cmd.py`` includes legacy sensor-0x248 examples and a temporary 3.75 FPS + AE request (0x3c0), alongside a comment describing the older 30 FPS request. + These are not IMX958 per-mode timing records. The qualified T6040 helper instead + requests 30/15 FPS AE limits; neither set supplies a measured nominal-rate table. +* ``isp_command.py`` implements ownership, command/reply and deadline handling. + ``src/isp.c`` prepares boot-time power and heap handoff, not a RAW capture client. +* ``docs/j616s-bringup.md`` lines 752-789 explicitly distinguish a stable RAW + snapshot after stop from a completed buffer report and say orchestration remains + private. Commit ``743e4e75`` adds that diagnostic RAW evidence as documentation; + it does not add a RAW receiver implementation. Lines 832-841 qualify one + completed 1080p P010 frame, with measured FPS still unqualified. + +These findings do not deny the recorded RAW snapshot. They mean a V4L2 Bayer +FOURCC, frame-completion contract and per-config rates cannot be derived from +these files without additional runtime replies or capture evidence. In particular, +the old 0x103 SIF example must not be treated as an IMX958 Bayer-format declaration. + +AOP endpoints and separate audio stream +-------------------------------------- + +The modern ``fw/aop/client.py`` map in PR 20 is 0x20 SPUApp, 0x21 wakehint, +0x22 audio, 0x23 voice trigger, 0x24 accelerometer, 0x25 gyroscope, 0x26 lid +angle, 0x27 ALS and four endpoints at 0x28-0x2b with unqualified roles. The +separate ENDPOINTS13 map assigns different roles and is not the T6040 map. + +The documented HQ audio sequence uses the advertised aop-audio service at +endpoint 0x22, EPIC version 2, checked attachment/property replies and owned +DART mappings. It first establishes LP readiness using adpx buffer property 300, +attachments lpai/pdm0/adpx, lpai pw1, lai attachment and lai/lpai runn. HQ then +attaches hpai, requests pw1, prepares stream-10 NS RX0 DMA and starts DMA before +requesting hpai pwrd. Matching reports and two full copies precede RX stop and +return through pw1 to idle. These are audio commands, not camera mode selectors. + +The qualified ISP preview helper contains no AOP command. This camera change +therefore does not replay the AOP audio stream or alter its Linux driver. The +ISP channel and command maps below describe the camera path independently. + Mode and format selection ------------------------- From 2114ed0c6245126cd90a2e5e96e2cddd4e0697e0 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 15:51:35 +1100 Subject: [PATCH 066/184] docs: media: fix AOP section heading width Correct the reStructuredText underline and verify the complete document with warnings treated as errors. Source protocol credit: Ryan Murray, m1n1 PR #20, https://github.com/aurora-silicon/m1n1/pull/20, at 9be380c8a5a94609fdbe62d693c7b2121478ad8f. Assisted-by: LLM Signed-off-by: Ryan Murray --- Documentation/driver-api/media/drivers/apple-isp-j616s.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/driver-api/media/drivers/apple-isp-j616s.rst b/Documentation/driver-api/media/drivers/apple-isp-j616s.rst index 650a109cc7ad1..9a2bf93d7b059 100644 --- a/Documentation/driver-api/media/drivers/apple-isp-j616s.rst +++ b/Documentation/driver-api/media/drivers/apple-isp-j616s.rst @@ -75,7 +75,7 @@ these files without additional runtime replies or capture evidence. In particula the old 0x103 SIF example must not be treated as an IMX958 Bayer-format declaration. AOP endpoints and separate audio stream --------------------------------------- +----------------------------------------- The modern ``fw/aop/client.py`` map in PR 20 is 0x20 SPUApp, 0x21 wakehint, 0x22 audio, 0x23 voice trigger, 0x24 accelerometer, 0x25 gyroscope, 0x26 lid From 75e950202a6192587c79e0ba5e197ee63dfa2ad7 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 18:26:51 +1100 Subject: [PATCH 067/184] drm/apple: publish J616s panel identity with notch-hidden timings The J616s panel has no adj-height-mm property in the r5g DT. With its notch hidden, probe therefore leaves height_mm zero and the panel EDID builder returns EINVAL. Read the full height before the optional adjusted height and describe the 216 mm viewport in DT. Even with dimensions repaired, the real 118-line vertical porch and 82-line notch-expanded pulse cannot fit in a base EDID DTD. Bound those fields only in the synthetic identity summary, retaining resolution, clock and blanking. Enumerate and program the unchanged DCP catalogue. Restore exact connector millimetres after DRM's EDID update. Test the absent ADT panel nodes with synthetic machine-level properties, real mode geometry, missing adjusted height, and the actual iomfb probe and DRM EDID blob allocation. The publication KUnit case fails on r5g's builder even after repairing the height read. Assisted-by: LLM Signed-off-by: Ryan Murray --- Documentation/gpu/apple-dcp-t6041-panel.rst | 16 ++- arch/arm64/boot/dts/apple/t6040-j616s.dts | 1 + drivers/gpu/drm/apple/dcp-display-metadata.c | 24 +++- drivers/gpu/drm/apple/dcp-panel-adt.c | 1 + drivers/gpu/drm/apple/dcp-panel-test.h | 57 ++++++++++ drivers/gpu/drm/apple/dcp.c | 21 ++-- drivers/gpu/drm/apple/dcp.h | 1 + drivers/gpu/drm/apple/iomfb.c | 14 ++- drivers/gpu/drm/apple/parser-test.c | 106 ++++++++++++++++++ .../selftests/drivers/gpu/apple-dcp/j616s.py | 99 ++++++++++++++++ .../selftests/drivers/gpu/apple-dcp/rate.py | 2 + .../selftests/drivers/gpu/apple-dcp/run.py | 2 + 12 files changed, 331 insertions(+), 13 deletions(-) create mode 100644 drivers/gpu/drm/apple/dcp-panel-test.h create mode 100644 tools/testing/selftests/drivers/gpu/apple-dcp/j616s.py diff --git a/Documentation/gpu/apple-dcp-t6041-panel.rst b/Documentation/gpu/apple-dcp-t6041-panel.rst index 2566c3fd36a94..3e0d107fa7d1b 100644 --- a/Documentation/gpu/apple-dcp-t6041-panel.rst +++ b/Documentation/gpu/apple-dcp-t6041-panel.rst @@ -117,7 +117,15 @@ mappings, not a claim that a particular firmware supplies every property. node is handled the same way. Machine serials from other nodes are ignored. * ``width-mm`` and ``height-mm`` supply bounded physical dimensions. Missing dimensions retain the OF panel node's ``width-mm`` and ``height-mm`` (or - ``adj-height-mm`` when the notch is hidden). + ``adj-height-mm`` when the notch is hidden). The full height is read first, + so a missing optional adjusted height cannot leave the height at zero. + J616s has a 345x223 mm full panel and a 345x216 mm notch-hidden viewport. + The connector retains these exact millimetres after the EDID update. + +A successfully parsed ADT does not necessarily contain a panel node. In +particular, a machine-level ``/product`` ID and ``/arm-io/disp0`` timing data +are not panel identity properties. They do not override OF panel dimensions +or prevent the generic fallback from being published. If the ADT or its identity fields are absent, the desktop J616 fallback is manufacturer APP, product 0616, name Apple Panel and unspecified numeric @@ -125,6 +133,12 @@ serial zero, represented by the non-empty serial descriptor ``00000000``. This generic identity does not claim to identify an individual panel. The preferred representable timing and range limits come from the retained DCP TimingElements catalog. Exact mode enumeration continues to use that catalog. +For notch-hidden modes, the synthetic identity DTD retains the clock, active +area and total blanking but bounds the vertical front porch and sync pulse to +EDID's six-bit fields. It is an identity summary, never an enumerated or +programmed mode; the original DCP sync positions and mode IDs are unchanged. +This also lets a 118-line porch and 82-line notch-expanded pulse publish an +identity instead of failing the base EDID size check. The EDID records model year 2024 (not a manufacture date) and a nominal D65 reference white point required by EDID parsing. No calibrated primaries, gamma, new mode or machine serial is claimed. The ADT reader, diff --git a/arch/arm64/boot/dts/apple/t6040-j616s.dts b/arch/arm64/boot/dts/apple/t6040-j616s.dts index 5707ec4a632d2..7abbd491489d0 100644 --- a/arch/arm64/boot/dts/apple/t6040-j616s.dts +++ b/arch/arm64/boot/dts/apple/t6040-j616s.dts @@ -639,6 +639,7 @@ compatible = "apple,panel-j616", "apple,panel-mini-led", "apple,panel"; width-mm = <345>; height-mm = <223>; + adj-height-mm = <216>; status = "disabled"; }; }; diff --git a/drivers/gpu/drm/apple/dcp-display-metadata.c b/drivers/gpu/drm/apple/dcp-display-metadata.c index f04f651a4ac57..c5921bf67cfa9 100644 --- a/drivers/gpu/drm/apple/dcp-display-metadata.c +++ b/drivers/gpu/drm/apple/dcp-display-metadata.c @@ -205,6 +205,28 @@ const struct drm_edid *dcp_metadata_edid(const struct dcp_display_metadata *meta return edid; } +/* + * This identity-only DTD is not enumerated or programmed. Keep the DCP clock, + * active area and blanking, but fit vertical sync placement into EDID's six-bit + * fields. Notch hiding expands the real sync pulse beyond those fields; the + * exact syncs remain in the authoritative TimingElements catalogue. + */ +static int dcp_panel_identity_timing(struct detailed_timing *timing, + const struct drm_display_mode *mode, + const struct dcp_display_metadata *metadata) +{ + struct drm_display_mode summary = *mode; + + /* Refuse invalid source geometry before reducing the EDID summary. */ + if (mode->vdisplay <= 0 || mode->vdisplay > 4095 || + mode->vsync_start < mode->vdisplay || mode->vsync_end <= mode->vsync_start || + mode->vsync_end > mode->vtotal) + return -EINVAL; + summary.vsync_start = mode->vdisplay + min(mode->vsync_start - mode->vdisplay, 63); + summary.vsync_end = summary.vsync_start + min(mode->vsync_end - mode->vsync_start, 63); + return dcp_metadata_timing(timing, &summary, metadata); +} + /* EDID 1.4 range offsets extend each one-byte limit by 255. */ static int dcp_panel_range_byte(u32 limit, u8 *out, u8 *offsets, u8 flag) { @@ -273,7 +295,7 @@ const struct drm_edid *dcp_panel_edid(const struct dcp_display_metadata *metadat clock = max_t(u32, clock, mode.clock); /* The EDID base timing clock has units of 10 kHz. */ mode.clock = DIV_ROUND_CLOSEST(mode.clock, 10) * 10; - ret = dcp_metadata_timing(&candidate, &mode, &dimensions); + ret = dcp_panel_identity_timing(&candidate, &mode, &dimensions); if (!ret && (!timing || (!preferred && (mode.type & DRM_MODE_TYPE_PREFERRED)))) { struct detailed_pixel_timing *pixel = &candidate.data.pixel_data; diff --git a/drivers/gpu/drm/apple/dcp-panel-adt.c b/drivers/gpu/drm/apple/dcp-panel-adt.c index e3a9b5e4f238e..5887091aa57cf 100644 --- a/drivers/gpu/drm/apple/dcp-panel-adt.c +++ b/drivers/gpu/drm/apple/dcp-panel-adt.c @@ -117,6 +117,7 @@ static int dcp_adt_node(struct dcp_adt_cursor *c, struct dcp_panel_adt *panel, dcp_adt_panel_property(panel, name, prop + 36, size); c->pos += 36 + ALIGN((size_t)size, 4); } + /* A valid ADT may have neither panel node; retain the OF defaults. */ panel->found |= selected; c->pos = props_end; for (u32 i = 0; i < children; i++) { diff --git a/drivers/gpu/drm/apple/dcp-panel-test.h b/drivers/gpu/drm/apple/dcp-panel-test.h new file mode 100644 index 0000000000000..05d575d975fd5 --- /dev/null +++ b/drivers/gpu/drm/apple/dcp-panel-test.h @@ -0,0 +1,57 @@ +/* SPDX-License-Identifier: GPL-2.0-only OR MIT */ +/* Synthetic J616s-shaped input: no /panel or /product/panel. */ +static const u8 panel_test_adt[] = { + 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x6e, 0x61, 0x6d, 0x65, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x64, 0x65, 0x76, 0x69, + 0x63, 0x65, 0x2d, 0x74, 0x72, 0x65, 0x65, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x6e, 0x61, 0x6d, 0x65, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x00, + 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x2d, 0x69, 0x64, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, + 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x70, 0x61, 0x72, 0x74, + 0x69, 0x61, 0x6c, 0x6c, 0x79, 0x2d, 0x6f, 0x63, 0x63, 0x6c, 0x75, 0x64, + 0x65, 0x64, 0x2d, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x6e, 0x61, 0x6d, 0x65, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x61, 0x72, 0x6d, 0x2d, + 0x69, 0x6f, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x6e, 0x61, 0x6d, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x64, 0x69, 0x73, 0x70, 0x30, 0x00, 0x00, 0x00, 0x64, 0x69, 0x73, 0x70, + 0x6c, 0x61, 0x79, 0x2d, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x2d, 0x69, + 0x6e, 0x66, 0x6f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x6e, 0x61, 0x6d, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x63, 0x68, 0x6f, 0x73, 0x65, 0x6e, 0x00, 0x00, 0x73, 0x65, 0x72, 0x69, + 0x61, 0x6c, 0x2d, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x53, 0x59, 0x4e, 0x54, + 0x48, 0x45, 0x54, 0x49, 0x43, 0x2d, 0x4d, 0x41, 0x43, 0x48, 0x49, 0x4e, + 0x45, 0x00, 0x00, 0x00, +}; + +/* Notch-hidden mode geometry observed through the public DRM interface. */ +static const struct dcp_display_mode panel_test_mode = { + .mode = { + .clock = 521065, .hdisplay = 3456, .hsync_start = 3464, + .hsync_end = 3496, .htotal = 3536, .vdisplay = 2160, + .vsync_start = 2278, .vsync_end = 2360, .vtotal = 2456, + .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC, + .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, + }, + .vrr = true, .min_vrr = 24U << 16, .max_vrr = 120U << 16, +}; diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 1d0b81aa3ad65..628c1b7e05660 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -785,6 +785,15 @@ const struct drm_edid *dcp_firmware_edid(struct apple_dcp *dcp, u64 *generation) * without an MTD dependency. Only the desktop panel calls this; other DCP * profiles retain their established identity and brightness paths. */ +void dcp_panel_read_dimensions(struct apple_dcp *dcp, struct device_node *panel_np) +{ + of_property_read_u32(panel_np, "width-mm", &dcp->panel.width_mm); + /* Read the full height first: adj-height-mm is optional. */ + of_property_read_u32(panel_np, "height-mm", &dcp->panel.height_mm); + if (dcp->notch_height) + of_property_read_u32(panel_np, "adj-height-mm", &dcp->panel.height_mm); +} + static void dcp_panel_adt_identity(struct apple_dcp *dcp) { struct dcp_panel_adt panel = { @@ -818,8 +827,9 @@ static void dcp_panel_adt_identity(struct apple_dcp *dcp) dcp->panel.width_mm = panel.width_mm; dcp->panel.height_mm = panel.height_mm; dcp->desktop->panel_adt_identity = panel.identity; - dev_info(dcp->dev, "panel identity fallback: ADT %s, OF dimensions %ux%u mm\n", - ret ? "unavailable" : "read", panel.width_mm, panel.height_mm); + dev_info(dcp->dev, "panel identity fallback: ADT %s, panel node %s, dimensions %ux%u mm\n", + ret ? "unavailable" : "parsed", panel.found ? "present" : "absent", + panel.width_mm, panel.height_mm); } /* Internal panel identity has no detachable Type-C service or EDID transport. */ @@ -4918,8 +4928,6 @@ static int dcp_comp_bind_pipe(struct device *dev, struct device *main, void *dat panel_np = of_get_compatible_child(dev->of_node, "apple,panel"); if (panel_np) { - const char height_prop[2][16] = { "adj-height-mm", "height-mm" }; - if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) { /* * Desktop 26.x swaps carry nits, and the panel node @@ -4939,10 +4947,7 @@ static int dcp_comp_bind_pipe(struct device *dev, struct device *main, void *dat /* J616 ProMotion capability belongs to the panel, not a boot option. */ dcp->panel.has_vrr = of_device_is_compatible(panel_np, "apple,panel-j616"); - of_property_read_u32(panel_np, "width-mm", &dcp->panel.width_mm); - /* use adjusted height as long as the notch is hidden */ - of_property_read_u32(panel_np, height_prop[!dcp->notch_height], - &dcp->panel.height_mm); + dcp_panel_read_dimensions(dcp, panel_np); if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) dcp_panel_adt_identity(dcp); diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h index 30b728820e2c7..02e13b32b62c7 100644 --- a/drivers/gpu/drm/apple/dcp.h +++ b/drivers/gpu/drm/apple/dcp.h @@ -101,6 +101,7 @@ bool dcp_crtc_mode_fixup(struct drm_crtc *crtc, const struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode); void dcp_set_dimensions(struct apple_dcp *dcp, u64 generation); +void dcp_panel_read_dimensions(struct apple_dcp *dcp, struct device_node *panel_np); void dcp_update_connector_dimensions(struct apple_dcp *dcp, struct drm_connector *connector); void dcp_send_message(struct apple_dcp *dcp, u8 endpoint, u64 message); diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 67a6ff3dfedb1..27c7ba4b39b66 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -2076,9 +2076,13 @@ int dcp_get_modes(struct drm_connector *connector) dev_info(dcp->dev, "using firmware-derived EDID metadata for connection %llu\n", generation); } - } else if (apple_connector->drm_edid_firmware_derived) { - drm_edid_connector_update(connector, NULL); - apple_connector->drm_edid_firmware_derived = false; + } else { + if (panel && nr_modes) + dev_dbg(dcp->dev, "panel EDID generation failed: %pe\n", derived); + if (apple_connector->drm_edid_firmware_derived) { + drm_edid_connector_update(connector, NULL); + apple_connector->drm_edid_firmware_derived = false; + } } } @@ -2132,6 +2136,10 @@ int dcp_get_modes(struct drm_connector *connector) connector->display_info.monitor_range.max_vfreq = max_vfreq; } + /* EDID base dimensions are rounded to cm; the panel's OF/ADT size is exact. */ + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && dcp_has_panel(dcp)) + dcp_update_connector_dimensions(dcp, connector); + return published; } diff --git a/drivers/gpu/drm/apple/parser-test.c b/drivers/gpu/drm/apple/parser-test.c index 85b6c0df99753..dca33251ad5ed 100644 --- a/drivers/gpu/drm/apple/parser-test.c +++ b/drivers/gpu/drm/apple/parser-test.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include "dcp.h" @@ -15,6 +16,7 @@ #include "iomfb_internal.h" #include "parser.h" #include "dcp-panel-adt.h" +#include "dcp-panel-test.h" static struct device parser_device = { .init_name = "apple-display-parser-test", @@ -1427,7 +1429,111 @@ static void parser_adt_bounds(struct kunit *test) KUNIT_EXPECT_EQ(test, dcp_panel_adt_parse(NULL, 0, &panel), -EINVAL); } +static void parser_panel_of_dimensions(struct kunit *test) +{ + __be32 width = cpu_to_be32(345), height = cpu_to_be32(223); + __be32 adjusted = cpu_to_be32(216); + struct property adj = { .name = "adj-height-mm", .length = 4, .value = &adjusted }; + struct property h = { .name = "height-mm", .length = 4, .value = &height }; + struct property w = { .name = "width-mm", .length = 4, .value = &width, .next = &h }; + struct device_node node = { .properties = &w }; + struct apple_dcp *dcp = kunit_kzalloc(test, sizeof(*dcp), GFP_KERNEL); + + KUNIT_ASSERT_NOT_NULL(test, dcp); + dcp->notch_height = 74; + /* A missing optional adj-height-mm must not erase the full height. */ + dcp_panel_read_dimensions(dcp, &node); + KUNIT_EXPECT_EQ(test, dcp->panel.width_mm, 345U); + KUNIT_EXPECT_EQ(test, dcp->panel.height_mm, 223U); + h.next = &adj; + dcp_panel_read_dimensions(dcp, &node); + KUNIT_EXPECT_EQ(test, dcp->panel.height_mm, 216U); + dcp->notch_height = 0; + dcp_panel_read_dimensions(dcp, &node); + KUNIT_EXPECT_EQ(test, dcp->panel.height_mm, 223U); +} + +static void parser_panel_j616s_publication(struct kunit *test) +{ + struct apple_dcp *dcp = kunit_kzalloc(test, sizeof(*dcp), GFP_KERNEL); + struct dcp_desktop_state *desktop = kunit_kzalloc(test, sizeof(*desktop), GFP_KERNEL); + struct platform_device *pipeline = kunit_kzalloc(test, sizeof(*pipeline), GFP_KERNEL); + struct apple_connector *connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + struct dcp_panel_adt panel = { + .identity = { .manufacturer = "APP", .product = 0x616, .name = "Apple Panel" }, + .width_mm = 345, .height_mm = 216, + }; + struct dcp_panel_adt before = panel; + struct device *dev = drm_kunit_helper_alloc_device(test); + struct drm_device *drm; + struct drm_display_mode *published; + struct dcp_display_mode mode = panel_test_mode; + const struct edid *raw; + int count, ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + drm = __drm_kunit_helper_alloc_drm_device(test, dev, sizeof(*drm), 0, + DRIVER_MODESET | DRIVER_ATOMIC); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); + KUNIT_ASSERT_NOT_NULL(test, dcp); + KUNIT_ASSERT_NOT_NULL(test, desktop); + KUNIT_ASSERT_NOT_NULL(test, pipeline); + KUNIT_ASSERT_NOT_NULL(test, connector); + ret = drmm_connector_init(drm, &connector->base, &parser_route_connector_funcs, + DRM_MODE_CONNECTOR_eDP, NULL); + KUNIT_ASSERT_EQ(test, ret, 0); + ret = dcp_panel_adt_parse(panel_test_adt, sizeof(panel_test_adt), &panel); + KUNIT_ASSERT_EQ(test, ret, 0); + /* /product's machine ID is not a panel product; neither panel node exists. */ + KUNIT_EXPECT_FALSE(test, panel.found); + KUNIT_EXPECT_MEMEQ(test, &panel, &before, sizeof(panel)); + parser_modes_init(dcp); + mutex_init(&dcp->dcpavserv.lock); + mutex_init(&dcp->hpd_mutex); + dcp->dev = dev; + dcp->desktop = desktop; + dcp->fw_compat = DCP_FIRMWARE_V_26_6_DESKTOP; + dcp->panel.width_mm = panel.width_mm; + dcp->panel.height_mm = panel.height_mm; + desktop->panel_adt_identity = panel.identity; + dcp->connector = connector; + dcp->modes = &mode; + dcp->nr_modes = 1; + dcp->modes_admitted = true; + connector->dcp = pipeline; + connector->edid_live = true; + platform_set_drvdata(pipeline, dcp); + + /* Probe through the real iomfb publication path and DRM blob allocator. */ + mutex_lock(&drm->mode_config.mutex); + count = dcp_get_modes(&connector->base); + mutex_unlock(&drm->mode_config.mutex); + KUNIT_EXPECT_EQ(test, count, 1); + KUNIT_ASSERT_NOT_NULL(test, connector->base.edid_blob_ptr); + KUNIT_EXPECT_EQ(test, connector->base.edid_blob_ptr->length, (size_t)128); + KUNIT_EXPECT_TRUE(test, connector->drm_edid_firmware_derived); + raw = connector->base.edid_blob_ptr->data; + KUNIT_EXPECT_MEMEQ(test, raw->detailed_timings[1].data.other_data.data.str.str, + "Apple Panel", 11); + KUNIT_EXPECT_EQ(test, connector->base.display_info.width_mm, 345U); + KUNIT_EXPECT_EQ(test, connector->base.display_info.height_mm, 216U); + KUNIT_EXPECT_MEMEQ(test, &mode, &panel_test_mode, sizeof(mode)); + KUNIT_ASSERT_FALSE(test, list_empty(&connector->base.probed_modes)); + published = list_first_entry(&connector->base.probed_modes, struct drm_display_mode, head); + KUNIT_EXPECT_EQ(test, published->clock, 521065); + KUNIT_EXPECT_EQ(test, published->vsync_start, 2278); + KUNIT_EXPECT_EQ(test, published->vsync_end, 2360); + list_del(&published->head); + drm_mode_destroy(drm, published); + /* The stack catalogue is not owned by dcp_modes_release(). */ + dcp->modes = NULL; + dcp->nr_modes = 0; + dcp_modes_release(dcp); +} + static struct kunit_case parser_cases[] = { + KUNIT_CASE(parser_panel_of_dimensions), + KUNIT_CASE(parser_panel_j616s_publication), KUNIT_CASE(parser_adt_bounds), KUNIT_CASE(parser_identity_bounds), KUNIT_CASE(parser_panel_identity_stable), diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/j616s.py b/tools/testing/selftests/drivers/gpu/apple-dcp/j616s.py new file mode 100644 index 0000000000000..3d903694e9e5c --- /dev/null +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/j616s.py @@ -0,0 +1,99 @@ +# SPDX-License-Identifier: GPL-2.0-only OR MIT +"""Panel identity with absent ADT panel nodes and notch-hidden timings.""" +import re +from common import block +import desktop + + +def identity(root, output): + base = root / 'drivers/gpu/drm/apple' + unit = desktop.panel_identity(root).split('int main(int argc, char **argv)')[0] + unit = unit.replace('int fw_compat;bool has_panel;', + 'int fw_compat;unsigned notch_height;bool has_panel;') + unit += r''' +#define ALIGN(x,n) (((x)+(n)-1)&~((n)-1)) +#define SZ_16M (16U<<20) +static u32 get_unaligned_le32(const void *p) {u32 n;memcpy(&n,p,4);return n;} +''' + unit += block((base / 'dcp-panel-adt.h').read_text(), 'struct dcp_panel_adt {') + ';\n' + unit += '\n'.join(line for line in (base / 'dcp-panel-adt.c').read_text().splitlines() + if not line.startswith('#include')) + unit += (base / 'dcp-panel-test.h').read_text() + source = (base / 'dcp.c').read_text() + # Also exercise the pre-fix property read, so the regression fails on r5g. + if 'void dcp_panel_read_dimensions(' in source: + reader = block(source, 'void dcp_panel_read_dimensions(') + else: + start = source.index('\t\tof_property_read_u32(panel_np, "width-mm"') + end = source.index('\n\n', start) + reader = ('void dcp_panel_read_dimensions(struct apple_dcp *dcp, ' + 'struct device_node *panel_np) {\n' + 'const char height_prop[2][16] = {"adj-height-mm", "height-mm"};\n' + + source[start:end] + '\n}') + dts = (root / 'arch/arm64/boot/dts/apple/t6040-j616s.dts').read_text() + values = [] + for name in ('width-mm', 'height-mm', 'adj-height-mm'): + match = re.search(r'^\s*' + name + r'\s*=\s*<(\d+)>;', dts, re.M) + values.append(int(match[1]) if match else 0) + unit += r''' +struct device_node { u32 width, height, adjusted; }; +static int of_property_read_u32(struct device_node *np, const char *name, u32 *out) +{ + u32 value = !strcmp(name,"width-mm") ? np->width : + !strcmp(name,"height-mm") ? np->height : np->adjusted; + if (!value) return -EINVAL; + *out=value; + return 0; +} +''' + reader + '\n' + r''' +int main(int argc, char **argv) +{ + assert(argc==3); + struct apple_dcp d={.fw_compat=5,.has_panel=true}; + __typeof__(*d.desktop) state={0};d.desktop=&state; + struct device_node np={DTS_VALUES}; + d.notch_height=74; + if (!strcmp(argv[1],"dimensions")) { + dcp_panel_read_dimensions(&d,&np); + assert(d.panel.width_mm==345 && d.panel.height_mm==216); + /* A missing optional adjusted height must retain the full height. */ + np.adjusted=0;d.panel.height_mm=0; + dcp_panel_read_dimensions(&d,&np); + assert(d.panel.width_mm==345 && d.panel.height_mm==223); + d.notch_height=0;np.adjusted=216; + dcp_panel_read_dimensions(&d,&np); + assert(d.panel.height_mm==223); + return 0; + } + struct dcp_panel_adt panel={ + .identity={.manufacturer="APP",.product=0x616,.name="Apple Panel"}, + .width_mm=345,.height_mm=216, + }, before=panel; + assert(!dcp_panel_adt_parse(panel_test_adt,sizeof(panel_test_adt),&panel)); + assert(!panel.found && !memcmp(&panel,&before,sizeof(panel))); + for (unsigned n=0;nraw; + assert(!memcmp(raw+77,"Apple Panel",11)); + assert((raw[66] | (raw[68]>>4)<<8)==345); + assert((raw[67] | (raw[68]&15)<<8)==216); + FILE *file=fopen(argv[2],"wb");assert(file); + assert(fwrite(raw,128,1,file)==1);fclose(file); + drm_edid_free(edid); + /* Invalid geometry must still be refused rather than repaired. */ + mode.mode.vsync_end=mode.mode.vtotal+1; + assert(IS_ERR(dcp_panel_edid_snapshot(&d,&mode,1,&generation))); + return 0; +} +'''.replace('DTS_VALUES', ','.join(map(str, values))) + return unit, (("dimensions", str(output / 'j616s-panel.edid')), + ("identity", str(output / 'j616s-panel.edid'))) diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py b/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py index 02e23247f50b2..466688aff3638 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py @@ -700,6 +700,8 @@ def publication(root): /* This fixture selects an external H17P sink, never desktop adaptive sync. */ static bool dcp_desktop_vrr_supported(struct apple_dcp *d) { assert(d->fw_compat != DCP_FIRMWARE_V_26_6_DESKTOP); return false; } +static void dcp_update_connector_dimensions(struct apple_dcp *d,struct drm_connector *c) +{ (void)c;assert(d->fw_compat != DCP_FIRMWARE_V_26_6_DESKTOP); } '''+firmware_lineage(root)+internal_inline(root,'dcp_fw_is_14_7')+r'''/* Off T6030 machines the direct crossbar output is not clocked separately. */ static int __attribute__((unused)) dcp_direct_crossbar_link(struct apple_dcp *d,bool up) { (void)d;(void)up;return 0; } ''' diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/run.py b/tools/testing/selftests/drivers/gpu/apple-dcp/run.py index 1b27400946e06..b78b93def55b2 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/run.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/run.py @@ -27,6 +27,7 @@ import accounting import idle import desktop +import j616s import rate import boot import m4 @@ -61,6 +62,7 @@ def main(): ('display', 'panel-brightness-takeover', display.takeover, ((),), ()), ('display', 'panel-mode-catalog', display.modes, ((),), ()), ('display', 'desktop-brightness-notifications', desktop.brightness_notifications, ((),), ()), + ('display', 'j616s-panel-identity', lambda r: j616s.identity(r, output), ((),), ('-Wno-unused-function', '-Wno-unused-const-variable')), ('display', 'desktop-adt-identity', desktop.adt_identity, ((),), ()), ('display', 'desktop-identity-trace-budget', desktop.identity_trace_budget, ((),), ()), ('display', 'desktop-identity-keys', desktop.identity_keys, ((),), ('-Wno-unused-function',)), From af0a664046fbb77c07828055e36d319427697c4f Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 18:33:13 +1100 Subject: [PATCH 068/184] drm/apple: label an unspecified panel serial without a dummy value Keep numeric serial zero when no panel identity source provides one, but use an explicit "unspecified" text descriptor instead of eight zeroes. Both remain non-null to userspace, and edid-decode no longer warns about a dummy serial on the generic J616s fallback. Do not invent an identity. Assisted-by: LLM Signed-off-by: Ryan Murray --- Documentation/gpu/apple-dcp-t6041-panel.rst | 2 +- drivers/gpu/drm/apple/dcp-display-metadata.c | 5 ++++- tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py | 2 +- tools/testing/selftests/drivers/gpu/apple-dcp/j616s.py | 1 + 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Documentation/gpu/apple-dcp-t6041-panel.rst b/Documentation/gpu/apple-dcp-t6041-panel.rst index 3e0d107fa7d1b..bdf15e644d68f 100644 --- a/Documentation/gpu/apple-dcp-t6041-panel.rst +++ b/Documentation/gpu/apple-dcp-t6041-panel.rst @@ -129,7 +129,7 @@ or prevent the generic fallback from being published. If the ADT or its identity fields are absent, the desktop J616 fallback is manufacturer APP, product 0616, name Apple Panel and unspecified numeric -serial zero, represented by the non-empty serial descriptor ``00000000``. +serial zero, represented by the non-empty serial descriptor ``unspecified``. This generic identity does not claim to identify an individual panel. The preferred representable timing and range limits come from the retained DCP TimingElements catalog. Exact mode enumeration continues to use that catalog. diff --git a/drivers/gpu/drm/apple/dcp-display-metadata.c b/drivers/gpu/drm/apple/dcp-display-metadata.c index c5921bf67cfa9..729eb58002f21 100644 --- a/drivers/gpu/drm/apple/dcp-display-metadata.c +++ b/drivers/gpu/drm/apple/dcp-display-metadata.c @@ -335,7 +335,10 @@ const struct drm_edid *dcp_panel_edid(const struct dcp_display_metadata *metadat metadata->name[0] ? metadata->name : name); if (ret) return ERR_PTR(ret); - snprintf(serial, sizeof(serial), "%08x", metadata->serial); + if (metadata->serial) + snprintf(serial, sizeof(serial), "%08x", metadata->serial); + else + snprintf(serial, sizeof(serial), "unspecified"); ret = dcp_metadata_text(&block.detailed_timings[2], EDID_DETAIL_MONITOR_SERIAL, metadata->serial_text[0] ? metadata->serial_text : serial); if (ret) diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py index c5e655ee46da7..60b903f85d22f 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py @@ -464,7 +464,7 @@ def panel_identity(root, output=None): const struct drm_edid *minimal_edid=dcp_panel_edid_snapshot(&d,modes,3,&generation); assert(!IS_ERR(minimal_edid) && drm_edid_valid(minimal_edid)); const u8 *m=(const void *)&minimal_edid->raw; - assert(!memcmp(m+72+5,"TST 002a",8) && !memcmp(m+90+5,"00000000",8)); + assert(!memcmp(m+72+5,"TST 002a",8) && !memcmp(m+90+5,"unspecified",11)); drm_edid_free(minimal_edid); } /* Null and unrelated setters preserve the existing ACK and identity. */ diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/j616s.py b/tools/testing/selftests/drivers/gpu/apple-dcp/j616s.py index 3d903694e9e5c..45af01294a55e 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/j616s.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/j616s.py @@ -84,6 +84,7 @@ def identity(root, output): assert(!memcmp(&mode,&saved,sizeof(mode)) && generation==0); const u8 *raw=(const u8 *)&edid->raw; assert(!memcmp(raw+77,"Apple Panel",11)); + assert(!memcmp(raw+95,"unspecified",11)); assert((raw[66] | (raw[68]>>4)<<8)==345); assert((raw[67] | (raw[68]&15)<<8)==216); FILE *file=fopen(argv[2],"wb");assert(file); From dbc1bbb9084f7063aa9be318975d23b04b6f3d9c Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 22:17:11 +1100 Subject: [PATCH 069/184] selftests: asahi-m4: exercise the retained timestamp CPU mapping The shared timestamp writer delegates to TimestampVmap after the G17 mapping reuse change. Compile that production helper in the host test instead of providing a Map without write(), so the stores, cache clean and overlapping-destination checks run again. Assisted-by: LLM Signed-off-by: Ryan Murray --- .../drivers/gpu/asahi-m4/test-timestamps.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tools/testing/selftests/drivers/gpu/asahi-m4/test-timestamps.py b/tools/testing/selftests/drivers/gpu/asahi-m4/test-timestamps.py index 842ca85c090fb..9a159e849e86b 100755 --- a/tools/testing/selftests/drivers/gpu/asahi-m4/test-timestamps.py +++ b/tools/testing/selftests/drivers/gpu/asahi-m4/test-timestamps.py @@ -24,6 +24,7 @@ def function(source, marker): poll = (backend / 'g16/poll.rs').read_text() cache_policy = function((backend / 'gem.rs').read_text(), 'pub(crate) fn is_writeback(') store = function(mmu, 'pub(crate) fn write(&self, value: u64)') +vmap_store = function(mmu, 'pub(crate) fn write(&self, offset: usize, value: u64)') write = function(shared, 'fn write(&self, ticks: u64, frequency_hz: u64)') checked_offset = function(shared, 'fn checked_offset(') publish = function(shared, 'pub(crate) fn publish(&self, ticks: [u64; 4], frequency_hz: u64)') @@ -50,7 +51,12 @@ def function(source, marker): unsafe impl Send for Map {} unsafe impl Sync for Map {} struct View(*mut u8);impl View{fn as_mut_ptr(&self)->*mut u8{self.0}} impl Map{fn get(&self)->View{View(self.backing.words.as_ptr().cast_mut().cast())}fn owner(&self)->&Backing{&self.backing}} -struct TimestampMapping{mapping:Map,offset:usize} +struct TimestampVmap(Map); +impl TimestampVmap { + fn size(&self)->usize{core::mem::size_of_val(&self.0.backing.words)} + __VMAP_STORE__ +} +struct TimestampMapping{mapping:TimestampVmap,offset:usize} impl TimestampMapping { __STORE__ } __CHECKED_OFFSET__ struct Destination{cpu:TimestampMapping} @@ -63,7 +69,7 @@ def function(source, marker): fn backing()->Arc{backing_with_flags(0)} fn backing_with_flags(flags:u32)->Arc{Arc::new(Backing{flags,words:[const{AtomicU64::new(u64::MAX)};4],timestamp_lock:std::sync::Mutex::new(())})} fn destination(memory:&Arc,offset:usize)->Destination{ - Destination{cpu:TimestampMapping{mapping:Map{backing:memory.clone(),addr:memory.words.as_ptr().cast_mut().cast()},offset}} + Destination{cpu:TimestampMapping{mapping:TimestampVmap(Map{backing:memory.clone(),addr:memory.words.as_ptr().cast_mut().cast()}),offset}} } fn outputs(memory:&Arc)->Destinations{ Destinations(core::array::from_fn(|i|Some(destination(memory,i*8)))) @@ -191,7 +197,7 @@ def function(source, marker): assert_eq!(firmware().publish_timestamps(&flight,&claims),Err(EINVAL)); assert_eq!(memory.words[0].load(Ordering::Relaxed),u64::MAX); } -'''.replace('__CHECKED_OFFSET__', checked_offset).replace('__STORE__', store).replace('__CACHE_POLICY__', cache_policy).replace('__WRITE__', write).replace('__PUBLISH__', publish).replace('__RETIRE__', retire.replace('fn publish_timestamps', 'pub(crate) fn publish_timestamps')) +'''.replace('__CHECKED_OFFSET__', checked_offset).replace('__STORE__', store).replace('__VMAP_STORE__', vmap_store.replace('.timestamp_lock.lock()', '.timestamp_lock.lock().unwrap()')).replace('__CACHE_POLICY__', cache_policy).replace('__WRITE__', write).replace('__PUBLISH__', publish).replace('__RETIRE__', retire.replace('fn publish_timestamps', 'pub(crate) fn publish_timestamps')) path = out / 'timestamps-host.rs' path.write_text(source) subprocess.run(rustc() + ['--edition=2021', '--test', str(path), '-o', str(out / 'timestamps-host')], check=True) From 029698013e0a32c7c6f76542bb6487c06ac1a91b Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 22:20:06 +1100 Subject: [PATCH 070/184] tools/asahi: follow shared compute ownership and release notifications Update the host adapters for the shared availability notifications and compute source loans introduced by the G17 submission changes. Exercise the production ReaderHint predicate and verify a loan prevents sharing, reuse and rearming until its last reader leaves. Keep the close, recovery, growth and first-publication negative controls. Point the sharing mutations at the current shareable predicate so both retirement and kill guards are still tested. Assisted-by: LLM Signed-off-by: Ryan Murray --- tools/asahi/g17-capacity-fault-tests.py | 29 ++++++++++++++++---- tools/asahi/g17-compute-constructor-tests.py | 2 +- tools/asahi/g17-growth-gate-tests.py | 1 + tools/asahi/g17-uninstalled-cleanup-tests.py | 5 ++-- 4 files changed, 28 insertions(+), 9 deletions(-) diff --git a/tools/asahi/g17-capacity-fault-tests.py b/tools/asahi/g17-capacity-fault-tests.py index 09a70caaaccb4..2d0e755b5b01f 100644 --- a/tools/asahi/g17-capacity-fault-tests.py +++ b/tools/asahi/g17-capacity-fault-tests.py @@ -30,7 +30,7 @@ def fixture(): methods = '\n'.join(item(source, sig) for sig in [ 'pub(crate) fn release_owner(', 'pub(crate) fn reusable(', 'pub(crate) fn recycle_quarantined_idle(', 'pub(crate) fn recycle_killed(', - 'fn recycle_settled(', 'fn release_witnessed_inner(', 'pub(crate) fn share_idle(', 'pub(crate) fn can_rearm(', 'pub(crate) fn recycled_pending(']) + 'fn recycle_settled(', 'fn release_witnessed_inner(', 'pub(crate) fn share_idle(', 'pub(crate) fn shareable(', 'pub(crate) fn can_rearm(', 'pub(crate) fn recycled_pending(']) exit_source = (ROOT / 'queue/compute/exit.rs').read_text() methods += '\n' + item(exit_source, 'pub(crate) fn settle_exit(').replace('super::Previous', 'Previous').replace('crate::g17::qos::Owner', 'qos::Owner') drain = item((ROOT / 'mod.rs').read_text(), 'fn drain_reclaims(') @@ -48,6 +48,7 @@ def fixture(): fn now_ns() -> u64 { 123 } struct Context { id: u64, live: Arc, killed: AtomicU32 } impl Context { + fn note_compute_binding(&self, _: bool) {} fn new(id: u64, live: &Arc) -> Arc { live.fetch_add(1, Ordering::SeqCst); Arc::new(Self { id, live: live.clone(), killed: AtomicU32::new(1) }) @@ -55,7 +56,7 @@ def fixture(): } impl Drop for Context { fn drop(&mut self) { self.live.fetch_sub(1, Ordering::SeqCst); } } struct Binding { context: Arc } -''' + previous + r''' +''' + previous + '\n' + item((ROOT / 'sync_input.rs').read_text(), 'pub(super) struct ReaderHint').replace('pub(super) ', '') + '\nimpl ReaderHint {\n' + item((ROOT / 'sync_input.rs').read_text(), 'pub(super) fn has_readers(').replace('pub(super) ', '') + '\n}\n' + r''' #[derive(Clone, Copy, PartialEq)] enum PoolPhase { Populated, ReleaseSent(u32), Vacant } #[derive(Clone, Copy)] struct Ticket { owner: u64, item_count: u32, descriptor: u64, item_producer: u32, kick: u64, @@ -107,6 +108,7 @@ def fixture(): retired_by_teardown: bool, spared_quarantine: bool, spared_deferred: bool, retire_pending: bool, retirement_proved: bool, replay_retirement: bool, replay_error: Option, replay_prepared: bool, submitted: u32, kick: Kick, + config_cache: Option<()>, source_hint: ReaderHint, source_readers: AtomicU32, claims: u32, } impl Queue { fn valid_ticket(&self, _: &Active, _: &Ticket) -> Result { Ok(()) } @@ -126,6 +128,7 @@ def fixture(): retired_by_teardown: false, spared_quarantine: false, spared_deferred: false, retire_pending: false, retirement_proved: false, replay_retirement: false, replay_error: None, replay_prepared: false, submitted: 0, kick: Kick, + config_cache: None, source_hint: ReaderHint { possible: false }, source_readers: AtomicU32::new(0), claims: 0, } } fn fault(live: &Arc) -> Queue { @@ -146,20 +149,34 @@ def fixture(): impl Reclaims { fn take_one(&mut self) -> Option { self.0.pop() } } struct Mutex(std::sync::Mutex); impl Mutex { fn lock(&self) -> std::sync::MutexGuard<'_, T> { self.0.lock().unwrap() } } -struct Shared { reclaim: Mutex, wakes: AtomicUsize } +struct Available(AtomicUsize); impl Available {fn notify(&self){self.0.fetch_add(1, Ordering::SeqCst);}} +struct Shared { reclaim: Mutex, wakes: AtomicUsize, queues: Available } impl Shared { fn queue_events(&self) { self.wakes.fetch_add(1, Ordering::SeqCst); } ''' + drain + r''' } fn main() { let finished = Arc::new(AtomicUsize::new(0)); - let shared = Arc::new(Shared { reclaim: Mutex(std::sync::Mutex::new(Reclaims(vec![Reclaim(finished.clone())]))), wakes: AtomicUsize::new(0) }); + let shared = Arc::new(Shared { reclaim: Mutex(std::sync::Mutex::new(Reclaims(vec![Reclaim(finished.clone())]))), wakes: AtomicUsize::new(0), queues: Available(AtomicUsize::new(0)) }); shared.drain_reclaims(); assert_eq!(finished.load(Ordering::SeqCst), 1); assert_eq!(shared.wakes.load(Ordering::SeqCst), 1); shared.drain_reclaims(); assert_eq!(shared.wakes.load(Ordering::SeqCst), 1); + assert_eq!(shared.queues.0.load(Ordering::SeqCst), 1); let live = Arc::new(AtomicUsize::new(0)); + let mut borrowed = idle(&live); + borrowed.retirement_ready = true; + borrowed.source_hint.possible = true; + borrowed.source_readers.store(1, Ordering::Release); + assert!(!borrowed.shareable()); + borrowed.release_owner().unwrap(); + assert!(!borrowed.reusable()); + borrowed.pool_phase = PoolPhase::Vacant; + assert!(!borrowed.can_rearm()); + borrowed.source_readers.store(0, Ordering::Release); + assert!(borrowed.reusable() && borrowed.can_rearm()); + drop(borrowed); for _ in 0..512 { // Rebound detached queue; lost VM / killed publisher / allocation error // before the first new command. No future completion exists to free it. @@ -266,8 +283,8 @@ def main(): ('missing-fault-release', source.replace(fault_fix, '', 1), False), ('missing-kill-release', source.replace('Previous::retire_witnessed(&mut self.previous);\n self.exit', 'self.exit'), False), ('missing-reclaim-wake', source.replace('self.queue_events();', ''), False), - ('share-before-retirement', source.replace('|| !self.retirement_ready', ''), False), - ('share-during-kill', source.replace('|| self.exit.is_some() || self.active.len', '|| self.active.len'), False)] + ('share-before-retirement', source.replace('&& self.owner.is_some() && self.retirement_ready', '&& self.owner.is_some()'), False), + ('share-during-kill', source.replace('&& self.exit.is_none() && self.active.len', '&& self.active.len'), False)] with tempfile.TemporaryDirectory(prefix='g17-capacity-') as tmp: for name, rust, passes in controls: path = Path(tmp) / (name + '.rs') diff --git a/tools/asahi/g17-compute-constructor-tests.py b/tools/asahi/g17-compute-constructor-tests.py index f4fdcf2c68222..8c0c4dc772885 100644 --- a/tools/asahi/g17-compute-constructor-tests.py +++ b/tools/asahi/g17-compute-constructor-tests.py @@ -50,7 +50,7 @@ def fixture(): fn notify_all(&self){} } struct Shared{preparations:Gate,state:State,changed:Changed,crashed:AtomicBool} -impl Shared{fn drm(&self)->Result<()>{Ok(())}} +impl Shared{fn drm(&self)->Result<()>{Ok(())} fn note_changed(&self){self.changed.notify_all();}} struct Context;impl Context{fn vm(&self)->&(){&()}} struct Backend{shared:Shared,context:Context} struct Allocator<'a>{dev:&'a (),uat:&'a ()} diff --git a/tools/asahi/g17-growth-gate-tests.py b/tools/asahi/g17-growth-gate-tests.py index d6ad5f9beef30..8b4285782f25b 100644 --- a/tools/asahi/g17-growth-gate-tests.py +++ b/tools/asahi/g17-growth-gate-tests.py @@ -38,6 +38,7 @@ def fixture(): struct Gate{state:AtomicU64,wait:Mutex,changed:CondVar} struct Lease{gate:Arc,epoch:u64} impl Gate { + fn wake_waiters(&self) {} ''' + methods + '\n}\n' + item(gate,'impl Drop for Lease') + r''' struct Backend {gate:Arc,ready:mpsc::Sender<()>,proceed:StdMutex>} impl Backend { diff --git a/tools/asahi/g17-uninstalled-cleanup-tests.py b/tools/asahi/g17-uninstalled-cleanup-tests.py index 33d2f252eb81c..d74f67aba6267 100644 --- a/tools/asahi/g17-uninstalled-cleanup-tests.py +++ b/tools/asahi/g17-uninstalled-cleanup-tests.py @@ -61,7 +61,8 @@ def fixture(): impl Drop for Guard<'_>{fn drop(&mut self){LOCKED.store(false,Ordering::SeqCst);}} struct State(Mutex>>); impl State{fn lock(&self)->Guard<'_>{let g=self.0.lock().unwrap();assert!(!LOCKED.swap(true,Ordering::SeqCst));Guard(g)}} -struct Shared{state:State} +struct Available; impl Available {fn notify(&self){}} +struct Shared{state:State,channels:Available} impl Shared{fn queue_events(&self){}} struct Backend{shared:Arc} impl Backend { @@ -76,7 +77,7 @@ def fixture(): } } fn main(){ - let b=Backend{shared:Arc::new(Shared{state:State(Mutex::new(Some(Box::new(Firmware{queues:Registry}))))})}; + let b=Backend{shared:Arc::new(Shared{state:State(Mutex::new(Some(Box::new(Firmware{queues:Registry})))),channels:Available})}; b.finish_uninstalled_compute(None).unwrap(); for _ in 0..512 { MAPPED.store(true,Ordering::SeqCst);RESERVED.store(true,Ordering::SeqCst); From 989406b403571dc0e7429fdd1433c8fd72ca12a8 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 22:20:53 +1100 Subject: [PATCH 071/184] drm/asahi: test the complete T6040 performance selector ladder Use valid sixteen-slot handoffs in the calibration KUnit fixture, with both eleven-state maps ending at raw OPP 15. Check the highest state's second voltage lane as well as the intermediate-state ordering and relative-frequency consistency. The former 0..10 fixture could not pass the production map reader and never exercised the final five raw operating points. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/gpu/drm/asahi/g17/calibration.rs | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/asahi/g17/calibration.rs b/drivers/gpu/drm/asahi/g17/calibration.rs index d973349d35d40..704a91f49b0c1 100644 --- a/drivers/gpu/drm/asahi/g17/calibration.rs +++ b/drivers/gpu/drm/asahi/g17/calibration.rs @@ -157,10 +157,10 @@ mod tests { fn chip() -> Calibration { Calibration { perf: PerfConfig { - states_a: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - states_b: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + states_a: map([0, 1, 2, 3, 4, 6, 8, 10, 12, 14, 15, 0, 0, 0, 0, 0]).unwrap(), + states_b: map([0, 1, 2, 3, 4, 6, 7, 9, 11, 13, 15, 0, 0, 0, 0, 0]).unwrap(), relative_a: [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100], - relative_b: [0, 0, 11, 22, 33, 44, 55, 66, 77, 88, 100], + relative_b: [0, 0, 7, 14, 21, 35, 50, 64, 78, 92, 100], sram_min_mv: 810, }, leakage: [0; 56], @@ -188,14 +188,21 @@ mod tests { assert!(chip.check_cores(&[0x3ff, 0x3ff]).is_ok()); assert!(chip.check_cores(&[0x3ff, 0x1ff]).is_err()); assert!(chip.check_cores(&[0xfffff]).is_err()); - opps[5].volt_uv[1] = 0; + // Both eleven-state maps must reach raw OPP 15, including its second voltage lane. + assert_eq!(chip.perf.states_a[PERF_STATES - 1], 15); + assert_eq!(chip.perf.states_b[PERF_STATES - 1], 15); + opps[15].volt_uv[1] = 0; assert!(chip.check_opps(&opps).is_err()); - opps[5].volt_uv[1] = 800_000; - opps[5].freq_hz = opps[4].freq_hz; + opps[15].volt_uv[1] = 800_000; + assert!(chip.check_opps(&opps).is_ok()); + opps[6].volt_uv[1] = 0; + assert!(chip.check_opps(&opps).is_err()); + opps[6].volt_uv[1] = 800_000; + opps[6].freq_hz = opps[4].freq_hz; assert!(chip.check_opps(&opps).is_err()); - opps[5].freq_hz = 501_000_000; + opps[6].freq_hz = 601_000_000; assert!(chip.check_opps(&opps).is_ok()); - opps[5].freq_hz = 511_000_000; + opps[6].freq_hz = 611_000_000; assert!(chip.check_opps(&opps).is_err()); } From 61aca8fb8212c8479a9e4830164ac2acf17c24e4 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 22:22:05 +1100 Subject: [PATCH 072/184] drm/asahi: cover the T6040 firmware queue floor in QID tests Run both dynamic compute growth and the full logical queue budget checks with the 25G83 floor of 32, alongside the existing floors. This checks render headroom and firmware-owned QID exclusion on the M4 Pro layout. Bring the host resource runner forward to the shared reserve_for helper and current render reserve, retaining its capacity and stale-ID controls. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/gpu/drm/asahi/g17/kick.rs | 4 ++-- tools/asahi/g17-resource-tests.py | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/asahi/g17/kick.rs b/drivers/gpu/drm/asahi/g17/kick.rs index a1f8f70f597d4..0f13c4e148563 100644 --- a/drivers/gpu/drm/asahi/g17/kick.rs +++ b/drivers/gpu/drm/asahi/g17/kick.rs @@ -309,7 +309,7 @@ mod accounting_tests { #[test] fn dynamic_compute_growth_preserves_render_headroom_above_firmware_floor() { - for floor in [0, 4] { + for floor in [0, 4, 32] { let mut ids = KBox::init(QueueIds::new(floor), GFP_KERNEL).unwrap(); let queues = 2; let mut owner = 1; @@ -328,7 +328,7 @@ mod accounting_tests { #[test] fn full_logical_queue_budget_keeps_compute_floor_and_excludes_firmware_ids() { - for floor in [0, 4] { + for floor in [0, 4, 32] { let mut ids = KBox::init(QueueIds::new(floor), GFP_KERNEL).unwrap(); let queues = queue_limit(floor) as usize; for owner in 1..=COMPUTE_FLOOR as u64 { diff --git a/tools/asahi/g17-resource-tests.py b/tools/asahi/g17-resource-tests.py index 5f87d2ea9e0f5..cf93704966c33 100644 --- a/tools/asahi/g17-resource-tests.py +++ b/tools/asahi/g17-resource-tests.py @@ -134,7 +134,7 @@ def item(s,start): return s[at:end] s=(source/'kick.rs').read_text() fw=(source/'fw/kick.rs').read_text() - constants=re.search(r'const QID_MAX: [^;]+;',fw).group(0)+'\n'+'\n'.join(re.search(r'const '+name+r': [^;]+;',s).group(0) for name in ['QID_COUNT','COMPUTE_RENDER_RESERVE']) + constants=re.search(r'const QID_MAX: [^;]+;',fw).group(0)+'\n'+'\n'.join(re.search(r'const '+name+r': [^;]+;',s).group(0) for name in ['QID_COUNT','RENDER_HEADROOM','FULL_RENDER_RESERVE']) header='''#![allow(dead_code, unused_macros)] type Result = std::result::Result; mod g17 { pub mod capacity { @@ -148,9 +148,9 @@ def item(s,start): #[derive(Clone,Copy,PartialEq,Eq,Debug)] enum DataMaster {Compute,Tiling,Fragment} #[derive(Clone,Copy,PartialEq,Eq,Debug)] struct Id {qid:u8,owner:u64,engine:DataMaster} #[derive(Clone,Copy)] struct Identity {id:Id,published:bool} - '''+constants+'\n'+item(s,'pub(crate) struct QueueIds')+'\nimpl QueueIds {\n'+''.join(item(s,'pub(crate) fn '+name+'(')+'\n' for name in ['reserve_initial_render','reserve','publish','can_cancel_unpublished','cancel_unpublished'])+'}\n' + '''+constants+'\n'+item(s,'const fn render_reserve(')+'\n'+item(s,'pub(crate) struct QueueIds')+'\nimpl QueueIds {\n'+''.join(item(s,'pub(crate) fn '+name+'(')+'\n' for name in ['reserve_initial_render','reserve','reserve_for','publish','can_cancel_unpublished','cancel_unpublished'])+''.join(item(s, sig)+'\n' for sig in ['fn render_ids(', 'fn compute_room('])+'}\n' test=''' - fn fresh()->QueueIds {QueueIds {entries:[None;QID_COUNT],initial_render:[None;2],publication:0,leased:0,floor:0}} + fn fresh()->QueueIds {QueueIds {entries:[None;QID_COUNT],initial_render:[None;2],leased:0,floor:0}} fn main(){ for floor in [0,32] { for render in [0,2,16,32,80,96,112,126].into_iter().filter(|render|*render<=QID_COUNT-floor) { @@ -158,7 +158,7 @@ def item(s,start): for i in 0..render {q.reserve(i as u64+1,if i%2==0 {DataMaster::Tiling}else{DataMaster::Fragment},None).unwrap();} let mut compute=0; while q.reserve(1000+compute,DataMaster::Compute,None).is_ok(){compute+=1;} - assert_eq!(compute as usize,QID_COUNT-floor-render.max(COMPUTE_RENDER_RESERVE-floor)); + assert_eq!(compute as usize,QID_COUNT-floor-render.max(FULL_RENDER_RESERVE-floor)); assert!(q.entries[..floor].iter().all(Option::is_none)); } } @@ -195,8 +195,8 @@ def item(s,start): assert_eq!(q.entries[0].unwrap().id,protected); // Replacement must not consume the protected render minimum. let mut q=fresh();q.reserve_initial_render().unwrap(); - for owner in 3..=COMPUTE_RENDER_RESERVE as u64 {q.reserve(owner,DataMaster::Tiling,None).unwrap();} - for owner in 1000..1000+(QID_COUNT-COMPUTE_RENDER_RESERVE) as u64 {q.reserve(owner,DataMaster::Compute,None).unwrap();} + for owner in 3..=FULL_RENDER_RESERVE as u64 {q.reserve(owner,DataMaster::Tiling,None).unwrap();} + for owner in 1000..1000+(QID_COUNT-FULL_RENDER_RESERVE) as u64 {q.reserve(owner,DataMaster::Compute,None).unwrap();} assert_eq!(q.leased,QID_COUNT);assert_eq!(q.reserve(2000,DataMaster::Compute,None),Err(ENOSPC)); assert!(q.initial_render.iter().all(Option::is_some)); // Ordinary free capacity leaves the initialization reservations untouched. From 4d0010e5f2271262c51b6430ea2ea964c0d3fd3e Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 22:24:18 +1100 Subject: [PATCH 073/184] tools/asahi: update pool and scheduler-release fixture boundaries Follow the split reusable-binding, raw publication and domain-wait helpers when checking scheduler-release ordering. Keep the timeout check at the shared wait helper. Update growth and pool-admission adapters for source loans, reserve_for and the off-lock compute slot allocation boundary. Assisted-by: LLM Signed-off-by: Ryan Murray --- tools/asahi/g17-growth-tests.py | 8 +++++--- tools/asahi/g17-owner-release-order-tests.py | 9 +++++---- tools/asahi/g17-pool-release-tests.py | 11 +++++++---- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/tools/asahi/g17-growth-tests.py b/tools/asahi/g17-growth-tests.py index 1236f166876af..2c46ccbf6359a 100644 --- a/tools/asahi/g17-growth-tests.py +++ b/tools/asahi/g17-growth-tests.py @@ -23,7 +23,7 @@ def fixture(): const EIO:i32=-5;const EBUSY:i32=-16;const EINVAL:i32=-22; fn now_ns()->u64{123} struct Context {live:Arc} -impl Context {fn new(live:&Arc)->Arc{live.fetch_add(1,Ordering::SeqCst);Arc::new(Self{live:live.clone()})}} +impl Context {fn note_compute_binding(&self,_:bool){} fn new(live:&Arc)->Arc{live.fetch_add(1,Ordering::SeqCst);Arc::new(Self{live:live.clone()})}} impl Drop for Context {fn drop(&mut self){self.live.fetch_sub(1,Ordering::SeqCst);}} struct Binding {context:Arc,pool_alias:Option,descriptors:Option} struct Previous {context:Arc,binding:Option,publication:Option<(u64,u64)>} @@ -31,17 +31,19 @@ def fixture(): struct Fifo {len:usize} struct Graph; impl Graph {fn set_owner(&mut self,_:&Context)->Result{Ok(())}} +struct ReaderHint; impl ReaderHint {fn has_readers(&self,count:&AtomicU32)->bool{count.load(Ordering::Acquire)!=0}} struct Queue { owner:Option,installed:bool,growing:bool,growth_blocks:usize,free_list_generation:u64, pool_phase:PoolPhase,release_sent_ns:Option,pool:Pool,binding:Option,previous:Option,graph:Graph, active:Fifo,replays:Fifo,quarantined:bool,released:bool,retirement_ready:bool, retired_by_teardown:bool,released_at:u64,exit:Option<()>,needs_fresh_backing:bool, + source_hint:ReaderHint,source_readers:AtomicU32, } fn idle(live:&Arc)->Queue{Queue{owner:Some(1),installed:true,growing:false,growth_blocks:0, free_list_generation:0,pool_phase:PoolPhase::Populated,release_sent_ns:None,pool:Pool{pages:Some(Pages(10)),fail:false}, binding:Some(Binding{context:Context::new(live),pool_alias:Some(1),descriptors:Some(2)}),previous:None,graph:Graph, active:Fifo{len:0},replays:Fifo{len:0},quarantined:false,released:false,retirement_ready:true, - retired_by_teardown:false,released_at:0,exit:None,needs_fresh_backing:false}} + retired_by_teardown:false,released_at:0,exit:None,needs_fresh_backing:false,source_hint:ReaderHint,source_readers:AtomicU32::new(0)}} impl Queue { ''' + item(source,'pub(crate) fn release_owner(') + item(source,'pub(crate) fn reusable(') + '\n}\n' methods = '\n'.join(item(source, sig) for sig in ['pub(crate) fn begin_growth(', 'pub(crate) fn finish_growth(', 'pub(crate) fn growth_token(', 'pub(crate) fn abort_growth(', 'pub(crate) fn observe_vacancy(']) @@ -156,7 +158,7 @@ def fixture(): def main(): source = fixture() runtime = (ROOT / 'runtime.rs').read_text() - assert 'growth = queue.growth_token();' in runtime and 'queue.abort_growth(token)?' in runtime + assert 'growth = queue.growth_token();' in runtime and 'queue.abort_growth(token)' in runtime cases = [('fixed', source), ('missing-abort-transfer', source.replace('self.growing = false;\n self.growth_blocks = 0;\n self.release_owner().map(Some)', 'self.release_owner().map(Some)')), ('ignore-consumption', source.replace('if !crate::g17::freelist::control_consumed(consumer, producer, cursor)? {', 'if false {')), diff --git a/tools/asahi/g17-owner-release-order-tests.py b/tools/asahi/g17-owner-release-order-tests.py index fd0e2c4a80936..ad17fa4048726 100755 --- a/tools/asahi/g17-owner-release-order-tests.py +++ b/tools/asahi/g17-owner-release-order-tests.py @@ -64,16 +64,17 @@ def check_guards(): ordered(detach, '.park();', 'parked.release_consumed(context.scheduler_va());') runtime = (ROOT / 'runtime.rs').read_text() # A compute queue does not change owner while its scheduler is released. - ordered(item(runtime, 'pub(super) fn rebind_compute('), '.filter(|queue| queue.reusable())', + ordered(item(runtime, 'fn bind_reusable('), '.filter(|queue| queue.reusable())', 'teardown.releasing(bound)', 'continue;', 'queue.bind_owner(owner, context.clone())?;') ordered(item(runtime, 'pub(super) fn bind_rearmed('), 'teardown.releasing(bound)', 'return Err(EAGAIN);', 'queue.bind_owner(') - ordered(item(runtime, 'fn publish_compute('), '.pending_switch_from()', 'queues.teardown.releasing(previous)', + ordered(item(runtime, 'fn publish_compute_raw('), '.pending_switch_from()', 'queues.teardown.releasing(previous)', 'return Err(EAGAIN);', 'queue.publish(') # A busy pair (for example one whose scheduler release is still being consumed) # kicks the event worker, which observes the release and parks the pair. - ordered(item(submit, 'fn wait_render_busy('), 'WORK_RING_WAIT_NS', 'return Err(ETIMEDOUT);', - 'self.shared.queue_events();', 'fsleep(') + ordered(item(submit, 'fn wait_render_busy('), 'WORK_RING_WAIT_NS', + 'self.shared.queue_events();', 'self.wait_domain(') + ordered(item(submit, 'fn wait_domain('), 'if remaining == 0', 'return Err(ETIMEDOUT);', 'domain.block(') for name, path in [('take_pair', 'runtime/render.rs'), ('take_render_observation', 'runtime/render/observe.rs')]: body = item((ROOT / path).read_text(), f'fn {name}(') ordered(body, 'entry.borrowed_switch = pair.switch_in_flight();', 'entry.pair.take()') diff --git a/tools/asahi/g17-pool-release-tests.py b/tools/asahi/g17-pool-release-tests.py index b7c715727acab..2ad4c6901cba4 100644 --- a/tools/asahi/g17-pool-release-tests.py +++ b/tools/asahi/g17-pool-release-tests.py @@ -31,7 +31,7 @@ def fixture(): predicate = '.any(|queue| queue.retirement_polling() || queue.pool_release_pending())' assert events.count(predicate) == 1 methods = '\n'.join(item(runtime, name) for name in [ - 'fn compute_admission_error(', 'pub(super) fn reserve_compute(']).replace('pub(super) ', '') + 'fn compute_admission_error(', 'pub(super) fn reserve_compute(']).replace('pub(super) ', '').replace('queue: ComputeSlot::new()?', 'queue: None') pending = item(compute, 'pub(crate) fn pool_release_pending(') + '\n' + \ item(compute, 'pub(crate) fn release_settling(') return r''' @@ -72,10 +72,12 @@ def fixture(): struct Id { qid: u8 } #[derive(Clone, Copy, Debug, PartialEq, Eq)] struct ComputeReservation { slot: usize, id: Id, owner: u64 } -struct ComputeEntry { reservation: ComputeReservation, queue: Option> } +type ComputeSlot = Option>; +struct ComputeEntry { reservation: ComputeReservation, queue: ComputeSlot } +struct Admission; impl Admission {fn queues(&self)->u32{4}} struct QueueIds { free: bool, fail: Option } impl QueueIds { - fn reserve(&mut self, _: u64, _: DataMaster, preferred: Option) -> Result { + fn reserve_for(&mut self, _: u64, _: DataMaster, preferred: Option, _: usize) -> Result { if let Some(error) = self.fail { return Err(error); } if !self.free { return Err(ENOSPC); } self.free = false; @@ -93,6 +95,7 @@ def fixture(): compute: Vec>, identities: Identities, qids: QueueIds, + admission: Admission, } impl Registry { METHODS @@ -109,7 +112,7 @@ def fixture(): })); } entries.push(None); - Registry { compute: entries, identities: Identities { next: 100 }, qids: QueueIds { free, fail: None } } + Registry { compute: entries, identities: Identities { next: 100 }, qids: QueueIds { free, fail: None }, admission: Admission } } fn main() { // Captured failure: four idle released queues, full physical namespace, From d24f314570992103cedad0fa3092ec78b199356c Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 22:28:05 +1100 Subject: [PATCH 074/184] tools/asahi: run compute deadline fixtures through release waits Adapt the controlled-clock tests to the progress-domain waits and bound compute fast-path fallback. Keep one admission budget across retries, growth and work-state waits, with cancellation and VM-status checks. Replace the obsolete single-check mutation with a disabled shared-deadline control: release waits now perform additional admission checks. Retain negative controls for renewed budgets, gate wake storms and alias lifetime. Assisted-by: LLM Signed-off-by: Ryan Murray --- tools/asahi/g17-compute-deadline-tests.py | 31 +++++++++++++------ .../g17-compute-growth-deadline-tests.py | 12 +++++-- 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/tools/asahi/g17-compute-deadline-tests.py b/tools/asahi/g17-compute-deadline-tests.py index eca8b73c5b12c..88c89f6857799 100644 --- a/tools/asahi/g17-compute-deadline-tests.py +++ b/tools/asahi/g17-compute-deadline-tests.py @@ -49,7 +49,7 @@ def fixture(): static MODE:AtomicUsize=AtomicUsize::new(0);static CANCEL:AtomicU64=AtomicU64::new(u64::MAX); static STATUS:AtomicU64=AtomicU64::new(u64::MAX);static CLOSED:AtomicBool=AtomicBool::new(false); fn now_ns()->u64{NOW.load(Ordering::SeqCst)} -fn advance(ns:u64){NOW.fetch_add(ns,Ordering::SeqCst);} +fn advance(ns:u64){NOW.fetch_add(ns,Ordering::SeqCst);assert!(now_ns()<=6_000_000_000,"admission budget exceeded");} mod kernel{pub mod time{ pub fn msecs_to_jiffies(ms:u32)->u32{ms} pub struct Delta(pub u64);impl Delta{pub fn from_millis(ms:u64)->Self{Self(ms*1_000_000)}pub fn from_micros(us:i64)->Self{Self(us as u64*1_000)}} @@ -76,20 +76,33 @@ def fixture(): impl Packet{fn check_dependencies(&self)->Result{Ok(())}fn cancel_requested(&self)->bool{now_ns()>=CANCEL.load(Ordering::SeqCst)}fn is_published(&self)->bool{false}} struct DeferredBatch;impl DeferredBatch{fn publication()->Result{Ok(Self)}fn finish(&mut self){}} struct Recovery;impl Recovery{fn pending(&self)->bool{MODE.load(Ordering::SeqCst)==1}} -struct Memory;impl Memory{fn recovery_state(_:&())->Result{Ok(0)}} +struct Init;impl Init{fn compute_vm_cached(&self,_:&())->bool{true}} +struct Context;impl Context{fn vm(&self)->&(){&()}} +struct Memory;impl Memory{fn recovery_state(_:&Init)->Result{Ok(0)}} struct Queues; impl Queues{ + fn compute_bound(&self,_:u64,_:&Context,_:usize)->Result{Ok(MODE.load(Ordering::SeqCst)!=0)} fn compute(&self,_:u64)->Option<()>{if MODE.load(Ordering::SeqCst)==0{None}else{Some(())}} fn render_prefix(&self,_:u64,_:&Arc)->Result>{Ok(vec![])} - fn release_compute_witness(&self,_:&(),_:u64,_:&mut DeferredBatch)->Result{Ok(MODE.load(Ordering::SeqCst)==2)} + fn release_compute_witness(&self,_:&Init,_:&(),_:u64,_:&mut DeferredBatch)->Result{Ok(MODE.load(Ordering::SeqCst)==2)} } -struct Firmware{recovery:Recovery,init:(),queues:Queues} +struct Firmware{recovery:Recovery,init:Init,queues:Queues,port:()} impl Firmware{fn replay_compute(&mut self,_:&mut DeferredBatch)->Result{Ok(())}fn publish_compute(&mut self,_:u64,_:Arc,_:&[()],_:&mut DeferredBatch)->Result{Err(EAGAIN)}} struct State(Mutex>>);impl State{fn lock(&self)->MutexGuard<'_,Option>>{self.0.lock().unwrap()}} -struct Shared{preparations:Arc,state:State,crashed:AtomicBool,active_cores:u32}impl Shared{fn queue_events(&self){}} -struct Backend{shared:Shared,owner:u64} +struct Available;impl Available{fn observe(&self)->u64{now_ns()}} +struct Shared{preparations:Arc,state:State,crashed:AtomicBool,active_cores:u32,progress:Available}impl Shared{fn queue_events(&self){}} +struct ComputeDemand;impl ComputeDemand{fn new(_:&Shared)->Self{Self}} +struct Backend{shared:Shared,owner:u64,context:Context} fn required_blocks(_:u32,_:u16)->Result{Ok(1)} impl Backend{ + fn compute_direct(&self,_:&Arc,_:usize,_:&mut DeferredBatch)->Option{None} + fn sources_published(&self,_:&Arc)->Result{Ok(true)} + fn remember_direct(&self,_:&Queues){} + fn wait_release(&self,_:&Available,_:u64,admission:&Admission<'_>)->Result{ + let _=admission.slice_ms()?; + advance(1_000_000); + admission.check() + } fn admission_open(&self)->Result{Ok(())} // The real ensure_compute forwards the original context into this allocation // boundary. Each success loses its binding before the publication mutex. @@ -99,7 +112,7 @@ def fixture(): ''' + ensure + '\n' + packet + r''' } fn main(){ - let b=Backend{owner:1,shared:Shared{active_cores:1,preparations:Arc::new(Gate{wait:Wait,changed:Changed}),state:State(Mutex::new(Some(Box::new(Firmware{recovery:Recovery,init:(),queues:Queues})))),crashed:AtomicBool::new(false)}}; + let b=Backend{owner:1,context:Context,shared:Shared{active_cores:1,progress:Available,preparations:Arc::new(Gate{wait:Wait,changed:Changed}),state:State(Mutex::new(Some(Box::new(Firmware{recovery:Recovery,init:Init,queues:Queues,port:()})))),crashed:AtomicBool::new(false)}}; let p=Arc::new(Packet{command:Validated::Compute{scratch:Scratch},completion:Completion}); for mode in 0..4 { MODE.store(mode,Ordering::SeqCst);NOW.store(0,Ordering::SeqCst);CALLS.store(0,Ordering::SeqCst); @@ -144,8 +157,8 @@ def main(): source = fixture() mutants = [ ('fixed', source, ''), - ('renew-on-binding-loss', source.replace('let admission = Admission::new(deadline, &check);\n loop {', 'loop {\n let admission = Admission::new(now_ns().saturating_add(WORK_RING_WAIT_NS), &check);'), 'one admission budget'), - ('omit-nested-check', source.replace(' admission.check()?;\n let pending', ' let pending'), 'nested unavailable-capacity'), + ('renew-on-binding-loss', source.replace('let admission = Admission::new(deadline, &check);\n let progress = &self.shared.progress;\n loop {', 'let progress = &self.shared.progress;\n loop {\n let admission = Admission::new(now_ns().saturating_add(WORK_RING_WAIT_NS), &check);'), 'admission budget exceeded'), + ('ignore-deadline', source.replace('if now_ns() >= self.deadline', 'if false'), 'admission budget exceeded'), ('omit-gate-check', source.replace(' admission.check()?;\n match self.try_enter()', ' match self.try_enter()').replace('admission.slice_ms()?', '10'), 'gate wake storm'), ] # Bind the old unbounded mutant with the review's eventual cancellation. diff --git a/tools/asahi/g17-compute-growth-deadline-tests.py b/tools/asahi/g17-compute-growth-deadline-tests.py index 1e1e715de8377..3c652a96df759 100644 --- a/tools/asahi/g17-compute-growth-deadline-tests.py +++ b/tools/asahi/g17-compute-growth-deadline-tests.py @@ -50,7 +50,8 @@ def fixture(): impl Lease{fn is_current(&self)->bool{true}} struct State(Mutex>>); impl State{fn lock(&self)->MutexGuard<'_,Option>>{self.0.lock().unwrap()}} -struct Shared{preparations:Gate,state:State,crashed:AtomicBool} +struct Available;impl Available{fn observe(&self)->u64{now_ns()}fn notify(&self){}} +struct Shared{preparations:Gate,state:State,crashed:AtomicBool,progress:Available,channels:Available} impl Shared{fn drm(&self)->Result<()>{Ok(())}fn queue_events(&self){}} struct Context;impl Context{fn vm(&self)->&(){&()}} struct Backend{shared:Shared,context:Context,owner:u64} @@ -85,12 +86,17 @@ def fixture(): impl Firmware{fn scan_compute(&mut self,_:Option<()>,_:&mut DeferredBatch)->Result{Ok(())}} struct DeferredBatch;impl DeferredBatch{fn publication()->Result{Ok(Self)}fn finish(&mut self){}} impl Backend{ + fn wait_release(&self,_:&Available,_:u64,admission:&Admission<'_>)->Result{ + let _=admission.slice_ms()?; + kernel::time::delay::fsleep(kernel::time::Delta::from_millis(1)); + admission.check() + } ''' + methods + r''' } fn main(){ for fresh in [false,true]{for cancel in [false,true]{ NOW.store(0,Ordering::SeqCst);ABORTED.store(false,Ordering::SeqCst);RESERVED.store(true,Ordering::SeqCst); - let b=Backend{owner:1,context:Context,shared:Shared{preparations:Gate,crashed:AtomicBool::new(false),state:State(Mutex::new(Some(Box::new(Firmware{uat:(),init:(),primary:Primary,queues:Registry{compute:vec![Some(Entry{reservation:ComputeReservation,queue:Some(Box::new(Queue{fresh}))})]}}))))}}; + let b=Backend{owner:1,context:Context,shared:Shared{preparations:Gate,progress:Available,channels:Available,crashed:AtomicBool::new(false),state:State(Mutex::new(Some(Box::new(Firmware{uat:(),init:(),primary:Primary,queues:Registry{compute:vec![Some(Entry{reservation:ComputeReservation,queue:Some(Box::new(Queue{fresh}))})]}}))))}}; let check=||{if cancel&&now_ns()>=10_000_000{Err(ECANCELED)}else{Ok(())}}; let admission=Admission::new(20_000_000,&check); assert_eq!(b.grow_compute(42,&admission),Err(if cancel{ECANCELED}else{EBUSY}),"growth must propagate cancel/deadline"); @@ -106,7 +112,7 @@ def fixture(): def main(): source = fixture() mutants = [('fixed', source, ''), - ('omit-growth-check', source.replace(' admission.check()?;\n let firmware', ' let firmware'), 'growth admission exceeded'), + ('ignore-shared-deadline', source.replace('if now_ns() >= self.deadline', 'if false').replace('if remaining == 0 { return Err(EBUSY); }', ''), 'growth admission exceeded'), ('keep-growth-alias', source.replace(' drop(alias.take());', ''), 'growth aliases must precede')] with tempfile.TemporaryDirectory(prefix='g17-compute-growth-') as tmp: for name, code, expected in mutants: From 392b394d4dbb43e3d7f29bad70263e0f93ab94b9 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 22:29:23 +1100 Subject: [PATCH 075/184] tools/asahi: test DART range flush dispatch and error continuation Update the host DART adapter for the firmware mirror/handoff dispatch, DCP domain flush and managed-stream quarantine. Verify that a failed page-table publication still issues invalidation, its first error wins, and a power failure does not skip a second reachable DART. Keep the device-address offset, locked/unlocked and legacy controls. Assisted-by: LLM Signed-off-by: Ryan Murray --- tools/asahi/apple-dart-range-tests.py | 45 +++++++++++++++++++++------ 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/tools/asahi/apple-dart-range-tests.py b/tools/asahi/apple-dart-range-tests.py index 6fa006381918c..5d311fef0813d 100644 --- a/tools/asahi/apple-dart-range-tests.py +++ b/tools/asahi/apple-dart-range-tests.py @@ -31,6 +31,7 @@ def main(): #include #include typedef uint64_t u64; +typedef uint32_t u32; #define BITS_TO_LONGS(n) 1 #define atomic_long_read(x) (*(x)) #define bitmap_empty(x, n) (!(x)[0]) @@ -42,31 +43,36 @@ def main(): struct apple_dart; struct apple_dart_stream_map { struct apple_dart *dart; unsigned long sidmap[1]; }; struct apple_dart_hw { int (*invalidate_tlb)(struct apple_dart_stream_map *); }; -struct apple_dart { int num_streams, locked, version; void *dev; struct apple_dart_hw *hw; }; +struct apple_dart { int num_streams, locked, version; bool fw_mirror, fw_handoff, managed_sids; void *dev; struct apple_dart_hw *hw; }; struct apple_dart_atomic_stream_map { struct apple_dart *dart; unsigned long sidmap[1]; }; struct apple_dart_domain { - void *pgtbl_ops; struct apple_dart_atomic_stream_map streams[2]; int count; + void *pgtbl_ops; bool dcp; struct apple_dart_atomic_stream_map streams[2]; int count; u64 dma_offset, mask; }; #define for_each_stream_map(i,d,m) for ((i)=0; (i)<(d)->count && ((m)=&(d)->streams[i],1); (i)++) -static int powered, power_error, sync_error, range_error, syncs, ranges, fulls; +static int powered, power_error, sync_error, range_error, syncs, ranges, fulls, firmware_flushes, quarantines, dcp_flushes; static u64 seen_first, seen_last; -static int pm_runtime_resume_and_get(void *dev) { if (power_error) return power_error; powered++; return 0; } +static int pm_runtime_resume_and_get(void *dev) { if (power_error && !dev) return power_error; powered++; return 0; } static void pm_runtime_put(void *dev) { assert(powered>0); powered--; } -static int apple_dart_hw_sync_locked(struct io_pgtable_cfg *cfg, struct apple_dart_stream_map *stream) { - syncs++; return sync_error; +static int apple_dart_hw_sync_locked(struct io_pgtable_cfg *cfg, struct apple_dart_stream_map *stream, bool invalidate) { + assert(invalidate); syncs++; return sync_error; } static int apple_dart_t8110_hw_tlb_command_range(struct apple_dart_stream_map *stream, int command, bool range, u64 first, u64 last) { assert(command==1 && range); ranges++; seen_first=first; seen_last=last; return range_error; } +static int apple_dart_dcp_domain_flush(struct apple_dart_domain *domain) { assert(domain->dcp); dcp_flushes++; return -EAGAIN; } +static void apple_dart_managed_quarantine(struct apple_dart *dart) { assert(dart->managed_sids); quarantines++; } +static int apple_dart_fw_invalidate(struct apple_dart_stream_map *stream, u64 first, u64 last) { + firmware_flushes++; seen_first=first; seen_last=last; return range_error; +} static int full(struct apple_dart_stream_map *stream) { fulls++; return 0; } ''' source += production source += r''' static void reset(void) { assert(powered==0); power_error=sync_error=range_error=syncs=ranges=fulls=0; - seen_first=seen_last=0; + seen_first=seen_last=0; firmware_flushes=quarantines=dcp_flushes=0; } int main(void) { struct apple_dart_hw hw={.invalidate_tlb=full}; @@ -88,14 +94,35 @@ def main(): assert(fulls==1 && ranges==0 && syncs==0); reset(); dart.locked=1; sync_error=-EBUSY; assert(apple_dart_domain_flush_tlb_range(&domain,true,0x4000,0x7fff)==-EBUSY); - assert(ranges==0 && fulls==0 && powered==0); + assert(ranges==1 && fulls==0 && powered==0); reset(); power_error=-EHOSTDOWN; assert(apple_dart_domain_flush_tlb_range(&domain,true,0x4000,0x7fff)==-EHOSTDOWN); assert(syncs==0 && ranges==0 && fulls==0 && powered==0); reset(); range_error=-ETIMEDOUT; assert(apple_dart_domain_flush_tlb_range(&domain,true,0x4000,0x7fff)==-ETIMEDOUT); assert(ranges==1 && powered==0); - reset(); domain.streams[0].sidmap[0]=0; + reset(); dart.managed_sids=true; range_error=-ETIMEDOUT; + assert(apple_dart_domain_flush_tlb_range(&domain,true,0x4000,0x7fff)==-ETIMEDOUT); + assert(ranges==1 && quarantines==1 && powered==0); + reset(); sync_error=-EBUSY; range_error=-ETIMEDOUT; + assert(apple_dart_domain_flush_tlb_range(&domain,true,0x4000,0x7fff)==-EBUSY); + assert(ranges==1 && quarantines==1 && powered==0); + reset(); dart.fw_mirror=true; + assert(!apple_dart_domain_flush_tlb_range(&domain,true,0x4000,0x7fff)); + assert(firmware_flushes==1 && ranges==0 && syncs==1); + reset(); dart.fw_mirror=false; dart.fw_handoff=true; + assert(!apple_dart_domain_flush_tlb_range(&domain,true,0x4000,0x7fff)); + assert(firmware_flushes==1 && ranges==0); + reset(); dart.fw_handoff=false; domain.dcp=true; + assert(apple_dart_domain_flush_tlb_range(&domain,true,0x4000,0x7fff)==-EAGAIN); + assert(dcp_flushes==1 && ranges==0 && syncs==0); + reset(); domain.dcp=false; + struct apple_dart second=dart; second.dev=(void *)1; + domain.streams[1].dart=&second; domain.streams[1].sidmap[0]=1; domain.count=2; + power_error=-EHOSTDOWN; + assert(apple_dart_domain_flush_tlb_range(&domain,true,0x4000,0x7fff)==-EHOSTDOWN); + assert(ranges==1 && syncs==1 && powered==0); + reset(); domain.count=1; domain.streams[0].sidmap[0]=0; assert(!apple_dart_domain_flush_tlb_range(&domain,true,0x4000,0x7fff)); assert(syncs==0 && ranges==0 && fulls==0 && powered==0); puts("PASS: device DVA, low window, legacy/unlocked fallback, sync/power/command errors and empty stream"); From bf6b0ec518342e56157e2ada7e8053d2b15ff98a Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 22:29:23 +1100 Subject: [PATCH 076/184] docs: record T6040 GPU parity and qualification gaps Trace calibration, firmware admission, queueing, power, recovery and memory paths against the shared Neo backend. Separate changes already present through #463 and #423 from firmware ABI restrictions and missing hardware measurements. Record the public-source boundary for M5 Pro. Assisted-by: LLM Signed-off-by: Ryan Murray --- GAPS.md | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 GAPS.md diff --git a/GAPS.md b/GAPS.md new file mode 100644 index 0000000000000..1f04b7ea5cde5 --- /dev/null +++ b/GAPS.md @@ -0,0 +1,55 @@ +# T6040 GPU parity audit + +Audit baseline: `integration/aurora` at +`9cbdd0734d9e`, with GPU integration #463 (`a0a9485847f8`), the +r5 display series (`905dff774561`), camera #462 (`2114ed0c6245`), and the +two r6 display patches. The reconstructed r6 branch is +`wip/j616s-display-r6` at `af0a664046fb`. + +This series repairs regression coverage. It does not change clock, voltage, +firmware policy, feature advertisement, or the submission algorithm. The major +T6040 calibration and image-admission changes requested by this audit are +already in the baseline. Software parity does not establish benchmark or CTS +parity. There are no J616s results for this series yet. + +## Gap table + +| Gap | Neo behavior | T6040 before this series | After this series | Evidence / remaining gate | +| --- | --- | --- | --- | --- | +| Per-chip leakage and power ceiling | T8140 has its own platform data | #463 requires a versioned DT handoff, finite positive leakage, a power ceiling and the measured core mask; no single-unit fallback | Same runtime behavior | `g17/calibration.rs`, `g17/initdata/v25g83.rs`; #463 commits `a8fb1380b414`, `53877eaebd4b`, `415fd2e6ee2d` | +| Raw OPP count versus logical states | Firmware consumes 11 logical states | 16 raw GPU OPPs; 11 selectors in each A/B map; both end at raw OPP 15; unused handoff slots must be zero | KUnit now uses valid complete maps and checks the final state's second voltage lane | `g17/calibration.rs`; a count of 15 in an ADT property is not evidence for expanding this firmware layout | +| Full clock eligibility | Platform OPPs feed firmware performance tables | DT supplies both cluster voltages and GPU/CS/AFR frequencies; maps and relative-B are checked against the GPU OPP table | Same runtime behavior | `g17/initdata/v25g83.rs`, `g17/fw/initdata/v25g83.rs`; confirm sustained clocks and limiting reasons on hardware | +| DVFS ramp / effort | Full effort on publication, cleared after 16 ms of continuous idle | Same `Firmware::note_submission`, direct-port accounting and race-checked idle clear | Same runtime behavior | `g17/mod.rs`, `g17/port.rs`, `g17/power.rs`; opaque firmware power words are not a documented ramp control | +| Idle / off policy | Shared host hold and firmware device-control path | Same host implementation; source documents a 25G83 effective 2 ms off delay and approximately 1 ms wake cost | Same runtime behavior; default hold unchanged | `g17/power.rs`; measure actual idle/wake latency before altering policy | +| Fast-die / PMP | Platform-specific firmware power graph | 25G83 AON table and separate power-state target/release (101/96 C) are already populated; DT enables the GFX suppliers | Same runtime behavior | `g17/fw/initdata/v25g83.rs::POWER_STATE_WORDS`, `g17/initdata/v25g83.rs`, J616s DT; verify clocks with PMP running | +| Periodic preemption | Context-switch timer mask is zero | Same zero mask; two-cluster operand-state allocation differs by hardware layout | Same runtime behavior | `g17/fw/initdata.rs::CONTEXT_SWITCH_TIMER_MASK`, `g17/fw/compute.rs::OperandState`; no evidence supports changing the mask | +| High-priority queues | HIGH / MEDIUM / LOW firmware scheduling profiles | Same scheduling-policy selector and priority work rings | Same runtime behavior | `g17/mod.rs::scheduling_policy`, `g17/fw/channels.rs`, `g17/qos.rs` | +| Timestamp publication | Retains one CPU mapping for each firmware timestamp buffer | Same shared timestamp buffer and completion publication | Host timestamp suite again compiles and executes the production retained-mapping writer | `timestamp.rs`, `mmu.rs::TimestampVmap`, `tools/testing/selftests/drivers/gpu/asahi-m4/test-timestamps.py` | +| Statistics | Drains firmware statistics; no `agx_stats` snapshot | Same; `stats_snapshot()` returns `None` on both | Same runtime behavior | `g17/channel.rs`, `g17/runtime/events.rs`, `g17/mod.rs`; statistics decoding/export remains a shared limitation | +| Alternate loaded image | T8140 uses fixed firmware selection | Both exact T6040 normalized TEXT digests are admitted in one `Build.images` slice | Same runtime behavior | #453 (`26f5eb68dbce`), #463 (`804bba86ca55`), `hw/t6040.rs`, `g17/fw/variant.rs`, `fwid.rs`; unknown images remain refused | +| Submission/completion improvements | #423 shared G17 runtime | #423 is already an ancestor of the baseline; direct compute/render publication, admission domains and completion collection are shared | Same runtime behavior | #423; `g17/runtime.rs`, `g17/runtime/render/direct.rs`, `g17/port.rs`, `g17/completion.rs` | +| G13/G14 `submit.empty` regressions | Neo uses the G17 runtime | G16S uses that same G17 runtime, not the maintained G13/G14 workqueue | Same runtime behavior | `driver.rs`, `file.rs`, `g17/queue/mod.rs`; user-space empty submits may never reach this ioctl; trace and benchmark the paired Mesa stack | +| G13/G14 idle wake regressions | Firmware-specific cost plus shared host overhead | G16S shares common ioctl/GEM/sync infrastructure, but has different firmware, power handling and queue backend | Same runtime behavior | `driver.rs`, `g17/power.rs`; G13/G14 benchmark ratios cannot be transferred to G16S | +| QID capacity | Firmware floor 0; reserve protects dedicated render pairs | 25G83 reserves QIDs 0..31; public queue limit is reduced accordingly | KUnit checks floor 32 in dynamic growth and full logical queue budget tests; host resource runner follows current reserve helper | `g17/kick.rs`, `g17/runtime.rs`, `tools/asahi/g17-resource-tests.py` | +| Compute ownership / retained mappings | Recovery and retirement witnesses protect mapping lifetime | Same production queue ownership, source loans, growth rollback and deferred release | Restored host fixtures and negative controls; source loans explicitly prevent premature reuse/sharing/rearming | `tools/asahi/g17-capacity-fault-tests.py`, constructor/growth/pool/release-order fixtures | +| Restart and fault classification | Shared recovery handshake plus validated T8140 per-slot keys | Shared handshake uses 25G83 status offsets; `recovery_slot_keys=None` preserves firmware blame when keys are unknown | Same runtime behavior | `hw/t6040.rs`, `g17/initdata.rs`, `g17/recovery/attribution.rs`; successful 25G83 fault/restart qualification is still required | +| DART / UAT | Shared mapping ownership and checked page-table borrowing | Shared MMU, with T6040 reserved tables, PoC cleaning, per-page TLBI and native GPU permission quirks | Same runtime behavior | `hw/t6040.rs`, `mmu.rs`, `pgtable.rs`; do not remove coherency or handoff requirements for a benchmark | +| Memory accounting | Creator-attributed committed/resident GEM accounting and capacity admission | Same `memacct::Client`, `File::memory_stats` and shared capacity machinery | Same runtime behavior | `file.rs`, `gem.rs`, `memacct.rs`, `g17/capacity.rs`; verify multi-client high-water and post-close behavior on hardware | +| MCache / few-primitives / ray traversal | Enabled by the 26A428 variant | Disabled by 25G83 variant; source records hangs for few-primitives and stalled ray traversal, while MCache is unqualified | Same runtime behavior | `g17/fw/variant.rs::V25G83`; enabling these requires independent ABI evidence and hardware tests | +| M5 Pro comparison | No M5 Pro hardware configuration exists in this public baseline | No T605x/G17S KMD path to compare | No claim of M5 parity | Pinned public tree contains T6040 and T8140; a licensed public M5 Pro reference is required | +| CTS and Linux/macOS performance ratio | Hardware results belong to their exact kernel/Mesa pair | No result for this candidate | Pending hardware agent | Run gpubench with matching suite, configuration and conditions; review CTS failures and kernel faults before claiming parity | + +## Source and validation boundaries + +The implementation references are the licensed public Aurora/Asahi kernel and +loader history. Existing Gravity Linux-derived layouts and attribution remain +unchanged. The public Gravity Linux `gravity-m4` reference inspected at +`4fc326f6c25ccceb7515e2635c22e01fde5d3bac` was checked in `g16.rs`, +`g16_runtime.rs` and `g16_power.rs`. Those files do not supply validated +T6040 recovery slot keys; that G16 runtime explicitly leaves compute +non-preemptible. No private implementation, firmware bytes, voltage table or +lab record is included here. + +The build handoff must identify the exact tested source and loader, verify boot +payload checksums, retain r6g rollback, and distinguish CPU/QEMU tests from GPU +execution. Missing J616s measurements are not passing results. From 3477ad38626619e213cdd9ad968e84a963c7cbdc Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 22:31:11 +1100 Subject: [PATCH 077/184] tools/asahi: match the module parameter value API in preemption tests The parameter accessor now returns a copied integer. Update the host adapter so the production priority selection compiles with the same API. This retains the existing firmware/version and explicit-policy controls. Assisted-by: LLM Signed-off-by: Ryan Murray --- tools/asahi/compute-preempt-tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/asahi/compute-preempt-tests.py b/tools/asahi/compute-preempt-tests.py index a13f103341b95..fdab1e9e7406b 100755 --- a/tools/asahi/compute-preempt-tests.py +++ b/tools/asahi/compute-preempt-tests.py @@ -48,7 +48,7 @@ def policy(text): pub static compute_preempt: Parameter = Parameter; pub static mut PARAM: i32 = -1; pub struct Parameter; - impl Parameter { pub fn value(&self) -> &i32 { unsafe { &*std::ptr::addr_of!(PARAM) } } } + impl Parameter { pub fn value(&self) -> i32 { unsafe { *std::ptr::addr_of!(PARAM) } } } } #[derive(Clone, Copy, Debug, PartialEq)] struct U64(u64); #[derive(Clone, Copy, Debug, PartialEq)] enum PipeType { Vertex, Fragment, Compute } From 2b2204fac3d5adac19c4a350e8f52a6254112aeb Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 22:33:34 +1100 Subject: [PATCH 078/184] pmdomain: apple: describe the PMP report test module Add the missing module description so the shared Fedora build no longer reports a modpost warning for pmp-report-test. Test behavior is unchanged. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/pmdomain/apple/pmp-report-test.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/pmdomain/apple/pmp-report-test.c b/drivers/pmdomain/apple/pmp-report-test.c index e04a91d339f23..d99ece1ac4b6f 100644 --- a/drivers/pmdomain/apple/pmp-report-test.c +++ b/drivers/pmdomain/apple/pmp-report-test.c @@ -276,4 +276,5 @@ static struct kunit_suite apple_pmp_report_suite = { }; kunit_test_suite(apple_pmp_report_suite); +MODULE_DESCRIPTION("KUnit tests for Apple PMP report validation"); MODULE_LICENSE("Dual MIT/GPL"); From ab9f25d29c0a542f8180a4df09af804a692998b8 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 22:44:57 +1100 Subject: [PATCH 079/184] drm/asahi: match G17 test helpers to built-in KUnit The Rust kunit_tests macro emits its test modules only for KUNIT=y. With the shared Fedora config's KUNIT=m, the helper constructors and compute-floor constant were still compiled and failed WERROR as dead code. Use the macro's exact condition for these four test-only helpers. Keep both the shipping config and warning policy unchanged. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/gpu/drm/asahi/g17/kick.rs | 2 +- drivers/gpu/drm/asahi/g17/preparation.rs | 2 +- drivers/gpu/drm/asahi/g17/queue/admission.rs | 2 +- drivers/gpu/drm/asahi/g17/runtime.rs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/asahi/g17/kick.rs b/drivers/gpu/drm/asahi/g17/kick.rs index 0f13c4e148563..f694dad86bbf0 100644 --- a/drivers/gpu/drm/asahi/g17/kick.rs +++ b/drivers/gpu/drm/asahi/g17/kick.rs @@ -43,7 +43,7 @@ const RENDER_HEADROOM: usize = 24; /// The render reserve with every execution context open. const FULL_RENDER_RESERVE: usize = render_reserve(crate::mmu::MAX_EXECUTION_CONTEXTS as usize); /// Compute channels that stay reachable with every execution context open. -#[cfg(CONFIG_KUNIT)] +#[cfg(CONFIG_KUNIT = "y")] pub(super) const COMPUTE_FLOOR: usize = QID_COUNT - FULL_RENDER_RESERVE; /// Upper bound on distinct physical compute channels named by one kick. pub(super) const COMPUTE_CHANNELS: usize = QID_COUNT; diff --git a/drivers/gpu/drm/asahi/g17/preparation.rs b/drivers/gpu/drm/asahi/g17/preparation.rs index eb2366fa3451b..2aa3213115076 100644 --- a/drivers/gpu/drm/asahi/g17/preparation.rs +++ b/drivers/gpu/drm/asahi/g17/preparation.rs @@ -60,7 +60,7 @@ pub(super) struct Gate { } impl Gate { - #[cfg(CONFIG_KUNIT)] + #[cfg(CONFIG_KUNIT = "y")] pub(super) fn new() -> Result> { Self::with_waiters(None) } diff --git a/drivers/gpu/drm/asahi/g17/queue/admission.rs b/drivers/gpu/drm/asahi/g17/queue/admission.rs index e1e7552c243f2..5d770ca1f64b0 100644 --- a/drivers/gpu/drm/asahi/g17/queue/admission.rs +++ b/drivers/gpu/drm/asahi/g17/queue/admission.rs @@ -406,7 +406,7 @@ pub(crate) struct Pool { renders: Option>, } impl Pool { - #[cfg(CONFIG_KUNIT)] + #[cfg(CONFIG_KUNIT = "y")] pub(crate) fn new(max_queues: u32) -> Result> { Self::with_waiters(max_queues, None, None) } diff --git a/drivers/gpu/drm/asahi/g17/runtime.rs b/drivers/gpu/drm/asahi/g17/runtime.rs index e04295d576606..9fe17db8a2809 100644 --- a/drivers/gpu/drm/asahi/g17/runtime.rs +++ b/drivers/gpu/drm/asahi/g17/runtime.rs @@ -186,7 +186,7 @@ pub(super) struct Registry { impl Registry { /// Initialize inside the firmware allocation instead of staging the whole /// registry on the probe stack. No additional allocation is required. - #[cfg(CONFIG_KUNIT)] + #[cfg(CONFIG_KUNIT = "y")] pub(super) fn new( clusters: u32, deflake: crate::g17::fw::render::Deflake, From 8987b717797feb652da658d4207dfd8b91cb867a Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 22:45:32 +1100 Subject: [PATCH 080/184] docs: record the shared modular KUnit build gap Record why the shipping KUNIT=m config exposed unused G17 test helpers and why matching Rust's built-in KUnit guard fixes it without changing hardware policy or weakening warning checks. Assisted-by: LLM Signed-off-by: Ryan Murray --- GAPS.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/GAPS.md b/GAPS.md index 1f04b7ea5cde5..b41325bdeaf26 100644 --- a/GAPS.md +++ b/GAPS.md @@ -6,7 +6,7 @@ r5 display series (`905dff774561`), camera #462 (`2114ed0c6245`), and the two r6 display patches. The reconstructed r6 branch is `wip/j616s-display-r6` at `af0a664046fb`. -This series repairs regression coverage. It does not change clock, voltage, +This series repairs regression coverage and a shared KUNIT=m WERROR build defect. It does not change clock, voltage, firmware policy, feature advertisement, or the submission algorithm. The major T6040 calibration and image-admission changes requested by this audit are already in the baseline. Software parity does not establish benchmark or CTS @@ -31,6 +31,7 @@ parity. There are no J616s results for this series yet. | G13/G14 `submit.empty` regressions | Neo uses the G17 runtime | G16S uses that same G17 runtime, not the maintained G13/G14 workqueue | Same runtime behavior | `driver.rs`, `file.rs`, `g17/queue/mod.rs`; user-space empty submits may never reach this ioctl; trace and benchmark the paired Mesa stack | | G13/G14 idle wake regressions | Firmware-specific cost plus shared host overhead | G16S shares common ioctl/GEM/sync infrastructure, but has different firmware, power handling and queue backend | Same runtime behavior | `driver.rs`, `g17/power.rs`; G13/G14 benchmark ratios cannot be transferred to G16S | | QID capacity | Firmware floor 0; reserve protects dedicated render pairs | 25G83 reserves QIDs 0..31; public queue limit is reduced accordingly | KUnit checks floor 32 in dynamic growth and full logical queue budget tests; host resource runner follows current reserve helper | `g17/kick.rs`, `g17/runtime.rs`, `tools/asahi/g17-resource-tests.py` | +| KUnit / WERROR config matrix | Shared test modules require KUNIT=y | Four G17 test helpers used the broader CONFIG_KUNIT guard and failed WERROR with the shipping KUNIT=m config | Helpers now use CONFIG_KUNIT = "y", matching the Rust macro; config and warning policy unchanged | `rust/macros/kunit.rs`, `g17/kick.rs`, `g17/preparation.rs`, `g17/queue/admission.rs`, `g17/runtime.rs`; verify both KUNIT=m release and KUNIT=y QEMU builds | | Compute ownership / retained mappings | Recovery and retirement witnesses protect mapping lifetime | Same production queue ownership, source loans, growth rollback and deferred release | Restored host fixtures and negative controls; source loans explicitly prevent premature reuse/sharing/rearming | `tools/asahi/g17-capacity-fault-tests.py`, constructor/growth/pool/release-order fixtures | | Restart and fault classification | Shared recovery handshake plus validated T8140 per-slot keys | Shared handshake uses 25G83 status offsets; `recovery_slot_keys=None` preserves firmware blame when keys are unknown | Same runtime behavior | `hw/t6040.rs`, `g17/initdata.rs`, `g17/recovery/attribution.rs`; successful 25G83 fault/restart qualification is still required | | DART / UAT | Shared mapping ownership and checked page-table borrowing | Shared MMU, with T6040 reserved tables, PoC cleaning, per-page TLBI and native GPU permission quirks | Same runtime behavior | `hw/t6040.rs`, `mmu.rs`, `pgtable.rs`; do not remove coherency or handoff requirements for a benchmark | From e5b0147408cfc6243b7ca2d7d0fe8e07efff55ab Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 22:50:09 +1100 Subject: [PATCH 081/184] docs: distinguish T6040 firmware registration and pointer layouts Record the startup, lead-command, pointer-block and topology differences alongside the shared submission path. MMIO pair registration on 25G83 and other fused core counts remain unqualified rather than inferred from Neo's different hardware and firmware interface. Assisted-by: LLM Signed-off-by: Ryan Murray --- GAPS.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/GAPS.md b/GAPS.md index b41325bdeaf26..2e0fd834ce28a 100644 --- a/GAPS.md +++ b/GAPS.md @@ -26,6 +26,9 @@ parity. There are no J616s results for this series yet. | High-priority queues | HIGH / MEDIUM / LOW firmware scheduling profiles | Same scheduling-policy selector and priority work rings | Same runtime behavior | `g17/mod.rs::scheduling_policy`, `g17/fw/channels.rs`, `g17/qos.rs` | | Timestamp publication | Retains one CPU mapping for each firmware timestamp buffer | Same shared timestamp buffer and completion publication | Host timestamp suite again compiles and executes the production retained-mapping writer | `timestamp.rs`, `mmu.rs::TimestampVmap`, `tools/testing/selftests/drivers/gpu/asahi-m4/test-timestamps.py` | | Statistics | Drains firmware statistics; no `agx_stats` snapshot | Same; `stats_snapshot()` returns `None` on both | Same runtime behavior | `g17/channel.rs`, `g17/runtime/events.rs`, `g17/mod.rs`; statistics decoding/export remains a shared limitation | +| Firmware startup / registration | Two instances, message acknowledgement, POWER_ON_DONE and MMIO render-pair registration; no lead commands | One merged 25G83 graph, status acknowledgement, no POWER_ON_DONE, no MMIO pair registration; lead commands required | Same runtime behavior | `g17/fw/variant.rs`, `g17/initdata/v25g83.rs`; startup differences are ABI requirements, while MMIO pair registration on 25G83 remains unqualified | +| Queue pointer / restart layout | Producer at +0x40, recovery target at +0x50 | Producer at +0x50, no recovery-target word | Same runtime behavior | `g17/fw/variant.rs::PointerLayout`, `g17/fw/queue.rs`; do not copy another firmware build's offsets | +| Core topology / mapping ownership | One six-core cluster; two firmware roots and fabric bridge | Qualified two-cluster, 20-core layout; reserved firmware tables, one root and no fabric bridge | Same runtime behavior | `hw/t6040.rs`, `hw/t8140.rs`, `g17/mod.rs`; other fused core counts are still refused, and this audit does not expand that admission | | Alternate loaded image | T8140 uses fixed firmware selection | Both exact T6040 normalized TEXT digests are admitted in one `Build.images` slice | Same runtime behavior | #453 (`26f5eb68dbce`), #463 (`804bba86ca55`), `hw/t6040.rs`, `g17/fw/variant.rs`, `fwid.rs`; unknown images remain refused | | Submission/completion improvements | #423 shared G17 runtime | #423 is already an ancestor of the baseline; direct compute/render publication, admission domains and completion collection are shared | Same runtime behavior | #423; `g17/runtime.rs`, `g17/runtime/render/direct.rs`, `g17/port.rs`, `g17/completion.rs` | | G13/G14 `submit.empty` regressions | Neo uses the G17 runtime | G16S uses that same G17 runtime, not the maintained G13/G14 workqueue | Same runtime behavior | `driver.rs`, `file.rs`, `g17/queue/mod.rs`; user-space empty submits may never reach this ioctl; trace and benchmark the paired Mesa stack | From 0d63308820ab84fb122a470bd482d714e531f253 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 23:08:18 +1100 Subject: [PATCH 082/184] soc: apple: clpc: account for GPU demand on T6040 Apply DjDeveloperr's GPU-demand mechanism from d69f14f4e4fb to T6040. The CPU-only policy can leave the fabric low during GPU-only work. Enable its graphics demand input and describe the existing graphics PMGR state in DT, as T8140 and T8132 already do. Use T6040's existing DCS 5 / fabric 2 table, not T8140's state numbers. CPU demand keeps its three-sample sustain rule; graphics demand uses the shared hold and idle release. No voltage, ceiling, parameter or new platform option is introduced. Confirm powered-state tracking and idle floor release on J616s before qualifying the performance result. Link: https://github.com/aurora-silicon/linux/pull/440 Assisted-by: LLM Signed-off-by: Ryan Murray --- arch/arm64/boot/dts/apple/t6040.dtsi | 1 + drivers/soc/apple/clpc.c | 3 +++ 2 files changed, 4 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t6040.dtsi b/arch/arm64/boot/dts/apple/t6040.dtsi index ad87eaf38ac31..2ec10e49cc1fb 100644 --- a/arch/arm64/boot/dts/apple/t6040.dtsi +++ b/arch/arm64/boot/dts/apple/t6040.dtsi @@ -1023,6 +1023,7 @@ clpc: performance-controller@1 { compatible = "apple,t6041-clpc"; reg = <1>; + apple,gpu-power-state = <&ps_gfx>; }; }; diff --git a/drivers/soc/apple/clpc.c b/drivers/soc/apple/clpc.c index cc7a620c1c772..d51501db045cb 100644 --- a/drivers/soc/apple/clpc.c +++ b/drivers/soc/apple/clpc.c @@ -508,6 +508,8 @@ static const struct apple_clpc_hw apple_clpc_hw_t8140 = { * A cluster requests only after three busy samples in a row (12 ms). At idle * a single busy sample raised the floor about 60 % of the time, from short * bursts of background work; three in a row bring that to about 11 %. + * GPU demand bypasses the CPU streak and uses the same qualified DCS 5 / + * fabric 2 states until the graphics state powers off and HOLD_MS expires. */ static const struct apple_clpc_hw apple_clpc_hw_t6040 = { .rail = { [APPLE_CLPC_DCS] = 0, [APPLE_CLPC_SOC] = 1 }, @@ -516,6 +518,7 @@ static const struct apple_clpc_hw apple_clpc_hw_t6040 = { [APPLE_CLPC_SOC] = { 0, 1, 2, 2 }, }, .sustain = 3, + .gpu_demand = true, }; static const struct of_device_id apple_clpc_of_match[] = { From c38dd831800a7529e3326316a547b9a990509b51 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 23:14:23 +1100 Subject: [PATCH 083/184] tools/asahi: exercise CLPC GPU demand and idle release Compile the production rail tables, sample, hold and floor encoder with controlled PMGR reads and time. Check T6040 DCS 5/fabric 2, unchanged T8140/T8132 floors, release after 32 ms, partial power state, read failure, CPU demand composition and vote errors. Use the real PMC rail encoding. Negative controls remove GPU demand, release too early or retain the floor at idle. These are CPU controls; J616s PMGR tracking, idle power, PMP cooperation and performance still need hardware qualification. Assisted-by: LLM Signed-off-by: Ryan Murray --- tools/asahi/clpc-gpu-demand-tests.py | 131 +++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 tools/asahi/clpc-gpu-demand-tests.py diff --git a/tools/asahi/clpc-gpu-demand-tests.py b/tools/asahi/clpc-gpu-demand-tests.py new file mode 100644 index 0000000000000..b39b0848ed74a --- /dev/null +++ b/tools/asahi/clpc-gpu-demand-tests.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only OR MIT +"""Run production CLPC demand/hold logic with controlled PMGR reads and time.""" +from pathlib import Path +import os +import re +import subprocess +import tempfile + +ROOT = Path(__file__).resolve().parents[2] + + +def item(text, signature): + start = text.index(signature) + begin = text.index('{', start) + depth, end = 1, begin + 1 + while depth: + depth += (text[end] == '{') - (text[end] == '}') + end += 1 + return text[start:end] + + +def main(): + source = (ROOT / 'drivers/soc/apple/clpc.c').read_text() + header = (ROOT / 'include/linux/soc/apple/pmc.h').read_text() + rail_shift = re.search(r'^#define APPLE_PMC_RAIL_SHIFT\(rail\).*$', header, re.M).group(0) + types = '\n'.join(item(source, s) + ';' for s in [ + 'struct apple_clpc_hw {', 'struct apple_clpc_vote {']) + tables = '\n'.join(item(source, 'static const struct apple_clpc_hw ' + n) + ';' + for n in ['apple_clpc_hw_t8132', 'apple_clpc_hw_t8140', 'apple_clpc_hw_t6040']) + methods = '\n'.join(item(source, s) for s in [ + 'static unsigned int apple_clpc_vote(', 'static u32 apple_clpc_floor(', + 'static void apple_clpc_sample(']) + code = r''' +#include +#include +#include +#include +#include +typedef uint8_t u8; +typedef uint32_t u32; +#define APPLE_CLPC_LEVELS 4 +#define APPLE_CLPC_HOLD_MS 32 +#define HZ 1000 +#define max(a,b) ((a) > (b) ? (a) : (b)) +#define GENMASK(h,l) (((1u << ((h)-(l)+1))-1) << (l)) +#define APPLE_PMC_RAIL_SHIFT(r) ((r)*8) +#define time_after_eq(a,b) ((long)((a)-(b)) >= 0) +#define msecs_to_jiffies(ms) (ms) +enum apple_clpc_rail { APPLE_CLPC_DCS, APPLE_CLPC_SOC, APPLE_CLPC_RAILS }; +''' + types + r''' +struct apple_clpc_cluster { unsigned int request[APPLE_CLPC_RAILS]; }; +struct apple_clpc { + const struct apple_clpc_hw *hw; + struct apple_clpc_cluster clusters[1]; + struct apple_clpc_vote vote[APPLE_CLPC_RAILS]; + unsigned int nr_clusters, agent; + unsigned long samples, next_scan, update_errors; + void *covered, *pmc, *gpu_pmgr; + u32 gpu_offset, mask; +}; +static unsigned long jiffies; +static void *cpu_possible_mask; +static u32 pmgr_status, published; +static int read_error, vote_error; +static bool queued_idle; +static bool cpumask_equal(void *a, void *b) { return a == b; } +static void apple_clpc_find_clusters(struct apple_clpc *c) { (void)c; } +static void apple_clpc_sample_cluster(struct apple_clpc *c, struct apple_clpc_cluster *cl) { (void)c; (void)cl; } +static int regmap_read(void *map, u32 offset, u32 *status) { + assert(map); (void)offset; *status=pmgr_status; return read_error; +} +static int apple_pmc_floor_update(void *pmc, unsigned int agent, u32 mask, u32 floor) { + (void)pmc; assert(agent==1 && mask==3); published=floor; return vote_error; +} +static void apple_clpc_queue(struct apple_clpc *c, bool idle) { (void)c; queued_idle=idle; } +''' + tables + '\n' + methods + r''' +static void check(const struct apple_clpc_hw *hw, u32 top) { + struct apple_clpc c={.hw=hw,.agent=1,.mask=3,.gpu_pmgr=hw->gpu_demand ? (void *)1 : NULL}; + jiffies=0; read_error=vote_error=0; pmgr_status=0; published=~0u; + apple_clpc_sample(&c); assert(published==0 && queued_idle); + jiffies=100; pmgr_status=0xf0; + apple_clpc_sample(&c); assert(published==top && !queued_idle); + pmgr_status=0; + jiffies=131; apple_clpc_sample(&c); assert(published==top && !queued_idle); + jiffies=132; apple_clpc_sample(&c); assert(published==0 && queued_idle); + // A partially powered state is not a powered graphics consumer. + jiffies=200; pmgr_status=0xe0; + apple_clpc_sample(&c); assert(published==0 && queued_idle); + // Preserve the qualified floor when a live demand input cannot be sampled. + read_error=-EIO; apple_clpc_sample(&c); assert(published==top); + read_error=0; jiffies=232; + apple_clpc_sample(&c); assert(published==0); + // GPU activity must not erase independent CPU demand at release. + c.nr_clusters=1; c.clusters[0].request[0]=2; c.clusters[0].request[1]=1; + jiffies=300; pmgr_status=0xf0; apple_clpc_sample(&c); assert(published==top); + jiffies=332; pmgr_status=0; apple_clpc_sample(&c); assert(published==0x12); + vote_error=-EIO; apple_clpc_sample(&c); assert(c.update_errors==1); +} +int main(void) { + check(&apple_clpc_hw_t6040,0x25); + check(&apple_clpc_hw_t8140,0x34); + check(&apple_clpc_hw_t8132,0x35); + puts("PASS: T6040 5/2, T8140 4/3 and T8132 5/3 GPU floors; 32 ms idle release; partial state, read failure, CPU demand and vote-error controls"); +} +''' + code = code.replace('#define APPLE_PMC_RAIL_SHIFT(r) ((r)*8)', rail_shift) + with tempfile.TemporaryDirectory(prefix='apple-clpc-gpu-') as tmp: + root = Path(tmp) + for name, text in [ + ('fixed', code), + ('omit-gpu-demand', code.replace('if (clpc->gpu_pmgr) {', 'if (false) {')), + ('release-too-early', code.replace('msecs_to_jiffies(APPLE_CLPC_HOLD_MS)', '0')), + ('retain-at-idle', code.replace('vote->state = 0;', '')), + ]: + path = root / (name + '.c') + path.write_text(text) + binary = path.with_suffix('') + subprocess.run([os.environ.get('CC', 'cc'), '-std=gnu11', '-Wall', '-Wextra', + '-Werror', str(path), '-o', str(binary)], check=True) + run = subprocess.run([str(binary)], capture_output=True, text=True) + if name == 'fixed': + assert run.returncode == 0, run.stderr + print(run.stdout.strip()) + else: + assert run.returncode != 0, name + print('PASS negative control:', name) + + +if __name__ == '__main__': + main() From 772a9d3104ff31d332a8e083cb0f90082369cc60 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 23:28:49 +1100 Subject: [PATCH 084/184] arm64: dts: apple: enable the PMP's T6040 memory votes after its owner binds The T6040 PMC description still excludes PMP agent 0 from the earlier bring-up, when the firmware did not run and iBoot's top-state floor would have stayed in force. PMP startup is now supported and the GPU's report entry follows its power lifetime, but the PMC cannot honour that demand while the agent remains disabled. Use the existing apple,pmp supplier dependency before enabling static PMP, CLPC and AP voters, as in the Neo voter integration. The T6040 match data continues to restrict them to the established DCS/SOC rail mask; no display rail, voltage, power ceiling or new control is introduced. Confirm live PMP votes, idle release and sustained GPU/CPU thermal behaviour on J616s before qualifying this candidate. Link: https://github.com/aurora-silicon/linux/pull/219 Link: https://github.com/aurora-silicon/linux/pull/402 Assisted-by: LLM Signed-off-by: Ryan Murray --- arch/arm64/boot/dts/apple/t6040.dtsi | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/arch/arm64/boot/dts/apple/t6040.dtsi b/arch/arm64/boot/dts/apple/t6040.dtsi index 2ec10e49cc1fb..1cbc2a97a51c1 100644 --- a/arch/arm64/boot/dts/apple/t6040.dtsi +++ b/arch/arm64/boot/dts/apple/t6040.dtsi @@ -1005,9 +1005,10 @@ * only hands to its firmware, so the two do not conflict; "rails" * comes first so that this node's unit address differs from * pmgr_dcp's. - * Static voters are CLPC (1) and AP (3). The PMP's agent (0) stays - * off while the PMP does not run: iBoot leaves its floor at the top - * DCS state. + * Bind after the PMP firmware owner. The PMP (0), CLPC (1) and AP (3) + * then vote through the PMC's T6040 DCS/SOC rail mask. Without the + * supplier dependency, the PMP's iBoot floor could be honoured before + * its firmware replaces it with live demand. */ pmc: power-management@5048aa000 { compatible = "apple,t6041-pmc"; @@ -1017,7 +1018,8 @@ nonposted-mmio; #address-cells = <1>; #size-cells = <0>; - apple,static-voters = <1 3>; + apple,pmp = <&pmp>; + apple,static-voters = <0 1 3>; status = "disabled"; clpc: performance-controller@1 { From fb63f95afe46ad66c35cf75a968ff201cd5de008 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 23:30:09 +1100 Subject: [PATCH 085/184] dt-bindings: spi: apple: Add the T6041 SPI controller The M4 Pro (T6040/T6041) SPI controllers are register compatible with T8103, like the T6020 and T8140 ones. Assisted-by: LLM Signed-off-by: Ryan Murray --- Documentation/devicetree/bindings/spi/apple,spi.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/spi/apple,spi.yaml b/Documentation/devicetree/bindings/spi/apple,spi.yaml index 4b30265686fa1..be47c5f9ff075 100644 --- a/Documentation/devicetree/bindings/spi/apple,spi.yaml +++ b/Documentation/devicetree/bindings/spi/apple,spi.yaml @@ -18,6 +18,7 @@ properties: - items: - enum: - apple,t6020-spi + - apple,t6041-spi - apple,t8140-spi - const: apple,t8103-spi - items: From ceca3fd9960240011d2609198a864d3a549d42cc Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 23:30:09 +1100 Subject: [PATCH 086/184] dt-bindings: input: apple,mesa-fingerprint: Name the signed root key Sensors whose enclave session needs a signed root key (the J700 and J616s part, 0x3356) take it as per-device factory data, like the calibration. Let firmware-name carry it as an optional second entry so the board description names it. Assisted-by: LLM Signed-off-by: Ryan Murray --- .../bindings/input/apple,mesa-fingerprint.yaml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Documentation/devicetree/bindings/input/apple,mesa-fingerprint.yaml b/Documentation/devicetree/bindings/input/apple,mesa-fingerprint.yaml index 53b304bcec7ff..631ba47a5a3ca 100644 --- a/Documentation/devicetree/bindings/input/apple,mesa-fingerprint.yaml +++ b/Documentation/devicetree/bindings/input/apple,mesa-fingerprint.yaml @@ -57,11 +57,17 @@ properties: description: Rail selector words, in power-on order. firmware-name: - maxItems: 1 + minItems: 1 + items: + - description: + Calibration blob for this sensor. + - description: + Signed root-key container, for sensors whose enclave session needs + one. description: - Calibration blob for this sensor. It is per-device factory data written - at manufacture, so it is neither redistributable nor derivable from the - compatible; the board description has to name it. + Per-device factory data written at manufacture, so neither + redistributable nor derivable from the compatible; the board + description has to name it. spi-cpha: true From 1bcccae53e5bfa901859a4e29f85cdbd9278a47c Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 23:30:10 +1100 Subject: [PATCH 087/184] soc: apple: sep: Take the sensor root key name from the device tree The signed root-key container is per-device data, but its name was fixed to J700's. Read it from the sensor node's second firmware-name entry and keep J700's name when the board gives only the calibration, so the J700 device tree and its installed file keep working unchanged. Name the file in the error when it cannot be loaded. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/soc/apple/sbio.rs | 9 +++++---- drivers/soc/apple/sensor.rs | 15 +++++++++++++-- drivers/soc/apple/sensor_shim.c | 24 ++++++++++++++---------- drivers/soc/apple/sep.rs | 3 +++ drivers/soc/apple/shim.h | 2 +- 5 files changed, 36 insertions(+), 17 deletions(-) diff --git a/drivers/soc/apple/sbio.rs b/drivers/soc/apple/sbio.rs index 88be672817b1c..5a81a370dda38 100644 --- a/drivers/soc/apple/sbio.rs +++ b/drivers/soc/apple/sbio.rs @@ -2183,14 +2183,15 @@ impl SepData { self.sensor_trace(c"SENSOR_NONCE"); if !self.sensor_key_loaded.load(Relaxed) { - let firmware = match kernel::firmware::Firmware::request( - c"apple/msrk-j700.bin", &self.dev, - ) { + // Per-device data, so the board names it; J700 predates that. + let name = sensor::root_key_firmware_name().unwrap_or(ROOT_KEY_FIRMWARE); + let firmware = match kernel::firmware::Firmware::request(name, &self.dev) { Ok(firmware) => firmware, Err(error) => { dev_err!( self.dev, - "sensor: signed root-key container unavailable: {:?}\n", + "sensor: signed root-key container {} unavailable: {:?}\n", + name, error ); return false; diff --git a/drivers/soc/apple/sensor.rs b/drivers/soc/apple/sensor.rs index 63a4ae28fbc6d..13bb470c89b3b 100644 --- a/drivers/soc/apple/sensor.rs +++ b/drivers/soc/apple/sensor.rs @@ -25,15 +25,26 @@ extern "C" { fn sep_sensor_irq_available() -> c_int; fn sep_sensor_irq_arm(); fn sep_sensor_irq_wait(timeout_ms: c_uint) -> c_int; - fn sep_sensor_firmware_name() -> *const c_char; + fn sep_sensor_firmware_name(index: c_uint) -> *const c_char; } /// The calibration blob's name from the board description, or `None` when the /// sensor node has no `firmware-name` property (the caller then uses a default). pub(crate) fn firmware_name() -> Option<&'static CStr> { + firmware_name_at(0) +} + +/// The signed root-key container's name from the board description: the +/// second `firmware-name` entry, or `None` when the board gives only the +/// calibration (the caller then uses a default). +pub(crate) fn root_key_firmware_name() -> Option<&'static CStr> { + firmware_name_at(1) +} + +fn firmware_name_at(index: c_uint) -> Option<&'static CStr> { // SAFETY: the shim returns NULL or a pointer to a NUL-terminated string // owned by the device property and valid for the device's lifetime. - let ptr = unsafe { sep_sensor_firmware_name() }; + let ptr = unsafe { sep_sensor_firmware_name(index) }; if ptr.is_null() { return None; } diff --git a/drivers/soc/apple/sensor_shim.c b/drivers/soc/apple/sensor_shim.c index 68405e0a74507..bed228b4d1ca3 100644 --- a/drivers/soc/apple/sensor_shim.c +++ b/drivers/soc/apple/sensor_shim.c @@ -568,19 +568,23 @@ static int sep_sensor_power_line_locked(void) } /* - * The per-device calibration blob name, as the board description gives it in - * the sensor node's "firmware-name" property. NULL when absent, so the caller - * can fall back to a default. + * A per-device blob name, as the board description gives it in the sensor + * node's "firmware-name" list: index 0 is the calibration, index 1 the signed + * root key. NULL when that entry is absent, so the caller can fall back to a + * default. */ -static const char *sep_sensor_firmware_name_locked(void) +static const char *sep_sensor_firmware_name_locked(unsigned int index) { - const char *name = NULL; + const char *names[2] = { NULL, NULL }; + int count; - if (!sep_spi) + if (!sep_spi || index >= ARRAY_SIZE(names)) return NULL; - if (device_property_read_string(&sep_spi->dev, "firmware-name", &name)) + count = device_property_read_string_array(&sep_spi->dev, "firmware-name", + names, ARRAY_SIZE(names)); + if (count <= (int)index) return NULL; - return name; + return names[index]; } /* Powers the sensor on/off, holding the hardware settling delay. */ @@ -871,14 +875,14 @@ int sep_sensor_power_line(void) return result; } -const char *sep_sensor_firmware_name(void) +const char *sep_sensor_firmware_name(unsigned int index) { struct sep_sensor_lifetime *sensor = sep_lock_sensor(); const char *result; if (!sensor) return NULL; - result = sep_sensor_firmware_name_locked(); + result = sep_sensor_firmware_name_locked(index); mutex_unlock(&sensor->lock); return result; } diff --git a/drivers/soc/apple/sep.rs b/drivers/soc/apple/sep.rs index df71496a564fe..7d9f4f2af1869 100644 --- a/drivers/soc/apple/sep.rs +++ b/drivers/soc/apple/sep.rs @@ -449,6 +449,9 @@ const PATCH_POLL_MS: u32 = 20; const PATCH_POLL_ATTEMPTS: u32 = 250; const CALIBRATION_FIRMWARE: &CStr = c"apple/mesa_calibration.bin"; +/// The signed root-key container on boards whose sensor node names only the +/// calibration (J700). +const ROOT_KEY_FIRMWARE: &CStr = c"apple/msrk-j700.bin"; const SBIO_PROBE_USER_ID: i32 = 1000; static_assert!(SBIO_PROBE_USER_ID >= crate::sks::SKS_DESIGNATE_USER_MIN); diff --git a/drivers/soc/apple/shim.h b/drivers/soc/apple/shim.h index 0425bf2cdaa6e..99d2661e2cb1c 100644 --- a/drivers/soc/apple/shim.h +++ b/drivers/soc/apple/shim.h @@ -132,7 +132,7 @@ int sep_sensor_cs_timing_mode(void); int sep_sensor_power_cycle(void); int sep_sensor_power_source(void); int sep_sensor_power(int on); -const char *sep_sensor_firmware_name(void); +const char *sep_sensor_firmware_name(unsigned int index); int sep_sensor_xfer(const void *tx, void *rx, size_t len); int sep_sensor_xfer_tx(const void *tx, size_t len); int sep_sensor_xfer2(const void *tx, size_t tx_len, void *rx, size_t rx_len); From 718202c86b1ff9f11fcc9aa957f017243c2d18b1 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 23:30:10 +1100 Subject: [PATCH 088/184] soc: apple: sep: Add the T6040 (J616s) platform profile The J616s ADT (26.6.2) describes a SEP that iBoot boots before the AP OS, an iop-sep,ascwrap-v6 whose DART window starts at the BIT(40) alias, and the Mesa sensor J700 carries (0x3356) on spi2 at 0x429108000, 8 MHz, chip select 0. So this is a warm-registration platform with T8140's authenticated sensor session, and the xART guard on from the first boot because macOS shares the enclave. Only J616s is matched; the other T6040 Macs' sensor wiring is unchecked. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/soc/apple/profile.rs | 40 ++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/drivers/soc/apple/profile.rs b/drivers/soc/apple/profile.rs index 0ba0eb510122b..0e632c65da81f 100644 --- a/drivers/soc/apple/profile.rs +++ b/drivers/soc/apple/profile.rs @@ -9,7 +9,7 @@ //! here once. The driver reads addresses from the device tree, never scans for //! them, and never patches properties at runtime. //! -//! Five bring-up targets are modelled: +//! These bring-up targets are modelled: //! //! * `T8103` / J313 (MacBook Air, M1): the host boots the SEP with the boot //! endpoint handshake over a 0x30000 shared-memory window. @@ -24,6 +24,8 @@ //! a powered sensor, so the driver uses the warm registration path. //! * `T8122` / J613, J615 (MacBook Air, M3): the T8112 warm registration with //! the T8140 SEP DART aperture above 4 GiB. +//! * `T6040` / J616s (MacBook Pro 16", M4 Pro): iBoot boots sepOS, so the +//! driver uses the warm registration path; the sensor is the J700 part. // The boot handshake, identity source, sensor and DART fields are consumed by // the boot-endpoint, identity and sensor-transport paths. @@ -426,6 +428,28 @@ pub(crate) const T8122: PlatformProfile = PlatformProfile { ..T8112 }; +/// MacBook Pro 16" M4 Pro (J616s). The J616s ADT (26.6.2, 25G83) records a +/// pre-booted SEP (`sepfw-load-at-boot`), an `iop-sep,ascwrap-v6` SEP whose +/// DART aperture starts at the BIT(40) alias (`vm-base` 0x10000004000), and +/// the Mesa sensor J700 carries (`sensor-id` 0x3356) on spi2 at +/// `0x429108000`, 8 MHz, chip select 0. Its power is NUB GPIO 53 and its +/// data-ready line AP GPIO 116. `/defaults` carries `cpx-encryption-mode = 2`. +/// +/// A warm-registration platform like T8140, with T8140's authenticated +/// sensor session: the enclave serves this sensor only after the nonce and +/// the signed root key. Device-tree nodes remain authoritative for addresses. +pub(crate) const T6040: PlatformProfile = PlatformProfile { + name: "T6040/J616s", + sensor: SensorProfile { + controller_base: 0x4_2910_8000, + ..T8140.sensor + }, + // macOS shares this enclave and Linux has no records here yet: keep + // Linux to its own xART records from the first boot. + xart_guard: 1, + ..T8140 +}; + static_assert!(T8103.shmem_capacity == 0x30000); static_assert!(T6020.shmem_capacity == 0x40000); static_assert!(T8140.shmem_capacity == 0x40000); @@ -441,6 +465,8 @@ static_assert!(T8112.sensor.controller_base == T8103.sensor.controller_base); static_assert!(T8122.shmem_capacity == T8112.shmem_capacity); static_assert!(T8122.sensor.controller_base == 0x2a1108000); static_assert!(T8122.wide_dma_mask); +static_assert!(T6040.sensor.controller_base == 0x429108000); +static_assert!(T6040.sensor.expected_id == T8140.sensor.expected_id); /// Whether the machine root declares `compatible`. /// @@ -587,6 +613,11 @@ pub(crate) fn detect() -> Result<&'static PlatformProfile> { if machine_has(b"apple,t8122") && (machine_has(b"apple,j613") || machine_has(b"apple,j615")) { return Ok(&T8122); } + // Only the board whose ADT was read: J616s. The other T6040 Macs share + // the SoC but their sensor wiring is unchecked. + if machine_has(b"apple,t6040") && machine_has(b"apple,j616s") { + return Ok(&T6040); + } Err(ENODEV) } @@ -637,6 +668,11 @@ mod tests { assert!(T8122.wide_dma_mask); assert!(T8122.shmem_capacity == T8112.shmem_capacity); assert!(T8122.sensor.controller_base == 0x2_a110_8000); + assert!(T6040.nonce_session); + assert!(T6040.wide_dma_mask); + assert!(T6040.bootstrap == Bootstrap::WarmRegister); + assert!(T6040.identity == IdentitySource::Chosen); + assert!(T6040.xart_guard == 1); } } @@ -667,7 +703,7 @@ mod dart_window_tests { #[test] fn the_other_profiles_keep_the_bit40_window() { - for profile in [&T8103, &T6000, &T6020, &T8140, &T6020_SEPOS13, &T8122] { + for profile in [&T8103, &T6000, &T6020, &T8140, &T6020_SEPOS13, &T8122, &T6040] { assert_eq!(offset(profile.dart_window), 1 << 40); assert_eq!(range(profile.dart_window), (0, 1 << 32)); } From ccbc6f976c8ee65c2947cd5f6eb9d85413fffd46 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 23:30:10 +1100 Subject: [PATCH 089/184] arm64: dts: apple: t6040: Add the SEP, its DART and mailbox, and spi2 From the J616s ADT: the SEP at 0x406600000 with its mailbox at +0x8000 (AIC 666-669), its DART at 0x404c80000 (AIC 676) with the window iBoot programmed (vm-base 0x10000004000, vm-size 0x3ff00000), and spi2 at 0x429108000 (AIC 1569) with the Mesa sensor child. Add the sep, spi_p and spi2 power states from the ADT pmgr devices. The SEP and its DART stay disabled; apple_sep enables them when it loads. Assisted-by: LLM Signed-off-by: Ryan Murray --- arch/arm64/boot/dts/apple/t6040-pmgr.dtsi | 28 ++++++++++ arch/arm64/boot/dts/apple/t6040.dtsi | 66 +++++++++++++++++++++++ 2 files changed, 94 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t6040-pmgr.dtsi b/arch/arm64/boot/dts/apple/t6040-pmgr.dtsi index 8c05fb4d4d5d3..bf357745d581d 100644 --- a/arch/arm64/boot/dts/apple/t6040-pmgr.dtsi +++ b/arch/arm64/boot/dts/apple/t6040-pmgr.dtsi @@ -140,6 +140,24 @@ power-domains = <&ps_sio>; }; + ps_spi_p: power-controller@198 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x198 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "spi_p"; + power-domains = <&ps_sio>; + }; + + ps_spi2: power-controller@1c8 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1c8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "spi2"; + power-domains = <&ps_spi_p>; + }; + ps_ans: power-controller@370 { compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; reg = <0x370 4>; @@ -178,6 +196,16 @@ power-domains = <&ps_ans>, <&ps_apcie_st1>; apple,always-on; }; + + ps_sep: power-controller@c00 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0xc00 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "sep"; + power-domains = <&ps_fab3_soc>; + apple,always-on; + }; }; /* diff --git a/arch/arm64/boot/dts/apple/t6040.dtsi b/arch/arm64/boot/dts/apple/t6040.dtsi index ad87eaf38ac31..fa677708d7c4f 100644 --- a/arch/arm64/boot/dts/apple/t6040.dtsi +++ b/arch/arm64/boot/dts/apple/t6040.dtsi @@ -701,6 +701,48 @@ status = "disabled"; }; + /* + * iBoot hands over a running SEP. Keep Linux off its DART at boot; + * apple_sep enables it when the driver binds. The SEP only accepts + * IOVAs in the window iBoot programmed (ADT vm-base 0x10000004000, + * vm-size 0x3ff00000), which carries the BIT(40) alias the DART + * translates. + */ + sep_dart: iommu@404c80000 { + compatible = "apple,t6041-dart", "apple,t8110-dart"; + reg = <0x4 0x04c80000 0x0 0x20000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + apple,dma-range = /bits/ 64 <0x10000004000 0x3ff00000>; + apple,dma-offset = /bits/ 64 <0x0>; + status = "disabled"; + }; + + sep: sep@406600000 { + compatible = "apple,sep"; + reg = <0x4 0x06600000 0x0 0x88000>; + mboxes = <&sep_mbox>; + mbox-names = "mbox"; + iommus = <&sep_dart 0>; + power-domains = <&ps_sep>; + status = "disabled"; + }; + + sep_mbox: mailbox@406608000 { + compatible = "apple,t6041-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x4 0x06608000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + status = "disabled"; + }; + ans_mbox: mailbox@409608000 { compatible = "apple,t6041-asc-mailbox", "apple,asc-mailbox-v4"; reg = <0x4 0x09608000 0x0 0x4000>; @@ -826,6 +868,30 @@ status = "disabled"; }; + /* + * Touch ID. iBoot leaves the pads muxed and the controller uses its + * native chip select, so there is no pinctrl here. + */ + spi2: spi@429108000 { + compatible = "apple,t6041-spi", "apple,t8103-spi"; + reg = <0x4 0x29108000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + clocks = <&clkref>; + power-domains = <&ps_spi2>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + + mesa: fingerprint@0 { + compatible = "apple,mesa-fingerprint"; + reg = <0>; + spi-max-frequency = <8000000>; + spi-cpol; + status = "disabled"; + }; + }; + nvme: nvme@44dcc0000 { compatible = "apple,t6041-nvme-ans2"; reg = <0x4 0x4dcc0000 0x0 0x10000>, From a4b962a20add7dd09ff167f8cfc7cc4bd53ad2e9 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 23:30:10 +1100 Subject: [PATCH 090/184] arm64: dts: apple: t6040-j616s: Enable Touch ID Give the boot loader the sep alias it reserves the SEP firmware through, enable the SEP mailbox and spi2, and describe the sensor: NUB GPIO 53 powers it, AP GPIO 116 is its data-ready line, and its sealed calibration and root key come from this Mac's own iBoot system container. Assisted-by: LLM Signed-off-by: Ryan Murray --- arch/arm64/boot/dts/apple/t6040-j616s.dts | 29 +++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t6040-j616s.dts b/arch/arm64/boot/dts/apple/t6040-j616s.dts index 7abbd491489d0..a4fcf1a227bd7 100644 --- a/arch/arm64/boot/dts/apple/t6040-j616s.dts +++ b/arch/arm64/boot/dts/apple/t6040-j616s.dts @@ -29,6 +29,8 @@ disp0-piodma = &disp0_piodma; /* the boot loader finds the GPU node through this alias */ gpu = &gpu; + /* the boot loader reserves the SEP firmware through this alias */ + sep = &sep; }; chosen { @@ -470,6 +472,33 @@ status = "okay"; }; +/* apple_sep enables the SEP and its DART when it loads. */ +&sep_mbox { + status = "okay"; +}; + +/* + * Touch ID sensor (Mesa 0x3356). Its power is NUB GPIO 53 and its + * data-ready line is AP GPIO 116, as the J616s ADT describes them. + */ +&spi2 { + status = "okay"; +}; + +&mesa { + status = "okay"; + enable-gpios = <&pinctrl_nub 53 GPIO_ACTIVE_HIGH>; + interrupt-parent = <&pinctrl>; + interrupts = <116 IRQ_TYPE_EDGE_RISING>; + spi-cs-setup-delay-ns = <20>; + spi-cs-hold-delay-ns = <20>; + /* + * Per-device factory data from this Mac's own iBoot system container: + * the sealed calibration and the sealed root key. + */ + firmware-name = "apple/mesacal-j616s.bin", "apple/msrk-j616s.bin"; +}; + /* The Type-C retimers (ADT atcrt at 0x18/0x19/0x1a) sit on this bus; no driver yet. */ &i2c6 { status = "okay"; From fc5a95c0ee8708e5aa0a241ea0fc6cd4cdf85a87 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Tue, 27 Oct 2026 00:05:40 +1100 Subject: [PATCH 091/184] docs: audit Neo KMD refinements commit by commit on T6040 List every #423 commit separately and enumerate the related integration and aurora-wip history, including original/rebased aliases and Dj's contributions. Record current reachability, firmware/data gates, superseded policies, source evidence and a J616s measurement for each. Separate public measured Neo results from stack results and estimates. Document the applicable CLPC GPU-demand and PMP-voter ports and the protocol-specific paths that cannot be enabled by copying Neo data. Keep unmerged measured follow-ups outside the pinned-head claims. Assisted-by: LLM Signed-off-by: Ryan Murray --- GAPS.md | 1050 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 1048 insertions(+), 2 deletions(-) diff --git a/GAPS.md b/GAPS.md index 2e0fd834ce28a..cfa2140a890b4 100644 --- a/GAPS.md +++ b/GAPS.md @@ -6,8 +6,10 @@ r5 display series (`905dff774561`), camera #462 (`2114ed0c6245`), and the two r6 display patches. The reconstructed r6 branch is `wip/j616s-display-r6` at `af0a664046fb`. -This series repairs regression coverage and a shared KUNIT=m WERROR build defect. It does not change clock, voltage, -firmware policy, feature advertisement, or the submission algorithm. The major +This series repairs regression coverage and a shared KUNIT=m WERROR build defect, +and ports GPU-driven memory/fabric demand and PMP voter cooperation using +T6040 data. GPU voltages, firmware features and the submission algorithm remain +unchanged from the reconstructed base. The major T6040 calibration and image-admission changes requested by this audit are already in the baseline. Software parity does not establish benchmark or CTS parity. There are no J616s results for this series yet. @@ -20,6 +22,8 @@ parity. There are no J616s results for this series yet. | Raw OPP count versus logical states | Firmware consumes 11 logical states | 16 raw GPU OPPs; 11 selectors in each A/B map; both end at raw OPP 15; unused handoff slots must be zero | KUnit now uses valid complete maps and checks the final state's second voltage lane | `g17/calibration.rs`; a count of 15 in an ADT property is not evidence for expanding this firmware layout | | Full clock eligibility | Platform OPPs feed firmware performance tables | DT supplies both cluster voltages and GPU/CS/AFR frequencies; maps and relative-B are checked against the GPU OPP table | Same runtime behavior | `g17/initdata/v25g83.rs`, `g17/fw/initdata/v25g83.rs`; confirm sustained clocks and limiting reasons on hardware | | DVFS ramp / effort | Full effort on publication, cleared after 16 ms of continuous idle | Same `Firmware::note_submission`, direct-port accounting and race-checked idle clear | Same runtime behavior | `g17/mod.rs`, `g17/port.rs`, `g17/power.rs`; opaque firmware power words are not a documented ramp control | +| GPU-driven fabric/memory floor | #440 lifts DCS 4/fabric 3 for GPU demand | T6040 CPU-only demand, gpu_demand=false and no power-state phandle | Port 0d63308820ab enables the shared mechanism with existing T6040 DCS 5/fabric 2 data | clpc.c and T6040 DT; g1/g2 P/C/R/H/I measurements required | +| PMP PMC vote cooperation | PMP/CLPC/AP agents enabled | T6040 static voters omit PMP 0 from pre-working-PMP bring-up | Port 772a9d3104ff adds the existing PMP supplier dependency and voter 0 on T6040 DCS/SOC rails | pmc.c and T6040 DT; live votes, idle release and thermal qualification required | | Idle / off policy | Shared host hold and firmware device-control path | Same host implementation; source documents a 25G83 effective 2 ms off delay and approximately 1 ms wake cost | Same runtime behavior; default hold unchanged | `g17/power.rs`; measure actual idle/wake latency before altering policy | | Fast-die / PMP | Platform-specific firmware power graph | 25G83 AON table and separate power-state target/release (101/96 C) are already populated; DT enables the GFX suppliers | Same runtime behavior | `g17/fw/initdata/v25g83.rs::POWER_STATE_WORDS`, `g17/initdata/v25g83.rs`, J616s DT; verify clocks with PMP running | | Periodic preemption | Context-switch timer mask is zero | Same zero mask; two-cluster operand-state allocation differs by hardware layout | Same runtime behavior | `g17/fw/initdata.rs::CONTEXT_SWITCH_TIMER_MASK`, `g17/fw/compute.rs::OperandState`; no evidence supports changing the mask | @@ -57,3 +61,1045 @@ lab record is included here. The build handoff must identify the exact tested source and loader, verify boot payload checksums, retain r6g rollback, and distinguish CPU/QEMU tests from GPU execution. Missing J616s measurements are not passing results. + +## Neo KMD improvements on T6040 + +This ledger audits refinements and handling, not just feature parity. It uses +`integration/aurora` at `9cbdd0734d9e186c5f5f5949a21b909d10c2571b` and +`aurora-wip` at `927a4a4480cf90ce8e3b4ab1d1ce58d84bf32fd4`. The conservative +history window starts 2026-09-26 (Neo platform prerequisites), before the public +T8140 firmware interface of 2026-10-03 and hardware bring-up of 2026-10-06. +Committer dates retain rebased older-author changes. The inventory also includes +the complete 105-commit public #171 history, all 65 commits of #423, and the +related PR versions used to identify original contributions and later ports. + +The path log covers `drivers/gpu/drm/asahi`, Apple PMP/PMGR/PMC/CLPC/MCC, +RTKit/mailbox, CPU-frequency/thermal and DART code, related AIC and DRM +scheduler/fence support, Rust DRM interfaces, and Neo DT dependencies. Broad +path/PR neighbors and imported baseline changes are explicitly marked N/A; +they are not represented as measured Neo optimizations. Old/rebased hashes are +listed as encountered aliases of the same subject/class, not asserted to be +byte-identical. The coverage inventory lists 1,551 physical commits (including +imported neighbors and PR-original versions), with 851 distinct subjects; all +65 #423 commits have separate rows. **Active** describes the surviving mechanism on T6040, including +its 7.3 port/refinement, rather than claiming that every historical SHA is an +ancestor. Multi-purpose firmware-introduction records retain their shared +parts while explicitly identifying dormant MCache/native-feature portions. A commit link is its exact diff; the evidence key states the current +reachability/gate. No per-commit speedup is invented from a stack measurement. + +### Applicable ports found by the commit audit + +| Neo mechanism | T6040 in g1 | g2 port and per-chip data | Required acceptance | +| --- | --- | --- | --- | +| #440 GPU-driven CLPC memory/fabric floor | CPU demand only; `gpu_demand=false`, no GPU power-state phandle | `0d63308820ab`: existing GPU-demand mechanism and DT phandle, T6040 DCS 5/fabric 2 and existing 32 ms release; CPU sustain=3 retained | C/R/H/I/P; actual PMGR tracking, floor release, video/browsing and sustained-load power | +| #219 PMP voter cooperation | Static voters 1/3 omit PMP 0 from pre-working-PMP bring-up | `772a9d3104ff`: existing `apple,pmp` supplier dependency, voters 0/1/3, existing T6040 DCS/SOC rail mask; no display rail or voltage change | P/B; PMP bound/ready, live request/honoured floors, idle and thermal behavior | + +Other audited gated paths are listed explicitly below. The T8140 lazy CPU +verification cost is absent on T6040's already-fast-switch profile. MMIO pair +registration, range-only TLB encoding, native RT/few-primitives and optional DCS +cooling cannot be enabled by copying a different firmware/hardware protocol. +The recovery slot-key attribution gate still needs validated 25G83 identity +facts; shared recovery, deadlines and retirement handling remain enabled. + +### Reported measurements and their scope + +These are public PR results, not new J616s measurements. Private qualification +reports were checked for scope and regressions; raw lab records are not copied +here. The older GPU profiling report's cost estimates are not measurements. + +| Evidence | Relevant commits/mechanism | Reported Neo result | J616s confirmation | +| --- | --- | --- | --- | +| [#171](https://github.com/aurora-silicon/linux/pull/171) | Retention, off-lock cleanup, reclaim quiet window and shared lifetime stack | Cold first-submit median 224 to 194–198 us, p99 338 to 295–305 us; menu-launch stalls below 100 ms versus 260–280 ms before quiet-window work. Stack results, not isolated gains. | B/M/C/R, repeated fresh contexts and foreground launch under bounded pressure | +| [#171](https://github.com/aurora-silicon/linux/pull/171) | Completion/recovery isolation | Earlier mixed render/compute lost its device within 2 s; later fixture ran over 568k verified checks in 60 s. Historical 1 ms stamp fallback was subsequently refined to 50 ms. | F/H/T; exact data and fences, peer liveness and no premature mapping release | +| [#423](https://github.com/aurora-silicon/linux/pull/423) | Cumulative scopes and submission stack | Same-boot submit API elapsed roughly 14 to 10 us; throughput stayed near 20 us. Combined candidate C-d64 19.44 us, R-d32 41.48 us, hop 42.12 us; workload/Mesa dependent. | C/R/H; preserve wall-time versus thread-CPU distinction | +| [#423](https://github.com/aurora-silicon/linux/pull/423) | `9bf3039873a1`, completion identity index | Scanner self samples 4.87% to 2.24% at depth 64, 3.16% to 1.93% on hops. CPU profile result. | O/C/H | +| [#423](https://github.com/aurora-silicon/linux/pull/423) | `26446182da3a`, 128-interval validation working set | C-d64 API elapsed 10.06 to 7.46 us; R-d32 12.29 to 8.16 us. Not a thread-CPU clock measurement. | C/R/D with rotating 64 buffers and remap/permission invalidation | +| [#423](https://github.com/aurora-silicon/linux/pull/423) | `b6248acf900c`, private-fence fast status | Submit API median 7.52 to 5.79 us; frontier samples 1.83% to 0.25%; throughput remained near 19.5 us. | C/H/O; errors, foreign inputs and delayed host publication | +| [#440](https://github.com/aurora-silicon/linux/pull/440) | `d69f14f4e4fb`, GPU fabric floor | C-d1 158.9 to 108.2 us; C-d64 19.3 to 14.7 us; R-d32 42.7 to 29.7 us; hop 42.2 to 31.9 us. Same driver/Mesa. | P/C/R/H, compare g1/g2 and keep heavy/idle checks | +| [#219](https://github.com/aurora-silicon/linux/pull/219) | GFX report/PMP vote and PMC enables | Short manual-bit glmark2 build experiment: 1453 with GFX report off to 2199 with it on; 2218 with all interface enables. Single-run 7–10 s experiment, not an isolated final-kernel A/B. | P/R; distinguish reported request, enabled agent and honoured floor | +| [#443](https://github.com/aurora-silicon/linux/pull/443) | Lazy verification and fast switch | C-d2 58.8–59.7 to 46.4–47.3 us; C-d1 107.8–108.4 to 102.6–102.7 us. T8140-specific spin removed. | C/I observation only: T6040 already lacks that spin | +| [#413](https://github.com/aurora-silicon/linux/pull/413) | Existing optional idle hold | The roughly 1.1 ms wake and 1.2–1.4 ms tiny-render-after-gap observations are **M4 Pro**, not Neo measurements. Default 2 ms adds no extended hold. | I at defaults; optional hold is a separate policy experiment | + +The provided current Neo Linux/macOS report has no completed paired Linux +record, so an overall Neo margin is still undefined. Historical PR ratios do +not establish the requested current all-workload margin. Use matched suite, +Mesa, clock/power conditions and repeats. Run only workloads admitted by the +controller; queue depth is not permission for extra process workers. + +### Reachability and gate evidence keys + +| Key | Current source/gate and interpretation | +| --- | --- | +| SHARED — Shared G17 execution | driver.rs selects g17::Gpu for T6040. g17/queue/mod.rs, runtime.rs, port.rs, runtime/render/direct.rs and runtime/render/submit.rs have no T8140-only gate on these mechanisms. The variant supplies QID floor 32, producer +0x50, two-cluster operands and 25G83 lead commands. | +| DRM — Shared DRM/scheduler/fences | G17 queues use job.rs, fence.rs and the common DRM scheduler/syncobj/GPUVM/shmem interfaces. CPU_FREQ and UCLAMP_TASK are enabled in the release config. Private cumulative scopes are advertised on both G17-backend targets at feature bit 29; foreign fences retain generic handling. | +| MEM — Common memory and lifetime | file.rs::memory_stats, gem.rs, memacct.rs, cleanup.rs, pgtable.rs and mmu.rs serve the T6040 G17 backend. Creator accounting, pins, revocable backing, canonical bindings and deferred destruction are not gated to T8140. T6040 keeps its explicit UAT coherency policy. | +| GMEM — Shared retained GPU resources | g17/freelist.rs, context.rs, object.rs, queue/compute.rs and runtime/render/pools.rs retain and reclaim the same resource owners. Allocations use T6040 core-layout sizes and its reduced QID budget; no Neo-only admission branch disables them. | +| REC — Shared recovery and deadlines | g17/recovery.rs, recovery/safety.rs, timeout.rs and runtime/events.rs serve both firmware variants. T6040 uses its status-B base and one firmware role. Exact retirement, interrupted-work failure, withheld-doorbell replay, progress-aware budgets and sticky VM reporting remain active. Current missing-stamp fallback is 50 ms, not the historical 1 ms. | +| POWER — Shared feed/effort/idle code | g17/feed.rs is instantiated for T6040 and derives CPU floors from each discovered policy maximum. g17/mod.rs::note_submission/idle_check and power.rs share effort 1.0 while work streams and a 16 ms idle clear. The existing optional idle hold is available on T6040 but its default 2 ms adds no extended hold. | +| TIMESTAMP — Shared timestamp publication | timestamp.rs::FirmwareBuffer retains a CPU vmap for the buffer lifetime; g17 completion publishes through it before signalling. T6040 uses this shared firmware-buffer path and its 24 MHz timestamp conversion, not a per-command vmap. | +| PRIORITY — Shared firmware priority profiles | g17/mod.rs::scheduling_policy selects HIGH/MEDIUM/LOW profiles for both targets; file.rs checks CAP_SYS_NICE or DRM master for HIGH and refuses REALTIME. Priority work-ring selection and QoS ownership are shared. | +| FW — Per-chip firmware adaptation | hw/t6040.rs selects V25G83. g17/initdata/v25g83.rs supplies one merged root and status acknowledgement, per-chip OPP/calibration input, separate views and no POWER_ON_DONE. Pointer and operand layouts are selected from Config/Variant rather than copied from the two-instance 26A428 graph. MCache is explicitly gated by V25G83.mcache=false; its firmware table/protocol is not part of admitted T6040 support. | +| PMGR — Shared PMGR state handling | pmdomain/apple/pmgr-pwrstate.c binds T6041 states through the t8103-compatible state protocol. BUSY masking, timeout unwind and firmware-owned/no-auto-PM properties apply through DT; no T8140-only register address is copied. | +| PMP — PMP/report per-chip data | soc/apple/pmp.rs and pmdomain/apple/pmp-report.c already contain T6041 match data: 42-bit DMA, version-5 bootargs checks, CoreAnalytics EP7, T6041 report offsets and three fast-die lanes. T6040 DT supplies the bootargs/report entries and CPU effort phandles. T8132/J713-only telemetry policy is excluded separately. | +| PMC — Shared PMC, T6040 rail mask | pmdomain/apple/pmc.c::apple_pmc_probe/agent_enable use T6040 agent stride, DCS maximum 5 and agent_rails=GENMASK(1,0). CPU/CLPC/AP voting already worked. Port 772a9d3104ff adds apple,pmp and static voter 0 after its supplier binds; display rails remain excluded. | +| CLPC — Shared CLPC, T6040 demand data | soc/apple/clpc.c uses T6040 DCS {1,2,5,5}, fabric {0,1,2,2}, sustain=3, 4 ms sampling and 32 ms hold. CPU demand/lifecycle refinements are shared. GPU demand was missing and is enabled by 0d63308820ab with the existing apple,gpu-power-state phandle. | +| GPUFLOOR — Applicable gate ported | Before g2, apple_clpc_hw_t6040.gpu_demand was false and its CLPC DT node had no apple,gpu-power-state. Port 0d63308820ab enables the same mechanism as #440 with T6040 DCS 5/fabric 2, not Neo 4/3. Hardware gain and idle/PMP cooperation remain unqualified. | +| MCC — MCC translated to 16 planes | soc/apple/mcc.c accepts the T6041/T8140-compatible controller; T6040 DT describes 16 planes and its display boot-DMA dependency. The J616s enables this node. Generic partial-enable unwind, lifetime and SLC allocation rules apply with that data. | +| CPU — Shared CPU frequency/limit handling | T6041 CPU nodes bind the t8112 fallback profile. Generic OPP cleanup, fast-switch and PMP effort QoS use its DT frequencies and three effort lanes. No Neo frequency or thermal curve is copied. | +| LAZY — Different CPU verification protocol | The lazy_verify refinements are T8140-only. T6040 binds soc_t8112_info, whose verify_transition is false; policy->fast_switch_possible is already true. It does not pay the T8140 blocking verification loop that these commits remove. Adding lazy verification would not port a missing gain. | +| DART — Related DARTs; native GPU uses UAT | iommu/apple-dart.c common publication/error/resume/stream logic is used by T6041 DARTs where their DT ownership mode selects it. T6040 display mirroring uses its own compatible/data. Native GFX translations use asahi/mmu.rs UAT, so this is related PMP/display robustness, not a claim that Neo DART addresses fit the G16 GPU. | +| RTKIT — Shared RTKit transport/lifetime | G17 Coprocessor uses the Rust RTKit wrapper over soc/apple/rtkit.c. Shared buffer retention, barriers, crash handling, bounded logs and shutdown ownership apply. T6040 supplies its firmware-data crash buffers and one ASC instance; MTP/J713-only adoption is excluded. | +| MBOX — ASCWrap transport, per-chip registers | soc/apple/mailbox.c and rust/kernel/soc/apple/mailbox.rs provide the common queue, IRQ and receiver ownership rules. T6040 binds its ASCWrap-v6 data; its wrapper/VBAR windows and IRQs come from DT. Neo-specific mailbox addresses are not reused. | +| AIC — Shared effective-affinity reporting | irq-apple-aic.c applies AICv2+ reporting to the T6041 AIC3 compatible too. It reports actual effective routing rather than manufacturing CPU migration. Record the IRQ/worker CPU on J616s; no isolated throughput gain is claimed. | +| DISPLAY — Related shared display hardening | Generic apple/dcp.c, parser and AFK lifecycle/bounds fixes are inherited by the reconstructed display stack where those helpers are used. They protect the display consumer of GPU buffers; they are not standalone compute performance claims. | +| OBS — Observability only when selected | Existing G17 tracepoints/diagnostics and first-failure logging are shared. Trace register sampling is conditional on the tracepoint being enabled; disabled tracing is not a new runtime policy or a measured throughput gain. | +| MMIO — Different registration ABI | runtime/render.rs::install_pair returns before registration power holds when V25G83.mmio_pair_registration=false. 25G83 installs from queue configuration and uses lead commands; 26A428 uses MMIO pair registration. Do not add its two control round trips or register writes to the admitted 25G83 protocol. | +| FEW — 25G83 rejects this fast path | V25G83.few_primitives=false; command.rs and feature reporting enforce it. The supported-source comment records a fragment hang with the flag. It is not an applicable refinement of the admitted 25G83 render ABI without new validated facts. | +| RT — RT specialization not admitted | V25G83.ray_traversal=false; command.rs rejects native scratch/intersection programs and get_params omits the feature. Generic watchdog/recovery infrastructure remains shared, but native RT pools/kill guards/RT-specific long-job use are dormant on the admitted 25G83 path. | +| KEY — Missing validated recovery identities | hw/t6040.rs has recovery_slot_keys=None. recovery/attribution.rs falls back to firmware blame when slot identity evidence is incomplete. Neo slot-key addresses must not be invented for 25G83; this specific attribution refinement remains gated pending validated T6040 facts. | +| UAT — Qualified alternate invalidation policy | hw/t6040.rs keeps reserved_tables, clean_tables and page_tlbi=true. mem.rs::tlbi_range takes the page/ASID alternative before its range encoder. The range-only field fix is dormant; per-SoC coherency/ownership support is active. Removing these working firmware-table requirements is not a justified performance port. | +| DCS — Optional cooling actor, no T6040 curve | PMC cooling/IPA paths require #cooling-cells and power-model DT data. J616s does not select them, and the Neo DCS thermal zone was subsequently removed because it throttled unrelated GPU/CPU heat. No T6040 power curve is invented to re-enable that policy. | +| DCSOFF — Already equivalent inactive zone | Neo removed its oscillating DCS thermal zone. J616s has no such zone to remove. Its PMP CPU/GPU limits remain; this does not justify disabling thermal protection. | +| IRQENTRY — DT-selected report context | IRQ-safe report entries are selected by apple,irq-safe for PMGR-parent use. GFX report consumption does not require copying the external-display parent topology; use the actual T6040 entry/consumer hierarchy. | +| STAT — Not the Neo backend | StatsChannel/GpuManager agx_stats decoding belongs to the maintained backend. g17::Gpu::stats_snapshot returns None on both Neo and T6040. It is not a Neo optimization waiting to be ported to G16S. | +| G15 — Different backend/hardware | T6030/T6031/T8122/G15 and M3-only runtime commits do not execute in T6040 g17::Gpu. Their firmware records, generation-specific power policy and native-G15 admission cannot be copied into G16S/25G83. | +| G16 — Different G16 backend/owner ABI | T8132/J713 uses the separate g16 backend and retained-owner PMP/TVM protocol. T6040 is G16S but dispatches to the g17 backend with V25G83, not that G16G runtime. Match/board gates and firmware ownership differ. | +| LEGACY — Maintained G13/G14 path | The workqueue/manager/versioned AGX firmware path is not T6040's G17 backend. G13/G14 automatic preemption, timestamp-copy and old InitData changes are not missing G16S mechanisms. | +| OTHERCHIP — Other CPU/guest hardware | T8142/T8152/T6050-specific transition, ownership or guest-timer data is not selected by T6041 CPU/GPU match data. No unsupported register/profile change is ported. | +| TUNNEL — Other peripheral topology | M1 tunneled PCIe/Thunderbolt recovery is not a Neo native-GPU improvement. T6040 has its own topology; these records are retained for coverage, not presented as a gated G16S optimization. | +| PERIPH — Not a GPU refinement | This scoped-history/PR neighbor concerns NVMe, HID, audio, SEP, USB, RTC or another peripheral, not a Neo native-GPU performance/handling mechanism. No port is implied. | +| BOARD — Neo-specific board route | The literal J700 topology/display source/domain-cell fix is not T6040 board data. Its independent J616s display/PMP/GPU composition is retained; copying a Neo route or physical address would be wrong. | +| RAWDIAG — Superseded raw diagnostics | The raw T8140 PMP DVFS debugfs dump was later removed. Current reporting uses validated per-SoC fields; no new raw interface or platform knob is added. | +| OLDTHERMAL — Superseded thermal policy | The interim J700 SMC CPU policy was removed/replaced by thermal-framework and PMP handling. It is not the policy selected for T6040 and no Neo temperature/power curve is copied. | +| SUPERSEDED — Superseded inside the series | The unconditional-doorbell/unbound-inline policies were refined by later #423 commits; the early disabled-J616s admission was replaced by calibrated #463 enablement. Current code preserves low-pressure probes and defers/coalesces eligible doorbells with explicit release/stall/recovery handling; do not reapply the obsolete intermediate policy. | +| VERIFY — Build/test/documentation only | This record changes tests, descriptions, bindings, layout/build checks or source style rather than a runtime performance mechanism. Its contract is retained where applicable; there is no isolated hardware speedup or new platform behavior to port. | +| MERGE — Integration record | This merge is provenance/composition, not an isolated performance mechanism. Its parent changes are enumerated below, including variant preservation and subsequent fixes; no separate speedup is assigned to a merge. | + +### Hardware measurements referenced by each commit + +All hardware actions require the controller's explicit J616s identity, policy +approval and ownership/session/resource gates. These are a measurement plan, +not permission to bypass them. Pair g1/g2 with the same loader and Mesa and +preserve failed runs, logs and r6g rollback. + +| Code | Measurement | +| --- | --- | +| C | compute.light d1/d2/d64; submit API wall and thread-CPU time, scheduler hops, faults and CPU-frequency/IRQ context; matching Mesa/suite. | +| R | render.light d1/d2/d32 and 8 queues, first render, heavy fill and tiler growth; API p50/p99, throughput and memory high-water. | +| H | same/cross-queue binary and timeline hops; p50/p95/p99, doorbells per command and 100k order/data checks; include eligible and fallback dependencies. | +| I | idle.wake after 2/20/100 ms, default policy, first-frame latency and clocks; 60/120 Hz frame pacing and idle power. Keep optional hold experiments separate. | +| F | controller-approved bounded stall/fault/long-job stress with innocent peers; no wrong data or false successful fence, recovery phase/time, VM errors and post-close reuse. | +| M | multi-client create/close/rearm and approved bounded pressure; committed/resident bytes, retained pools/QIDs, restoration time and leak slope. | +| P | PMP readiness, PMC per-agent requests/honoured floors and CLPC readbacks during GPU-only, mixed CPU/GPU, 90 s heavy load, idle, video and browsing; verify idle release and no sustained thermal regression. | +| T | timestamp/query correctness, 24 MHz conversion, aligned/unaligned publication where supported, buffer-map reuse and query CPU cost. | +| Q | privileged/unprivileged priority admission and concurrent LOW/MEDIUM/HIGH progress, global-priority transition cases and timeout attribution. | +| D | approved bounded VM_BIND/unmap/remap and DART ownership/resume boundaries; CPU cost, invalidation errors and zero unexpected DART/UAT faults. | +| B | exact boot/kernel/loader/Mesa pins, firmware admission, first queue/install, panel/display regression, supplier readiness and rollback. | +| O | existing tracepoints/counters under approved instrumentation; IRQ and event-worker CPU, pass duration, notifications and failure site. Tracing off remains the reference. | +| V | static/build/contract or capability-refusal check; no isolated hardware improvement claimed. | + +### Every commit in #423 + +All 65 entries are retained, including intermediate policies, tests and the +integration merge. Superseded steps are not mistaken for current behavior. + +| Commit | Author | Improvement / record | T6040 after ports — evidence key and changed source | Measurement | +| --- | --- | --- | --- | --- | +| [ad691cd5e9f7](https://github.com/aurora-silicon/linux/commit/ad691cd5e9f76aa9e486d93b560c58d5c8ff88c4) | DjDeveloperr | drm/asahi: publish bound G17 compute work without the admission passes [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/completion.rs` | C,H | +| [1121ae36b8d4](https://github.com/aurora-silicon/linux/commit/1121ae36b8d426be24c41ae360281b5f0709a69d) | DjDeveloperr | drm/asahi: wake G17 render publication when its pair is returned [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/mod.rs` | R,H | +| [abd85935bd4d](https://github.com/aurora-silicon/linux/commit/abd85935bd4db7db6be23b8194b1173c9a8988c5) | DjDeveloperr | drm/asahi: clear each G17 compute descriptor only once [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/fw/compute.rs` | C,H | +| [3ef3720890f8](https://github.com/aurora-silicon/linux/commit/3ef3720890f8c085f18e4f5e35db6853075b121c) | DjDeveloperr | drm/asahi: retain compute pool backing through pending channel admission [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — GMEM; `drivers/gpu/drm/asahi/g17/capacity.rs` | M,C,R | +| [f35d7ea1b373](https://github.com/aurora-silicon/linux/commit/f35d7ea1b37395a9bc1241d0fafcb698e0c12d46) | DjDeveloperr | dma-buf: stop polling an array after its first pending member [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — DRM; `drivers/dma-buf/dma-fence-array.c` | C,H,M,F | +| [319805fed41b](https://github.com/aurora-silicon/linux/commit/319805fed41bb181db8e90378beaf905f38f6297) | DjDeveloperr | drm/asahi: avoid unbound compute inline attempts on compute-only queues [#423](https://github.com/aurora-silicon/linux/pull/423) | Not applicable — SUPERSEDED; `drivers/gpu/drm/asahi/g17/context.rs` | C,H | +| [5a4822c6fb3d](https://github.com/aurora-silicon/linux/commit/5a4822c6fb3d1fc0a83d9b754b477bf1db35d782) | DjDeveloperr | drm/asahi: preserve low-pressure compute inline probes [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/runtime.rs` | C,H | +| [026065c43526](https://github.com/aurora-silicon/linux/commit/026065c435260844e39d05a182e99b128c5f9b2d) | DjDeveloperr | drm/asahi: submit same-VM compute input waits through firmware [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/capacity.rs` | C,H | +| [89c8873651b7](https://github.com/aurora-silicon/linux/commit/89c8873651b729fdd4e981e87b9d0df0d0ad0017) | DjDeveloperr | drm/asahi: treat a firmware source as complete once its retirement is queued [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/completion.rs` | C,R,H | +| [3ff4f4baa05a](https://github.com/aurora-silicon/linux/commit/3ff4f4baa05a88d5f99d2fee76089cf72b5617a4) | DjDeveloperr | drm/asahi: publish G17 commands from the ioctl at any queue depth [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/mod.rs` | C,R,H | +| [a0dfc3c0b5a4](https://github.com/aurora-silicon/linux/commit/a0dfc3c0b5a4fced66119fded07ed141990a9025) | DjDeveloperr | drm/asahi: map G17 compute descriptors cached for the firmware [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/compute.rs` | C,H | +| [a36e352d8564](https://github.com/aurora-silicon/linux/commit/a36e352d8564947071fef7e11dba709f399cd106) | DjDeveloperr | drm/asahi: size the G17 compute QID budget by open logical queues [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/kick.rs` | C,H | +| [b141ca9b16c3](https://github.com/aurora-silicon/linux/commit/b141ca9b16c33abb26238782cd7c0b751b5f52fe) | DjDeveloperr | drm/asahi: order same-VM render attachment overlaps in firmware [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/completion.rs` | R,H | +| [a475f4f9f1f7](https://github.com/aurora-silicon/linux/commit/a475f4f9f1f721b6636a4147dacbcf9f6224763b) | DjDeveloperr | drm/asahi: map G17 compute command records cached for the firmware [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/compute.rs` | C,H | +| [ca71e4449b4a](https://github.com/aurora-silicon/linux/commit/ca71e4449b4a23d5f4e0149db76cf5198bfa7e5f) | DjDeveloperr | drm/asahi: stop the G17 compute completion scan once every record is found [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/initdata.rs` | C,H | +| [1cdac9ba0ddd](https://github.com/aurora-silicon/linux/commit/1cdac9ba0dddec96f59eb02a5067ee343aae43f4) | DjDeveloperr | drm/asahi: publish G17 compute into a bound queue without the device mutex [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/context.rs` | C,H | +| [7242bf87efd2](https://github.com/aurora-silicon/linux/commit/7242bf87efd2b3f4f8bf2a31fadb79b3e687f195) | DjDeveloperr | drm/asahi: send a G17 compute queue configuration only when it changes [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/compute.rs` | C,H | +| [014605a36795](https://github.com/aurora-silicon/linux/commit/014605a3679581351913c3ab137653297f7a0cf7) | DjDeveloperr | drm/asahi: add G17 resource wait domains and use them for queue creation [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/availability.rs` | C,R,H | +| [64f717dcfd1e](https://github.com/aurora-silicon/linux/commit/64f717dcfd1e4aa5856882181f5c2ac040c5ce4e) | DjDeveloperr | drm/asahi: wait for G17 compute capacity on the release that frees it [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/availability.rs` | C,H | +| [327e50fc45b5](https://github.com/aurora-silicon/linux/commit/327e50fc45b57acf1954e8c2d78977198286feb0) | DjDeveloperr | drm/asahi: wait for a G17 compute source only through its completion record [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/runtime.rs` | C,H | +| [6f4d3bbb47c1](https://github.com/aurora-silicon/linux/commit/6f4d3bbb47c1a7b19abef118b41b131440a8f414) | DjDeveloperr | drm/asahi: add G17 command lifecycle tracepoints [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — OBS; `drivers/gpu/drm/asahi/g17/mod.rs` | O,F | +| [e24e3bad9e7d](https://github.com/aurora-silicon/linux/commit/e24e3bad9e7d3c6cf7079d683c9364d619cd3a55) | DjDeveloperr | drm/asahi: ring the G17 compute pipe doorbell for every publication [#423](https://github.com/aurora-silicon/linux/pull/423) | Not applicable — SUPERSEDED; `drivers/gpu/drm/asahi/g17/queue/compute.rs` | C,H | +| [94c66ecf24e6](https://github.com/aurora-silicon/linux/commit/94c66ecf24e6d931a434914d6a513ee36ff6ffd7) | DjDeveloperr | drm/asahi: map the G17 compute queue graph cached for the firmware [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/compute.rs` | C,H | +| [db365ecc299f](https://github.com/aurora-silicon/linux/commit/db365ecc299f0663db8a629e70dfdb4d117eb95e) | DjDeveloperr | drm/asahi: wait for G17 render capacity on the release that frees it [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/context.rs` | R,H | +| [06bc290c266a](https://github.com/aurora-silicon/linux/commit/06bc290c266abcb8b0d45f75c3369e231ae8c4a7) | DjDeveloperr | drm/asahi: give the G17 render scheduler the pair's depth as credits [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/job.rs` | R,H | +| [3d1f2047766c](https://github.com/aurora-silicon/linux/commit/3d1f2047766c2d6e8d165a4af2cfe4adc0edd366) | DjDeveloperr | drm/asahi: keep a G17 render free list registered while the firmware is busy [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/freelist.rs` | R,H | +| [b7fa0d482eea](https://github.com/aurora-silicon/linux/commit/b7fa0d482eea670f9004422eaf57703a7ec8d4e5) | DjDeveloperr | drm/asahi: take the G17 device mutex less often to publish a render [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/admission.rs` | R,H | +| [d272a7060d20](https://github.com/aurora-silicon/linux/commit/d272a7060d20a0beb635ca8426c8822b8e823a43) | DjDeveloperr | drm/asahi: publish a G17 render into its pair without the device mutex [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/initdata.rs` | R,H | +| [6428f7fd42fe](https://github.com/aurora-silicon/linux/commit/6428f7fd42fe075981984da569b0fa076f59a127) | DjDeveloperr | drm/asahi: lend a G17 render pair without the identity check first [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/runtime.rs` | R,H | +| [8cf02f589bce](https://github.com/aurora-silicon/linux/commit/8cf02f589bce34b13002cbdf0f3b0361c4a8e66b) | DjDeveloperr | drm/asahi: wait for G17 render admission before run_job [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/admission.rs` | R,H | +| [550fb355e9a3](https://github.com/aurora-silicon/linux/commit/550fb355e9a30c0db9c59fe35a931f1db9c6b5c4) | DjDeveloperr | drm/asahi: trace the G17 core power state at each work doorbell [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — OBS; `drivers/gpu/drm/asahi/g17/mod.rs` | O,F | +| [f381ac9b71eb](https://github.com/aurora-silicon/linux/commit/f381ac9b71ebcb70eb72497ec4115553856a31fc) | DjDeveloperr | drm/asahi: trace G17 host-to-firmware messages [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — OBS; `drivers/gpu/drm/asahi/g17/channel.rs` | O,F | +| [adcd56e02036](https://github.com/aurora-silicon/linux/commit/adcd56e020362664c7bc3af288e1ad812df7b4c7) | DjDeveloperr | drm/asahi: maintain cumulative completion scopes without repeated fence merges [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — DRM; `drivers/gpu/drm/asahi/g17/fence.rs` | C,H,M,F | +| [bcdfdafd80cf](https://github.com/aurora-silicon/linux/commit/bcdfdafd80cf93fadc79df688dd0109b9c4ae8d2) | DjDeveloperr | drm/asahi: test G17 queue budgets with firmware-reserved identities [#423](https://github.com/aurora-silicon/linux/pull/423) | Not applicable — VERIFY; `drivers/gpu/drm/asahi/g17/kick.rs` | V | +| [971d4316d91c](https://github.com/aurora-silicon/linux/commit/971d4316d91cce97b92db72c4f29e5155b76d626) | DjDeveloperr | drm/asahi: size cached compute records for every native wait [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/compute.rs` | C,H | +| [9bf3039873a1](https://github.com/aurora-silicon/linux/commit/9bf3039873a1e241991b5999aceff5610c660ade) | DjDeveloperr | drm/asahi: index pending compute completion identities before reading payloads [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/compute/retirement.rs` | C,H | +| [26446182da3a](https://github.com/aurora-silicon/linux/commit/26446182da3aa8d98ce799478a025117b021eaba) | DjDeveloperr | drm/asahi: retain a deep working set of validated GPU page ranges [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/validation.rs` | C,R,D | +| [b6248acf900c](https://github.com/aurora-silicon/linux/commit/b6248acf900c1cf30deac52791c2c50d10055d7b) | DjDeveloperr | drm/asahi: avoid locking pending private completion fences [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/job.rs` | C,R,H | +| [b08c81722d89](https://github.com/aurora-silicon/linux/commit/b08c81722d896eee8da9771e99e38e6986a2a806) | DjDeveloperr | drm/asahi: give G17 firmware restarts time to drain long work [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — REC; `drivers/gpu/drm/asahi/g17/recovery.rs` | F,Q | +| [f1db16a83586](https://github.com/aurora-silicon/linux/commit/f1db16a83586e4bec596a71e12e0d8b87dd3b16c) | DjDeveloperr | drm/asahi: fail commands a GPU restart interrupted instead of completing them [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — REC; `drivers/gpu/drm/asahi/g17/completion.rs` | F,Q | +| [30f85560fd28](https://github.com/aurora-silicon/linux/commit/30f85560fd28ce05151c16eab7a58433d125a81e) | DjDeveloperr | drm/asahi: stop the G17 device on a watchdog storm only without progress [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — REC; `drivers/gpu/drm/asahi/g17/queue/compute.rs` | F,Q | +| [2c0abe2d090a](https://github.com/aurora-silicon/linux/commit/2c0abe2d090a9701b13b22d5ac56fb24d87cfe12) | DjDeveloperr | drm/asahi: scan only the G17 compute completion records written since the last pass [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/initdata.rs` | C,H | +| [39bdd5b8e2d5](https://github.com/aurora-silicon/linux/commit/39bdd5b8e2d584fff0d342a71a8945e64c8a9673) | DjDeveloperr | drm/asahi: release pruned G17 frontier entries after the ioctl publishes [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/mod.rs` | C,R,H | +| [b41ecb56c077](https://github.com/aurora-silicon/linux/commit/b41ecb56c0775a43b747adaa37661b353651dbff) | DjDeveloperr | drm/asahi: publish both G17 render QoS owners in one pass [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/port.rs` | R,H | +| [b86c445ba282](https://github.com/aurora-silicon/linux/commit/b86c445ba2829f644075ca663217abe65844e8c0) | DjDeveloperr | drm/asahi: stop copying G17 fragment registers to the unused client page [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/render/memory.rs` | C,R,H | +| [5a55068193e2](https://github.com/aurora-silicon/linux/commit/5a55068193e213d7115a63f2222ffc304a4ced98) | DjDeveloperr | drm/asahi: read the G17 render work rings once per direct publication [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/port.rs` | R,H | +| [b67018ed5974](https://github.com/aurora-silicon/linux/commit/b67018ed5974069c13c13249834dbc3b84e806ed) | DjDeveloperr | drm/asahi: request the G17 tiling descriptor slot before preparing a render [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/render.rs` | R,H | +| [4d0362d0c18d](https://github.com/aurora-silicon/linux/commit/4d0362d0c18d288d3bd520fdfb69c2e7624cf6b0) | DjDeveloperr | drm/asahi: store G17 register arrays without an intermediate copy [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/fw/kick.rs` | C,R,H | +| [861b39d948f6](https://github.com/aurora-silicon/linux/commit/861b39d948f6c0588b3646e1246e660b52230e5b) | DjDeveloperr | drm/asahi: also request the G17 queue configuration records early [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/render/memory.rs` | C,R,H | +| [66ef051d8bd4](https://github.com/aurora-silicon/linux/commit/66ef051d8bd4e66bdc145eb7fb7a920235a8e668) | DjDeveloperr | drm/asahi: scan only render pool slots for G17 free-list leases [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — GMEM; `drivers/gpu/drm/asahi/g17/runtime/render/pools.rs` | M,C,R | +| [1aec5be232da](https://github.com/aurora-silicon/linux/commit/1aec5be232da7bacbbe38b7f9b22c27906eac8c6) | DjDeveloperr | drm/asahi: omit G17 render queue configurations that only reload the held completion [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/render.rs` | R,H | +| [d0df244b8f02](https://github.com/aurora-silicon/linux/commit/d0df244b8f023037bb0f9cc9793f1d0f285cfc87) | DjDeveloperr | drm/asahi: keep one CPU mapping per G17 timestamp buffer [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — TIMESTAMP; `drivers/gpu/drm/asahi/g17/completion.rs` | T,C | +| [3ff8c49c5f12](https://github.com/aurora-silicon/linux/commit/3ff8c49c5f12b908a72e6c076ff0d7336ffddcb8) | DjDeveloperr | drm/asahi: withhold G17 compute doorbells behind two unretired commands [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/compute.rs` | C,H | +| [b1f5dc6df0ba](https://github.com/aurora-silicon/linux/commit/b1f5dc6df0ba5650c8424027a025db233c6544c4) | DjDeveloperr | drm/asahi: drop G17 compute doorbells that another doorbell announced [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/port.rs` | C,H | +| [d38cce2d8be5](https://github.com/aurora-silicon/linux/commit/d38cce2d8be54bdbc01e90e1a92ef3dd65ac0640) | DjDeveloperr | drm/asahi: announce withheld G17 compute doorbells whenever retirement stalls [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/compute.rs` | C,H | +| [2d8286101c86](https://github.com/aurora-silicon/linux/commit/2d8286101c8631939cb13ce1b7b14c9041856ca6) | DjDeveloperr | drm/asahi: build the G17 driver without warnings [#423](https://github.com/aurora-silicon/linux/pull/423) | Not applicable — VERIFY; `drivers/gpu/drm/asahi/g17/queue/compute.rs` | V | +| [9a0ed807e4d3](https://github.com/aurora-silicon/linux/commit/9a0ed807e4d3691a5ea46fce2a2424295f8af6ea) | DjDeveloperr | drm/asahi: renew G17 job deadlines while the GPU keeps retiring work [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — REC; `drivers/gpu/drm/asahi/g17/job.rs` | F,Q | +| [afbfbc0b89ca](https://github.com/aurora-silicon/linux/commit/afbfbc0b89ca4779b13cefff5bed0b4036751b2c) | DjDeveloperr | drm/asahi: kill closed G17 contexts only while no other work runs [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — REC; `drivers/gpu/drm/asahi/g17/runtime/compute_exit.rs` | F,Q | +| [31279abf0648](https://github.com/aurora-silicon/linux/commit/31279abf0648c00e454b181f01d65b277a32fb90) | DjDeveloperr | drm/asahi: fail G17 render groups a recovery dropped out of order at once [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — REC; `drivers/gpu/drm/asahi/g17/queue/render.rs` | F,Q | +| [7012a59ed129](https://github.com/aurora-silicon/linux/commit/7012a59ed129baf969d83a3aa4d90f2a6cb410f2) | DjDeveloperr | drm/asahi: announce withheld G17 compute doorbells after a recovery [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — REC; `drivers/gpu/drm/asahi/g17/recovery.rs` | F,Q | +| [8e257af33a6c](https://github.com/aurora-silicon/linux/commit/8e257af33a6c4036ee56bc8a628e733dba2bd525) | DjDeveloperr | drm/asahi: move the G17 cumulative-signals feature to bit 29 [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/sync_output.rs` | C,R,H | +| [76d4e880149e](https://github.com/aurora-silicon/linux/commit/76d4e880149e5592396001f728f7803efb616377) | DjDeveloperr | drm/asahi: build the G17 compute QID budget test against the variant constructor [#423](https://github.com/aurora-silicon/linux/pull/423) | Not applicable — VERIFY; `drivers/gpu/drm/asahi/g17/runtime.rs` | V | +| [d0da7e79dc9c](https://github.com/aurora-silicon/linux/commit/d0da7e79dc9c99020d109e95d18256cd5b3d6bc2) | DjDeveloperr | drm/asahi: check the G17 QID floor test against the open-queue compute budget [#423](https://github.com/aurora-silicon/linux/pull/423) | Not applicable — VERIFY; `drivers/gpu/drm/asahi/g17/runtime.rs` | V | +| [18b408b7368b](https://github.com/aurora-silicon/linux/commit/18b408b7368b9470c22d92c9afe1991d0fe608ba) | DjDeveloperr | drm/asahi: check the G17 admission churn test against its deadline, not the clock [#423](https://github.com/aurora-silicon/linux/pull/423) | Not applicable — VERIFY; `drivers/gpu/drm/asahi/g17/preparation.rs` | V | +| [de1be1b4b5b1](https://github.com/aurora-silicon/linux/commit/de1be1b4b5b110ae60877f77aa1637a920333579) | DjDeveloperr | Merge integration/aurora into fix/g17-recovery-dropped-groups-20261025 [#423](https://github.com/aurora-silicon/linux/pull/423) | Not applicable — MERGE | V | + +### Other Neo and shared-support commits + +Each listed alias has the stated current mechanism/gate. Device-specific data +uses the T6040 counterpart, not literal Neo register/DT values. + +| Commits / encountered aliases | Author | Improvement / record | T6040 after ports — evidence key and changed source | Measurement | +| --- | --- | --- | --- | --- | +| [6a5d96e61fa5](https://github.com/aurora-silicon/linux/commit/6a5d96e61fa52810e709a0b8bb5e9988df78a7d7) | Sasha Finkelstein | cpufreq: apple-soc: Calculate frequency as a 64-bit value | Active — CPU; `drivers/cpufreq/apple-soc-cpufreq.c` | C,I,P | +| [a861779b92c2](https://github.com/aurora-silicon/linux/commit/a861779b92c259c0a5ba7ec3fd512ad7bda1b8d4) | Asahi Lina | rust: soc: apple: rtkit: Add Apple RTKit abstraction | Active — RTKIT; `drivers/soc/apple/Kconfig` | B,F | +| [4b42187aef53](https://github.com/aurora-silicon/linux/commit/4b42187aef53c266b472e731e4f8e0a1fe0de3a7) | Janne Grunau | rust: soc: apple: rtkit: Add ASC CPU control register | Active — RTKIT; `rust/kernel/soc/apple/rtkit.rs` | B,F | +| [6a2c57b5a719](https://github.com/aurora-silicon/linux/commit/6a2c57b5a719f7c71fe6252865e4f036f6d27a64) | Sasha Finkelstein | soc: apple: rtkit: Add apple_rtkit_has_endpoint() | Active — RTKIT; `drivers/soc/apple/rtkit.c` | B,F | +| [b7fbb2fde4ea](https://github.com/aurora-silicon/linux/commit/b7fbb2fde4eaa0ac53e46dcb76a835fdc9cd9477) | Sasha Finkelstein | rust: soc: apple: rtkit: Add apple_rtkit_has_endpoint | Active — RTKIT; `rust/kernel/soc/apple/rtkit.rs` | B,F | +| [bba00880a59b](https://github.com/aurora-silicon/linux/commit/bba00880a59b144155716928066b12cc5751dd52) | Sasha Finkelstein | rust: soc: apple: Add Apple mailbox abstractions | Active — MBOX; `drivers/soc/apple/Kconfig` | B,O | +| [883657fe64b3](https://github.com/aurora-silicon/linux/commit/883657fe64b3a62fa36c90666b0a00a4e5093166) | Sasha Finkelstein | soc: apple: Add support for the PMP co-processor | Active — PMP; `drivers/soc/apple/Kconfig` | P,B | +| [fccd1dbe8ba8](https://github.com/aurora-silicon/linux/commit/fccd1dbe8ba820d4d9f5d1a624987e2820035204), [04841b120bbf](https://github.com/aurora-silicon/linux/commit/04841b120bbf7a2fb454cbfa984647e867b64848), [da7aa98b1aae](https://github.com/aurora-silicon/linux/commit/da7aa98b1aae834db5f7f4a8396afc39099d1b2f) | Janne Grunau | fixup! soc: apple: Add support for the PMP co-processor | Active — PMP; `drivers/soc/apple/pmp.rs` | P,B | +| [c81971a6c4c7](https://github.com/aurora-silicon/linux/commit/c81971a6c4c706b2f5783653a2df8d79782d4efe) | Asahi Lina | drm/scheduler: Fix UAF in drm_sched_fence_get_timeline_name | Active — DRM; `drivers/gpu/drm/scheduler/sched_entity.c` | C,H,M,F | +| [94bfb56baeeb](https://github.com/aurora-silicon/linux/commit/94bfb56baeeb02bc9c9ce8616aef0bd72a5de3f2) | Asahi Lina | rust: drm: sched: Add GPU scheduler abstraction | Active — DRM; `rust/kernel/drm/mod.rs` | B,M | +| [1340031e447c](https://github.com/aurora-silicon/linux/commit/1340031e447c56ec9d0f8ea7a68436a623fafc91) | Asahi Lina | drm/asahi: Add the Asahi driver for Apple AGX GPUs | Active — MEM; `drivers/gpu/drm/Kconfig` | M,D | +| [af74e2af96bc](https://github.com/aurora-silicon/linux/commit/af74e2af96bc483417f5015f98f4aa54e355851d) | Janne Grunau | drm/asahi: v6.19 probe change | Active — MEM; `drivers/gpu/drm/asahi/driver.rs` | M,D | +| [8c048a0ab849](https://github.com/aurora-silicon/linux/commit/8c048a0ab84971117a5f641ba2185b51e0b12059) | Janne Grunau | drm/asahi: Move unsafe data initialization to driver code | Active — MEM; `drivers/gpu/drm/asahi/driver.rs` | M,D | +| [a95f0620d24c](https://github.com/aurora-silicon/linux/commit/a95f0620d24c456c74d11261e329df054bf70e7b) | Janne Grunau | drm/asahi: RiiR page tables | Active — MEM; `drivers/gpu/drm/asahi/alloc.rs` | M,D | +| [19e9dc7ccb94](https://github.com/aurora-silicon/linux/commit/19e9dc7ccb9499ac3c92e7764edc2312656dc9d5) | Janne Grunau | drm/asahi: Implement ASAHI_BIND_SINGLE_PAGE (mmu/pgtbl) | Active — MEM; `drivers/gpu/drm/asahi/mmu.rs` | M,D | +| [96ce16e432a2](https://github.com/aurora-silicon/linux/commit/96ce16e432a213f7ae185870826697900c9acc60) | Asahi Lina | drm/asahi: pgtable: Add dumper | Active — MEM; `drivers/gpu/drm/asahi/pgtable.rs` | M,D | +| [ad2fb060c5dc](https://github.com/aurora-silicon/linux/commit/ad2fb060c5dcce503bc099062ff54c237b6866d0) | Asahi Lina | drm/asahi: pgtable: Add helpers for decoding PTE perms | Active — MEM; `drivers/gpu/drm/asahi/pgtable.rs` | M,D | +| [7b55670c3a59](https://github.com/aurora-silicon/linux/commit/7b55670c3a59219e37192a1f5771a86217008808) | Janne Grunau | drm/asahi: Copy tyr's mmu/vm/range.rs | Active — MEM; `drivers/gpu/drm/asahi/asahi.rs` | M,D | +| [9d16f582e208](https://github.com/aurora-silicon/linux/commit/9d16f582e2085935f893f99326fbcc0346c998bb) | Janne Grunau | drm/asahi: Switch gpuvm to DRM_GPUVM_IMMEDIATE_MODE | Active — MEM; `drivers/gpu/drm/asahi/file.rs` | M,D | +| [829551e64f1a](https://github.com/aurora-silicon/linux/commit/829551e64f1a350adddb71751be16a0243e1b666) | Janne Grunau | drm/asahi: MemFlag change in v6.19 | Active — MEM; `drivers/gpu/drm/asahi/gpu.rs` | M,D | +| [f5b02e71cd67](https://github.com/aurora-silicon/linux/commit/f5b02e71cd671ac4e3ceff54bbd1b25e7f20ae9d) | Janne Grunau | drm/asahi: rtkit pin adaptation | Active — MEM; `drivers/gpu/drm/asahi/gpu.rs` | M,D | +| [4220285e8415](https://github.com/aurora-silicon/linux/commit/4220285e8415cc772736b57f0dface3dcdbbee4f) | Janne Grunau | drm/asahi: select APPLE_PMP_REPORT | Active — PMP; `drivers/gpu/drm/asahi/Kconfig` | P,B | +| [30d957799282](https://github.com/aurora-silicon/linux/commit/30d9577992828e8d2f8345cc4b34a19098ca068c) | Janne Grunau | drm/asahi: Improve VM bind performance for large BOs | Active — MEM; `drivers/gpu/drm/asahi/mmu.rs` | M,D | +| [22e9a0b69598](https://github.com/aurora-silicon/linux/commit/22e9a0b69598bc23e66d72d1e51bd40396966dc1) | Janne Grunau | drm/asahi: `use` formatting annd rename changes | Active — MEM; `drivers/gpu/drm/asahi/driver.rs` | M,D | +| [f014fcba9d71](https://github.com/aurora-silicon/linux/commit/f014fcba9d71972198f28c3d5315605669073bea) | Janne Grunau | drm/asahi: v7.1 Mmio relaxed changes | Active — MEM; `drivers/gpu/drm/asahi/regs.rs` | M,D | +| [20dec7b1eaa9](https://github.com/aurora-silicon/linux/commit/20dec7b1eaa9a31dc67a8216d338bb10a452659d) | DesktopECHO | drm/asahi: Clean up deferred BOs when dropping a VM | Active — MEM; `drivers/gpu/drm/asahi/file.rs` | M | +| [e07ff81ee812](https://github.com/aurora-silicon/linux/commit/e07ff81ee812fd84603a498beaa1bb25eb629caa) | Janne Grunau | fixup! rust: soc: apple: rtkit: Add Apple RTKit abstraction | Active — RTKIT; `rust/kernel/soc/apple/rtkit.rs` | B,F | +| [44a01c258b11](https://github.com/aurora-silicon/linux/commit/44a01c258b111368f3c75848553ae1a658f46013) | Janne Grunau | drm/asahi: Adjustment for v7.2 + partial drm-rust v7.3 | Active — MEM; `drivers/gpu/drm/asahi/alloc.rs` | M,D | +| [23b564dd6c09](https://github.com/aurora-silicon/linux/commit/23b564dd6c09982106e2b5cc5ce80c25010c354a) | Janne Grunau | drm/asahi: pgtable: Drop more dev coredump related code | Active — MEM; `drivers/gpu/drm/asahi/pgtable.rs` | M,D | +| [b7f0faf4da36](https://github.com/aurora-silicon/linux/commit/b7f0faf4da367be4a29146f5eb82b795dc2f270d), [afffea42ac29](https://github.com/aurora-silicon/linux/commit/afffea42ac29a1f6f8b3ef3819691c1d7b0a259a) | Hojun Cho | pmdomain: apple: Add missing spin_lock_init | Active — PMP; `drivers/pmdomain/apple/pmp-report.c` | P,B | +| [0de0a3740a9a](https://github.com/aurora-silicon/linux/commit/0de0a3740a9a26973e7e49ad1cd87981914c457a), [919b934ff719](https://github.com/aurora-silicon/linux/commit/919b934ff7191fa72fab73ecf4ac785dd570c3c3), [ae512e4edf8b](https://github.com/aurora-silicon/linux/commit/ae512e4edf8b96d5eacd79b11fb190837d311b1b) | Ryan Murray | arm64: dts: apple: t8140-pmgr: Add ANS, SPMI, PMP and SMC I2C domains | Active — PMP | P,B | +| [2740578bc68c](https://github.com/aurora-silicon/linux/commit/2740578bc68c410528172a54f9e2e19d35c255d1), [c96a7ffae393](https://github.com/aurora-silicon/linux/commit/c96a7ffae39306507c405d49a3a19927eab289fe), [5578a4e73d10](https://github.com/aurora-silicon/linux/commit/5578a4e73d10d63615ed1eaaf5a7215310df19b8) | Ryan Murray | arm64: dts: apple: t8140: Add CPU frequency scaling | Active translated data — FW | B,P | +| [e7e641d03183](https://github.com/aurora-silicon/linux/commit/e7e641d03183aa76578a1504a4758da6b71c968f), [b58c399f44d7](https://github.com/aurora-silicon/linux/commit/b58c399f44d7499ed62c907efda7b0017f421baf), [f9ec3820d63d](https://github.com/aurora-silicon/linux/commit/f9ec3820d63d57ddb9790ac168a5642efc11f21c) | Ryan Murray | arm64: dts: apple: t8140: Use the Tahiti CPU compatibles and L1 cache sizes | Active translated data — FW | B,P | +| [c702ccc166bb](https://github.com/aurora-silicon/linux/commit/c702ccc166bbe593718a8b4d8a51063e6c5bc102), [f104bb09ce2a](https://github.com/aurora-silicon/linux/commit/f104bb09ce2abf48baa4cf5028b9224141cfdd00), [118ffba5b7b0](https://github.com/aurora-silicon/linux/commit/118ffba5b7b06b9786e24dfab819caa1fb1a2333) | Ryan Murray | arm64: dts: apple: t8140-j700: Enable ANS, SMC and CPU frequency scaling | Active translated data — FW | B,P | +| [5e5d5289f17e](https://github.com/aurora-silicon/linux/commit/5e5d5289f17ede272d495e7e4db117d47c6523a3), [dd366e9d3a8e](https://github.com/aurora-silicon/linux/commit/dd366e9d3a8e05ab209c7ebe1b28413820f018d4) | Marcelo Alcantara | iommu: apple-dart: raise MAX_DARTS_PER_DEVICE to 8 | Active — DART; `drivers/iommu/apple-dart.c` | D,B | +| [e1817d4152f5](https://github.com/aurora-silicon/linux/commit/e1817d4152f57baca92148c8bbc48fe9b0a8e9f9), [97cdb08431f3](https://github.com/aurora-silicon/linux/commit/97cdb08431f3eef20c2e24c41166bc5dee9e9976), [162b64627bbc](https://github.com/aurora-silicon/linux/commit/162b64627bbcb2fa0ae83cbb6cbf86825ced3e33), [70dfd8f93d3c](https://github.com/aurora-silicon/linux/commit/70dfd8f93d3cac6d6ac7ad26909ca6378fd6a2b4) | Ryan Murray | iommu/apple-dart: Check devm_memremap() for an error pointer | Active — DART; `drivers/iommu/apple-dart.c` | D,B | +| [f57d90040433](https://github.com/aurora-silicon/linux/commit/f57d9004043387ed6c437ddf0afcfcc1bb49f2fd), [6fa300933bb1](https://github.com/aurora-silicon/linux/commit/6fa300933bb1f05c9a49bddc05f8ea3b42c47d35), [79379798f12f](https://github.com/aurora-silicon/linux/commit/79379798f12fdb32ba79f74b0524cfc4f0170471) | Ryan Murray | iommu/apple-dart: Support T8140 DARTs and firmware-owned stream roots | Active — DART; `drivers/iommu/apple-dart.c` | D,B | +| [6ec845b2f381](https://github.com/aurora-silicon/linux/commit/6ec845b2f3812090ed39893f8e26999c757950c9), [3bd878053ca7](https://github.com/aurora-silicon/linux/commit/3bd878053ca7aed1e4af56bc14c339b8bddc6d9c), [6898f34ac7bd](https://github.com/aurora-silicon/linux/commit/6898f34ac7bd349ce29b60fb9785168950c5ed69), [fb7b8edf4ee1](https://github.com/aurora-silicon/linux/commit/fb7b8edf4ee19a35c07c1a2c6aa6b8e65c71777b) | DjDeveloperr | arm64: dts: apple: t8140-pmgr: Add the GPU power domains [#171](https://github.com/aurora-silicon/linux/pull/171) | Active translated data — FW | B,P | +| [cc2d03580b0a](https://github.com/aurora-silicon/linux/commit/cc2d03580b0a68ca0f9779adebd646645813d673), [ea4abd222b2f](https://github.com/aurora-silicon/linux/commit/ea4abd222b2fa2473dba79ca4a91a220e8cb20ba), [52fac306b907](https://github.com/aurora-silicon/linux/commit/52fac306b90783087af17af7ba3e2fc6570122e5), [e77d74a6be76](https://github.com/aurora-silicon/linux/commit/e77d74a6be7645e64b9b50bb51713e72a08c9520) | DjDeveloperr | arm64: dts: apple: t8140: Add the GPU [#171](https://github.com/aurora-silicon/linux/pull/171) | Active translated data — FW | B,P | +| [8284bd4d3914](https://github.com/aurora-silicon/linux/commit/8284bd4d3914ce296a83c38753a69b07e1c10a0e), [29b0b3a54664](https://github.com/aurora-silicon/linux/commit/29b0b3a546641438b14da95054ae136540465de8), [54124e3efacb](https://github.com/aurora-silicon/linux/commit/54124e3efacba7ba530e742cb2f297bbdeb719fa), [f4a46cd97b2a](https://github.com/aurora-silicon/linux/commit/f4a46cd97b2a3e70875d76e3cf2b82f3959fd0f5) | DjDeveloperr | arm64: dts: apple: t8140-j700: Enable the GPU [#171](https://github.com/aurora-silicon/linux/pull/171) | Active translated data — FW | B,P | +| [31ee8e23124d](https://github.com/aurora-silicon/linux/commit/31ee8e23124d77700e47cabc0e8645404187fd74), [5b85b301d4ee](https://github.com/aurora-silicon/linux/commit/5b85b301d4ee38256c757299a0c58c4a79a531a7), [727284277d33](https://github.com/aurora-silicon/linux/commit/727284277d335215edd48b5f74a6f4d4e949ff3a), [9c10d7198c8e](https://github.com/aurora-silicon/linux/commit/9c10d7198c8e56b16da35914ffde0eecb6bfb525) | DjDeveloperr | arm64: dts: apple: t8140: describe the GPU mailboxes as ASCWrap v6 [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — MBOX | B | +| [dbc9f011ec91](https://github.com/aurora-silicon/linux/commit/dbc9f011ec91fb72089a45487891bfd7c5a8939d), [86fba21bf7c9](https://github.com/aurora-silicon/linux/commit/86fba21bf7c926033306cb2dc5b6d5bcfbbe30ce), [018ab982644a](https://github.com/aurora-silicon/linux/commit/018ab982644a6eff94a6020d8910e9c8c187d6a2) | Ryan Murray | arm64: dts: apple: t8140: Add CPU energy model and thermal sensor cells | Active translated data — FW | B,P | +| [b15c2faba082](https://github.com/aurora-silicon/linux/commit/b15c2faba08274f02bf9a11e0e15eef470017dbd), [d693f725d774](https://github.com/aurora-silicon/linux/commit/d693f725d77422774ae683701732562a84789439), [d69c31212d2b](https://github.com/aurora-silicon/linux/commit/d69c31212d2b93b6396775c7890d5734f962c486) | Ryan Murray | arm64: dts: apple: t8140-j700: Add CPU thermal zones | Active translated data — FW | B,P | +| [25b99e9b6a75](https://github.com/aurora-silicon/linux/commit/25b99e9b6a75a9e783d7b5f04ee60b4f97fe24ed), [1ffe37e7af24](https://github.com/aurora-silicon/linux/commit/1ffe37e7af241fb91ecd7a8d17c946fd0a5d8ecc), [64eff8a46206](https://github.com/aurora-silicon/linux/commit/64eff8a46206613ff354cbeaff49d1354e4c1f52) | Ryan Murray | arm64: dts: apple: t8140: Drop the J700 CPU thermal policy property | Active translated data — FW | B,P | +| [db87f30fa4e7](https://github.com/aurora-silicon/linux/commit/db87f30fa4e7a322d655989316aef41a00fc46e5), [cf7dc9d0474c](https://github.com/aurora-silicon/linux/commit/cf7dc9d0474cc53e3716948365b72bacc848d5e9), [8ba3c01c426a](https://github.com/aurora-silicon/linux/commit/8ba3c01c426a8efd643289689493835c51b8937d) | Ryan Murray | arm64: dts: apple: t8140: Add measured CPU capacity values | Active translated data — FW | B,P | +| [6bb7669702ed](https://github.com/aurora-silicon/linux/commit/6bb7669702edf26c05f2be84f303d0fd612487b2), [2623391b533d](https://github.com/aurora-silicon/linux/commit/2623391b533de7f038408e57e3084a75f765b56c), [3dd2554de0c5](https://github.com/aurora-silicon/linux/commit/3dd2554de0c5b6e9d16a3809186872847987de6a) | Ryan Murray | arm64: dts: apple: enable the PMP fast-die limiter on J700 [#179](https://github.com/aurora-silicon/linux/pull/179) | Active — PMP | P,B | +| [47ed624515f4](https://github.com/aurora-silicon/linux/commit/47ed624515f40f37b39230c3b1b20bb6678c788a), [66988937c0d6](https://github.com/aurora-silicon/linux/commit/66988937c0d62aeb1ed828379cb83bbc68581afc), [39a036ead934](https://github.com/aurora-silicon/linux/commit/39a036ead934c701af76e05eebd72b102810861c) | Ryan Murray | arm64: dts: apple: t8140-j700: enable the PMP nodes once | Active — PMP | P,B | +| [a539d62f765f](https://github.com/aurora-silicon/linux/commit/a539d62f765fb0e8153f2be0fa8fb33058f6df9a), [c1af50661b44](https://github.com/aurora-silicon/linux/commit/c1af50661b44940df0c5747ae7dfca25c871bbcb), [d30b0dfcf358](https://github.com/aurora-silicon/linux/commit/d30b0dfcf35813ce52d935e24e7e6d46349ec3cd) | Ryan Murray | arm64: dts: apple: t8140-j700: Keep the idle E cluster out of its lowest OPP | Active translated data — FW | B,P | +| [fb68b124b886](https://github.com/aurora-silicon/linux/commit/fb68b124b8865e39b72b2387e7f739856f72cc26), [76dd6fb9e643](https://github.com/aurora-silicon/linux/commit/76dd6fb9e643747eb61c3b409c93b1ce2975184e), [2d51c6aada40](https://github.com/aurora-silicon/linux/commit/2d51c6aada40a6c97ae81464eea6935ebf70ba95) | Ryan Murray | arm64: dts: apple: t8140: Describe the memory cache controller | Active — MCC | M,P | +| [5811648de37d](https://github.com/aurora-silicon/linux/commit/5811648de37d1bcd66a5294f44191da8880947b2), [840606faf824](https://github.com/aurora-silicon/linux/commit/840606faf824b4f6ea862f8475e7edaff9af4150), [b5b71dfc50b2](https://github.com/aurora-silicon/linux/commit/b5b71dfc50b2bfbf5800a76c540f878924a678cb), [ed31b37fb032](https://github.com/aurora-silicon/linux/commit/ed31b37fb0327ee7c20961fce2785ed3d4ddb154) | DjDeveloperr | arm64: dts: apple: t8140: describe the PMC and report GFX to the PMP [#212](https://github.com/aurora-silicon/linux/pull/212) [#219](https://github.com/aurora-silicon/linux/pull/219) | Active after port — PMC | P,C,R | +| [15f21cbc0d4b](https://github.com/aurora-silicon/linux/commit/15f21cbc0d4bbbc901454d1e69f53084e1c5dc18), [6cab4decfb66](https://github.com/aurora-silicon/linux/commit/6cab4decfb664524936da59558bc39407b633b9a), [0272b63e30f0](https://github.com/aurora-silicon/linux/commit/0272b63e30f07b8c1a496cb01f5aa74e2cc1ff93) | Ryan Murray | arm64: dts: apple: t8140: keep the GPU on its PMGR state [#219](https://github.com/aurora-silicon/linux/pull/219) | Active translated data — FW | B,P | +| [0a44c956dde2](https://github.com/aurora-silicon/linux/commit/0a44c956dde28aad0bd37b66c6ff5d8096e5676d), [8fa836c5de81](https://github.com/aurora-silicon/linux/commit/8fa836c5de8111ab90d8f2a746526d2cd487afb7), [ae8e780a5b7d](https://github.com/aurora-silicon/linux/commit/ae8e780a5b7dedcb4a31adbd106fb9f9dbff8be0) | Ryan Murray | arm64: dts: apple: t8140: describe the PMC's DCS limit [#219](https://github.com/aurora-silicon/linux/pull/219) | Gated — DCS | P | +| [84eb6b53da34](https://github.com/aurora-silicon/linux/commit/84eb6b53da3453d7c2838bd2a41779e0a9bce181), [f01e1cde7d9f](https://github.com/aurora-silicon/linux/commit/f01e1cde7d9f270e156ee77c6819466757b938f6), [a037985ac960](https://github.com/aurora-silicon/linux/commit/a037985ac9605eb76fe51a05b172b022ac1f0005) | Ryan Murray | arm64: dts: apple: t8140-j700: limit the DCS vote when the die runs hot [#219](https://github.com/aurora-silicon/linux/pull/219) | Gated — DCS | P | +| [4f6bac29d934](https://github.com/aurora-silicon/linux/commit/4f6bac29d9349ced88686239f259ab2747207708), [e10ab48a4590](https://github.com/aurora-silicon/linux/commit/e10ab48a4590c23c664546f524650310222b3a35), [c1bae63595e8](https://github.com/aurora-silicon/linux/commit/c1bae63595e8fc7b18e79664ee6aa6b4536f5d95) | Ryan Murray | arm64: dts: apple: t8140: Keep the GPU power state on across sleep | Active translated data — FW | B,P | +| [01d1be596840](https://github.com/aurora-silicon/linux/commit/01d1be596840358a7258d87fc91d3201a3287313), [8b6371b5500d](https://github.com/aurora-silicon/linux/commit/8b6371b5500d062c00f71d46a0bccbcbed111edd) | Ryan Murray | iommu: apple-dart: Program T8140 and T8142 streams individually | Active — DART; `drivers/iommu/apple-dart.c` | D,B | +| [2398a03f445e](https://github.com/aurora-silicon/linux/commit/2398a03f445e8f9f65fa9f86748a3deeab3e1b1f), [69c39f85461e](https://github.com/aurora-silicon/linux/commit/69c39f85461e8944dff387eab703c58ea6c6ed49), [1d2904fd3301](https://github.com/aurora-silicon/linux/commit/1d2904fd3301c63620f07c81ae79f2486a81a9a6) | Dj | iommu/apple-dart: defer locked root publication until attach | Active — DART; `drivers/iommu/apple-dart.c` | D,B | +| [ae5849165749](https://github.com/aurora-silicon/linux/commit/ae58491657494b0f56ec0113a3aa43612648e242), [44c7f32baad0](https://github.com/aurora-silicon/linux/commit/44c7f32baad0511e6f3ae5c2ffdd863eec8e5d23), [dc9d13edb0e5](https://github.com/aurora-silicon/linux/commit/dc9d13edb0e5b18c61d25240ed4ba532afb8d11f) | Acelogic | iommu/apple-dart: accept T8142 firmware slots that mirror Linux mappings | Active — DART; `drivers/iommu/apple-dart.c` | D,B | +| [1917cbce81cb](https://github.com/aurora-silicon/linux/commit/1917cbce81cb806498b0a5a3f5e636c851255408), [61dfeb8f659d](https://github.com/aurora-silicon/linux/commit/61dfeb8f659d36149d3dc6162d304136b108c5b9), [c361702757bf](https://github.com/aurora-silicon/linux/commit/c361702757bfccb9183ed79be0c6200fb3722e6f) | Ryan Murray | iommu/apple-dart: defer publication until locked roots are known | Active — DART; `drivers/iommu/apple-dart.c` | D,B | +| [c74dc14619ec](https://github.com/aurora-silicon/linux/commit/c74dc14619ec5467789855d89c0ef224a3655093), [35f59a797e91](https://github.com/aurora-silicon/linux/commit/35f59a797e914308f4ae289e87551ad97997a497), [d957b0bd713b](https://github.com/aurora-silicon/linux/commit/d957b0bd713bbf4617f54f5ed0d274964e9af6c9) | Ryan Murray | iommu/apple-dart: validate software mappings against inherited slots | Active — DART; `drivers/iommu/apple-dart.c` | D,B | +| [f89a3c873999](https://github.com/aurora-silicon/linux/commit/f89a3c87399911e0faa4f66091b2e7dcc1145825), [64013b9557ab](https://github.com/aurora-silicon/linux/commit/64013b9557abadab17883a7e697f32f32e0c4160), [707f28a4c399](https://github.com/aurora-silicon/linux/commit/707f28a4c399092ba01bd281a3f0b6623f7bb223) | Ryan Murray | iommu/apple-dart: import firmware translations on T8140 | Active — DART; `drivers/iommu/apple-dart.c` | D,B | +| [99633bd5454e](https://github.com/aurora-silicon/linux/commit/99633bd5454eac0e455812358be63b4f08d78a98), [c29b618f6340](https://github.com/aurora-silicon/linux/commit/c29b618f6340b4ede94747a266f86a844344e769), [ebcff4ba64dd](https://github.com/aurora-silicon/linux/commit/ebcff4ba64dd90983cc8150b0f46fda82b5fb9f0) | Ryan Murray | iommu: apple-dart: retain inherited translations before reservation setup | Active — DART; `drivers/iommu/apple-dart.c` | D,B | +| [8d2d7790d8d5](https://github.com/aurora-silicon/linux/commit/8d2d7790d8d552f01ce2bf10f922aaccbf7918aa), [dbd410951406](https://github.com/aurora-silicon/linux/commit/dbd4109514067a2ed8140d8138bee68453272f02), [8478ae014201](https://github.com/aurora-silicon/linux/commit/8478ae0142014d170a62df6dd94cc66bd989627b) | Chris Kearney | iommu/apple-dart: restore legacy locked display domain handoff | Active — DART; `drivers/iommu/Kconfig` | D,B | +| [0297adcecc72](https://github.com/aurora-silicon/linux/commit/0297adcecc72a563b5cfb3ca3e483f71422fb77c), [b71446526889](https://github.com/aurora-silicon/linux/commit/b71446526889b27c60461685c3c320b7e123155d), [2bad3dba632f](https://github.com/aurora-silicon/linux/commit/2bad3dba632ff85537392b79255fc527baf01746) | Ryan Murray | iommu: apple-dart: keep inherited firmware translations visible before attach | Active — DART; `drivers/iommu/apple-dart.c` | D,B | +| [80ce713f5251](https://github.com/aurora-silicon/linux/commit/80ce713f52511abe4d35b35bfac0f10f9072d401), [8ece17f482f1](https://github.com/aurora-silicon/linux/commit/8ece17f482f11da6e81915b98fc8b93ed796504a), [a64a4771b276](https://github.com/aurora-silicon/linux/commit/a64a4771b27602bf5a72b413b6bc9a868892992a) | Ryan Murray | iommu: apple-dart: invalidate T8110 ranges by device DVA [#221](https://github.com/aurora-silicon/linux/pull/221) | Active — DART; `drivers/iommu/apple-dart.c` | D,B | +| [cef9a23aa45e](https://github.com/aurora-silicon/linux/commit/cef9a23aa45e3ac6b352e6c2bd839935374ae351), [253ba9bebf47](https://github.com/aurora-silicon/linux/commit/253ba9bebf470c0d78845103c93b937f2d9cdfb3), [dd5ac6fdffb0](https://github.com/aurora-silicon/linux/commit/dd5ac6fdffb03d048df510198af6e8d53dceb17d) | Ryan Murray | iommu: apple-dart: leave live firmware translations out of invalidates [#221](https://github.com/aurora-silicon/linux/pull/221) | Active — DART; `drivers/iommu/apple-dart.c` | D,B | +| [0a73b0299574](https://github.com/aurora-silicon/linux/commit/0a73b0299574da4c42659024b4a0e8390c36d2d8), [bc6682c215ad](https://github.com/aurora-silicon/linux/commit/bc6682c215adc6febfb86c9fa2ed0fb69657dcc2), [9c512b25f55e](https://github.com/aurora-silicon/linux/commit/9c512b25f55eaf6332b590e5a205ca7d55de12c8) | Ryan Murray | iommu: apple-dart: invalidate client-ranged T8140 streams again [#232](https://github.com/aurora-silicon/linux/pull/232) | Active — DART; `drivers/iommu/apple-dart.c` | D,B | +| [ae812d47ea17](https://github.com/aurora-silicon/linux/commit/ae812d47ea176c9bb0b4ddccc17349fee5afbf1a), [fe15676f6e03](https://github.com/aurora-silicon/linux/commit/fe15676f6e03395f4947cbffb2d49fbee17ea5f9), [4ca4ba380552](https://github.com/aurora-silicon/linux/commit/4ca4ba3805528f5370bbe6bab3d8dbb8f7f9226c) | Ryan Murray | iommu: apple-dart: invalidate four-level T8140 streams by DVA range [#237](https://github.com/aurora-silicon/linux/pull/237) | Active — DART; `drivers/iommu/apple-dart.c` | D,B | +| [a17634424697](https://github.com/aurora-silicon/linux/commit/a17634424697b082221d141ef61d9689460eebf8), [d1a5c65245b8](https://github.com/aurora-silicon/linux/commit/d1a5c65245b87c457a8fc4b4190d440e9bc38b8d) | Sasha Finkelstein | soc: apple: rtkit: Add tracekit endpoint. | Active — RTKIT; `drivers/soc/apple/rtkit.c` | B,F | +| [ff7d81511780](https://github.com/aurora-silicon/linux/commit/ff7d81511780770e944ebe6e66ed56a06b497a31) | Ryan Murray | drm/asahi: Adapt to the Linux 7.3 Rust APIs [#375](https://github.com/aurora-silicon/linux/pull/375) | Active — MEM; `drivers/gpu/drm/asahi/debug.rs` | M,D | +| [588c231eaa54](https://github.com/aurora-silicon/linux/commit/588c231eaa54f4a34872519517fbbcc079b41175) | Ryan Murray | soc: apple: Adapt the Rust SEP, PMP and AOP drivers to Linux 7.3 | Active — PMP; `drivers/soc/apple/aop.rs` | P,B | +| [c025e6cd0615](https://github.com/aurora-silicon/linux/commit/c025e6cd0615e416e0a06ba997e8d9f417a3e185), [af0890cf33cf](https://github.com/aurora-silicon/linux/commit/af0890cf33cf899cf0005daf6138da1dfe573c19) | Ryan Murray | soc: apple: rtkit: Harden syslog buffer handling | Active — RTKIT; `drivers/soc/apple/rtkit.c` | B,F | +| [3a8760c28c33](https://github.com/aurora-silicon/linux/commit/3a8760c28c3341380e105c75bb48e76c895d3f3d), [c01af1b31c52](https://github.com/aurora-silicon/linux/commit/c01af1b31c523b09106c4bed43f8bb7cba65cbfc) | Ryan Murray | soc: apple: rtkit: Keep a live shared buffer on a repeated request | Active — RTKIT; `drivers/soc/apple/rtkit.c` | B,F | +| [495b2f037c28](https://github.com/aurora-silicon/linux/commit/495b2f037c284f4b7b10f54c90a7d34fbf5e99fa), [6b7e37786882](https://github.com/aurora-silicon/linux/commit/6b7e37786882db803e9de1352a2af623044f9237), [1fc9ea993905](https://github.com/aurora-silicon/linux/commit/1fc9ea9939058bc1307565f036506da30dd62908) | Ryan Murray | soc: apple: rtkit: Serialize receiver ownership through teardown | Active — MBOX; `drivers/soc/apple/mailbox.c` | B,O | +| [335eaeb9b9f1](https://github.com/aurora-silicon/linux/commit/335eaeb9b9f147230f464a140287e820b1d78518), [0593ad398a6e](https://github.com/aurora-silicon/linux/commit/0593ad398a6eb83e2e2ce9dd25bf1fd0768e5bcd) | Ryan Murray | soc: apple: rtkit: Add apple_rtkit_free_retaining_buffers() | Active — RTKIT; `drivers/soc/apple/rtkit.c` | B,F | +| [e796f5fbaf06](https://github.com/aurora-silicon/linux/commit/e796f5fbaf0698b478c43ba6eb15e046c9cc6244), [8527354650e8](https://github.com/aurora-silicon/linux/commit/8527354650e810479ceb0a58417e683b2dee852c) | Ryan Murray | soc: apple: rtkit: Allow streaming-mapped shared buffers | Active — RTKIT; `drivers/soc/apple/rtkit.c` | B,F | +| [9191296f1898](https://github.com/aurora-silicon/linux/commit/9191296f1898d1acf2d0cd500230ae80c9e3e0ca), [88e3c5c07943](https://github.com/aurora-silicon/linux/commit/88e3c5c07943fedf73e45f88b270f8550d7691ae) | Ryan Murray | soc: apple: rtkit: Support adopting a running co-processor | Active — RTKIT; `drivers/soc/apple/rtkit-internal.h` | B,F | +| [1932e1e1b184](https://github.com/aurora-silicon/linux/commit/1932e1e1b18437408e77dcc1d23d6d966b0fdb98), [f232f22bef54](https://github.com/aurora-silicon/linux/commit/f232f22bef54eb6fbd848a1374e49fe91035b397) | Ryan Murray | soc: apple: rtkit: Report crashes of an adopted co-processor | Active — RTKIT; `drivers/soc/apple/rtkit-internal.h` | B,F | +| [b38944203ab0](https://github.com/aurora-silicon/linux/commit/b38944203ab0ad8ec01c26bd67df67455fcc98c3), [4f0416f2c43b](https://github.com/aurora-silicon/linux/commit/4f0416f2c43ba895b3c34a263cf5d0e070095a55), [2768025e0f1d](https://github.com/aurora-silicon/linux/commit/2768025e0f1d9792f84e9fbb99f6c0b155c0a22a), [35b2acbf474d](https://github.com/aurora-silicon/linux/commit/35b2acbf474d8505c3edc1a2749a8d6fbfaf7a09) | Ryan Murray | pmdomain: apple: Print the read-back state on a power state timeout | Active — OBS; `drivers/pmdomain/apple/pmgr-pwrstate.c` | O,F | +| [768a665d0ca1](https://github.com/aurora-silicon/linux/commit/768a665d0ca138d0f2c80029fd1d272c11468e2e), [9c5b8fe6d6d6](https://github.com/aurora-silicon/linux/commit/9c5b8fe6d6d6a93f7c4bbbd14026fd93d5f6aa73), [afe19efeff9c](https://github.com/aurora-silicon/linux/commit/afe19efeff9c466486fb723f7066f86b9a3e71d5), [9dc79fb25c92](https://github.com/aurora-silicon/linux/commit/9dc79fb25c9294bd7a8ecb00acc9bcea379c3d1a) | Ryan Murray | pmdomain: apple: Mask BUSY in power state writes and ratelimit errors | Active — PMGR; `drivers/pmdomain/apple/pmgr-pwrstate.c` | B,F | +| [a6aa625a9b35](https://github.com/aurora-silicon/linux/commit/a6aa625a9b35b96ba4ffc0749d55a0f21fe9e426), [03f4792761f4](https://github.com/aurora-silicon/linux/commit/03f4792761f42c3a4581be4b585a479cfc65bfc3), [f82e88f4049e](https://github.com/aurora-silicon/linux/commit/f82e88f4049ed6ea278717be6d922f8e347aab13), [928641d26be3](https://github.com/aurora-silicon/linux/commit/928641d26be3083dc06c0de072ce99537fa059b7) | DjDeveloperr | soc: apple: rtkit: Support co-processors that place their own buffers [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — RTKIT; `drivers/soc/apple/rtkit.c` | B,F | +| [e52b589758bd](https://github.com/aurora-silicon/linux/commit/e52b589758bd9c72db728ef41947aadc2d1eec3f), [a8e468104169](https://github.com/aurora-silicon/linux/commit/a8e46810416977c034c45d805c0b504cfdab9a6a), [c07d044a0311](https://github.com/aurora-silicon/linux/commit/c07d044a03117705721c78500bc36489ea34e6c4) | Ryan Murray | soc: apple: rtkit: recognise an initial crash log in adopted sessions | Active — RTKIT; `drivers/soc/apple/rtkit-crashlog.c` | B,F | +| [8420f4272083](https://github.com/aurora-silicon/linux/commit/8420f42720830b9e5bad37647b6eead5303bc566), [352b68249894](https://github.com/aurora-silicon/linux/commit/352b682498947d0062635916a9d6902165ab38b5), [2f434459eb09](https://github.com/aurora-silicon/linux/commit/2f434459eb094edc046cd2ff71f1901302d7ef3d) | Ryan Murray | soc: apple: rtkit: reject an already owned mailbox receiver | Active — RTKIT; `drivers/soc/apple/rtkit.c` | B,F | +| [791f8c37bad3](https://github.com/aurora-silicon/linux/commit/791f8c37bad39ada4186e7db9efb74472ef1670b), [44a668c87df1](https://github.com/aurora-silicon/linux/commit/44a668c87df18147ca0c9ace22e897e4c65b7620), [41e7892e40eb](https://github.com/aurora-silicon/linux/commit/41e7892e40eb21ddb08b2391a7940ed6499daf8d) | Ryan Murray | soc: apple: pmp: use a 42-bit DMA mask on T8140 [#179](https://github.com/aurora-silicon/linux/pull/179) | Active — PMP; `drivers/soc/apple/pmp.rs` | P,B | +| [7a9913ea25e2](https://github.com/aurora-silicon/linux/commit/7a9913ea25e2d95d3ebe8f0392fec92979d08728), [493080d07b91](https://github.com/aurora-silicon/linux/commit/493080d07b918a2025fb79ff987d0dc9aeb294fe), [f69f1b76fcb0](https://github.com/aurora-silicon/linux/commit/f69f1b76fcb04e6bd7d7d066654e612be9761af0) | Ryan Murray | pmdomain: apple: pmp-report: expose T8140 fast-die effort [#179](https://github.com/aurora-silicon/linux/pull/179) | Active — PMP; `drivers/pmdomain/apple/pmp-report.c` | P,B | +| [1a665f53f540](https://github.com/aurora-silicon/linux/commit/1a665f53f540a523d8df2c11d11a824f933b0d14), [8410c0eac2d9](https://github.com/aurora-silicon/linux/commit/8410c0eac2d9419a33c6a8d4a00bd14059f94610), [4f3e0ed14a9c](https://github.com/aurora-silicon/linux/commit/4f3e0ed14a9c280aafb3642619e7335413592e06) | Ryan Murray | pmdomain: apple: drain PMP report users before teardown | Active — PMP; `drivers/pmdomain/apple/pmp-report.c` | P,B | +| [50b59e9b8f43](https://github.com/aurora-silicon/linux/commit/50b59e9b8f43e49c14a8a5e426f7891ac5f5e8c7), [061cf8fae73f](https://github.com/aurora-silicon/linux/commit/061cf8fae73f12de8d6720602ccac2aec250a20f), [f4ec00ff19db](https://github.com/aurora-silicon/linux/commit/f4ec00ff19dbfdc6d9bdbf03e8d7ec2798502f1a) | Ryan Murray | soc: apple: Enable T8140 system level cache allocation | Active — MCC; `drivers/soc/apple/Kconfig` | M,P | +| [628dbbe941d2](https://github.com/aurora-silicon/linux/commit/628dbbe941d25e5153dc62cb04104f846f3ffc48), [af915d02f5f6](https://github.com/aurora-silicon/linux/commit/af915d02f5f6022114a46e302642e915f9b22a01), [8b7d7f837ff6](https://github.com/aurora-silicon/linux/commit/8b7d7f837ff68f7fa37f6aaae55114bbd15efb43), [132cee4161dc](https://github.com/aurora-silicon/linux/commit/132cee4161dcff4307c55314f2a6189a00e7b0df) | DjDeveloperr | pmdomain: apple: add PMC voter interface driver [#212](https://github.com/aurora-silicon/linux/pull/212) [#219](https://github.com/aurora-silicon/linux/pull/219) | Active after port — PMC; `drivers/pmdomain/apple/Kconfig` | P,C,R | +| [698f429f4256](https://github.com/aurora-silicon/linux/commit/698f429f42566c059e7788e4efd44978a5db33a9), [9e840cf32336](https://github.com/aurora-silicon/linux/commit/9e840cf323360eabdb61223cf73198f4f4f3179d), [6454e02db2a5](https://github.com/aurora-silicon/linux/commit/6454e02db2a562d82adfbc4447ffe093f43dae0a), [5bd867f5c339](https://github.com/aurora-silicon/linux/commit/5bd867f5c33919ed65c81ead1e31595bb6bb91c5) | DjDeveloperr | soc: apple: add host performance controller floors [#212](https://github.com/aurora-silicon/linux/pull/212) [#219](https://github.com/aurora-silicon/linux/pull/219) | Active — CLPC; `drivers/soc/apple/Kconfig` | P,C,R,I | +| [faff1f7acf75](https://github.com/aurora-silicon/linux/commit/faff1f7acf752284972af5d469f91186c1e28552), [1c69f57e2c3e](https://github.com/aurora-silicon/linux/commit/1c69f57e2c3e5ba7cea2db4708242b7b538b15b2), [e736b2a049bf](https://github.com/aurora-silicon/linux/commit/e736b2a049bf708512f845093f712c431c20da3b) | Ryan Murray | pmdomain: apple: pmc: report the honoured floor of each agent [#219](https://github.com/aurora-silicon/linux/pull/219) | Active — PMC; `drivers/pmdomain/apple/pmc.c` | P,C,R | +| [9afc8195e8e2](https://github.com/aurora-silicon/linux/commit/9afc8195e8e2b3b4da25805f0b0c53baac16f1de), [e3f7747b88a3](https://github.com/aurora-silicon/linux/commit/e3f7747b88a38a8f2db51a63b323bb6fe3a25157), [cf8edfc522ce](https://github.com/aurora-silicon/linux/commit/cf8edfc522ce82871acc421119e324b56ab2813f) | Ryan Murray | pmdomain: apple: pmc: limit the DCS rail as a cooling device [#219](https://github.com/aurora-silicon/linux/pull/219) | Gated — DCS; `drivers/pmdomain/apple/pmc.c` | P | +| [c809017f2926](https://github.com/aurora-silicon/linux/commit/c809017f2926613cc9b683189c5e37aa6236c647), [bbbc7f0f2526](https://github.com/aurora-silicon/linux/commit/bbbc7f0f2526501df99e7d40c7a14a5bcb3c168d), [99bf4fc4c39c](https://github.com/aurora-silicon/linux/commit/99bf4fc4c39cbe59493531b7682f0a7eb60ac2e0) | Ryan Murray | pmdomain: apple: build the PMC driver by default [#219](https://github.com/aurora-silicon/linux/pull/219) | Active — PMC; `drivers/pmdomain/apple/Kconfig` | P,C,R | +| [413512c8c6c7](https://github.com/aurora-silicon/linux/commit/413512c8c6c7503e21b2d845b7072f3e67985e80), [afbf48da68ef](https://github.com/aurora-silicon/linux/commit/afbf48da68ef244414db4e174b630cf95ed33bd2), [d0f012d7b92f](https://github.com/aurora-silicon/linux/commit/d0f012d7b92f9967d42c35a70bc09c2b87274c0b) | Ryan Murray | soc: apple: clpc: do not count offline CPUs as busy [#219](https://github.com/aurora-silicon/linux/pull/219) | Active — CLPC; `drivers/soc/apple/clpc.c` | P,C,R,I | +| [a1517450f0d6](https://github.com/aurora-silicon/linux/commit/a1517450f0d6afa99ed5a5f2acbbc46350fdccd5), [a0301cd2e0fb](https://github.com/aurora-silicon/linux/commit/a0301cd2e0fb788836d8ef4d09b397a478891a72), [ddc216cd42cb](https://github.com/aurora-silicon/linux/commit/ddc216cd42cb7c7ebc831d3fafca17df244b280e) | Ryan Murray | soc: apple: clpc: stop waking idle CPUs and release the floor in sleep [#219](https://github.com/aurora-silicon/linux/pull/219) | Active — CLPC; `drivers/soc/apple/clpc.c` | P,C,R,I | +| [17c6097b09dd](https://github.com/aurora-silicon/linux/commit/17c6097b09ddd81a31a618e1d2447c3fdfed87ce), [ebd97e7441ff](https://github.com/aurora-silicon/linux/commit/ebd97e7441ff7ca5f7169f6e2bf550e7fb1438e0), [4f13ab717cb3](https://github.com/aurora-silicon/linux/commit/4f13ab717cb3a5ab19e5c097084ff2c676457f37) | Ryan Murray | pmdomain: apple: pmp-report: keep entry domains bound [#223](https://github.com/aurora-silicon/linux/pull/223) | Active — PMP; `drivers/pmdomain/apple/pmp-report.c` | P,B | +| [bd2a439af1fe](https://github.com/aurora-silicon/linux/commit/bd2a439af1feefbb19cec4af61a28ae81a7fe479), [6733b19981f1](https://github.com/aurora-silicon/linux/commit/6733b19981f1d70f51264f609582025889e083db), [59540c7f72b3](https://github.com/aurora-silicon/linux/commit/59540c7f72b3721d293fb36cc5fd5bf79e1f47bc) | Ryan Murray | pmdomain: apple: Undo a power state transition that times out | Active — PMGR; `drivers/pmdomain/apple/pmgr-pwrstate.c` | B,F | +| [afa52a4aacd2](https://github.com/aurora-silicon/linux/commit/afa52a4aacd24be61e6963bcfa96a673fcd0f7fd), [b1dadbe56c1a](https://github.com/aurora-silicon/linux/commit/b1dadbe56c1ac98c1687aacd6f9a4ffd45d44802) | Acelogic | soc: apple: rtkit: Allow AP readiness before the IOP power acknowledgement | Active — RTKIT; `drivers/soc/apple/rtkit-helper.c` | B,F | +| [80223777e1c0](https://github.com/aurora-silicon/linux/commit/80223777e1c0fcdd18cdf7dbb693389f18bffe66), [7de139272456](https://github.com/aurora-silicon/linux/commit/7de1392724567cdab869ed7102a23642d6e64320), [b5c2960b4b44](https://github.com/aurora-silicon/linux/commit/b5c2960b4b441fa51b7f0ccdbaa17eae8a9de4bc) | Ryan Murray | soc: apple: rtkit: identify failed boot handshake stages | Active — RTKIT; `drivers/soc/apple/rtkit.c` | B,F | +| [e02a7f13d77d](https://github.com/aurora-silicon/linux/commit/e02a7f13d77d0c450e6e663da89160b8e2e6a9de), [3a165a5f418d](https://github.com/aurora-silicon/linux/commit/3a165a5f418df4ca2445c14657bdc6dbe39bb223), [50a711417171](https://github.com/aurora-silicon/linux/commit/50a711417171e48a07340999bf43bb8c281b14d7) | Eryk Wieliczko | pmdomain: apple: leave firmware-owned power domains untouched | Active — PMGR; `drivers/pmdomain/apple/pmgr-pwrstate.c` | B,F | +| [15a254a94f16](https://github.com/aurora-silicon/linux/commit/15a254a94f1685a5550aec6c99a3270bfb389951), [e271de959b7d](https://github.com/aurora-silicon/linux/commit/e271de959b7de2e436f15415329d175e2e158517) | Ryan Murray | soc: apple: rtkit: Recognise opaque OSLog events 3 to 5 | Active — RTKIT; `drivers/soc/apple/rtkit.c` | B,F | +| [faadb28854ca](https://github.com/aurora-silicon/linux/commit/faadb28854caaf534df60f6d629eca87c963344f), [aa486de92319](https://github.com/aurora-silicon/linux/commit/aa486de923198c933849bb2172f5ddad4f4710b6), [6559da7d50ab](https://github.com/aurora-silicon/linux/commit/6559da7d50ab10da722b3719f447836b12427159), [7810f444ae5a](https://github.com/aurora-silicon/linux/commit/7810f444ae5a403773baf8d4f460e29cb2375cf5) | Ryan Murray | cpufreq: apple-soc: Use related_cpus when removing OPP tables at exit | Active — CPU; `drivers/cpufreq/apple-soc-cpufreq.c` | C,I,P | +| [f630f4e6a724](https://github.com/aurora-silicon/linux/commit/f630f4e6a724b62182875eeb75e3559f6304b286), [4df394b4c36b](https://github.com/aurora-silicon/linux/commit/4df394b4c36be04ca63fabae2c630bfdda20188a), [31ce0bec7173](https://github.com/aurora-silicon/linux/commit/31ce0bec7173eb3fbc1067e6dba46c2ca2f4dc8e) | Ryan Murray | cpufreq: apple-soc: Add T8140 support | Active — CPU; `drivers/cpufreq/apple-soc-cpufreq.c` | C,I,P | +| [116ec26bc074](https://github.com/aurora-silicon/linux/commit/116ec26bc074456b670823002c81ccffc5fbbecc), [2e97d372534e](https://github.com/aurora-silicon/linux/commit/2e97d372534e73b72b9b36b9debaa3caa9ae5ab6), [b0a02dcbfded](https://github.com/aurora-silicon/linux/commit/b0a02dcbfded7f1485db7f37694712892025a4d9) | Ryan Murray | cpufreq: apple-soc: enforce PMP fast-die limits through QoS [#179](https://github.com/aurora-silicon/linux/pull/179) | Active — PMP; `drivers/cpufreq/apple-soc-cpufreq.c` | P,B | +| [3a1ff1c99f6a](https://github.com/aurora-silicon/linux/commit/3a1ff1c99f6a35163c77f9c2cc5f6b19d4fe6242), [9d74a86460df](https://github.com/aurora-silicon/linux/commit/9d74a86460df995f3522f3267c3f3fa34d79a47e) | Ryan Murray | cpufreq: apple-soc: keep required fallback limits with an older DT | Active — CPU; `drivers/cpufreq/apple-soc-cpufreq.c` | C,I,P | +| [1248cb920813](https://github.com/aurora-silicon/linux/commit/1248cb9208135326d9724eb1a83e3c67d25c3664), [a40bdafc42e5](https://github.com/aurora-silicon/linux/commit/a40bdafc42e5dcf8c0c4bc728f883c7e3d48bffe), [a61bca6382a7](https://github.com/aurora-silicon/linux/commit/a61bca6382a765f5b74134dea3b094039f2a15bd) | Ryan Murray | cpufreq: apple-soc: tidy includes and the ->exit() callback [#317](https://github.com/aurora-silicon/linux/pull/317) | Active — CPU; `drivers/cpufreq/apple-soc-cpufreq.c` | C,P | +| [c52554b353b4](https://github.com/aurora-silicon/linux/commit/c52554b353b4fcecf684ea648e4b1ab5f896b677), [143dc7bec441](https://github.com/aurora-silicon/linux/commit/143dc7bec44131ac467fa21049f2d63c52783d52), [e7d3d687d207](https://github.com/aurora-silicon/linux/commit/e7d3d687d20745259fcb83af83c9ed377ff6a1f3) | Ryan Murray | cpufreq: apple-soc: describe the unmanaged P-state ceiling as data [#317](https://github.com/aurora-silicon/linux/pull/317) | Active — CPU; `drivers/cpufreq/apple-soc-cpufreq.c` | C,P | +| [e71cc1ccab4e](https://github.com/aurora-silicon/linux/commit/e71cc1ccab4eaefdf63d66104107c9604ab04121), [cd8875cfd1bf](https://github.com/aurora-silicon/linux/commit/cd8875cfd1bfaa80b1b1dbd7e27ea234d129471b), [cfe897d4abac](https://github.com/aurora-silicon/linux/commit/cfe897d4abac70a3d9703ea7f4c082ed89da7a96) | Ryan Murray | rust: soc: apple: rtkit: Add shutdown() and a buffer-retaining drop | Active — RTKIT; `rust/kernel/soc/apple/rtkit.rs` | B,F | +| [10dd5c21ef72](https://github.com/aurora-silicon/linux/commit/10dd5c21ef7211836beaa47f6f3fd3e250d36866), [8779ecb38c89](https://github.com/aurora-silicon/linux/commit/8779ecb38c89c103617c4ea24207fef96472ebc4), [06914e1d4995](https://github.com/aurora-silicon/linux/commit/06914e1d499555a96e0beccccc0b5485051108cd) | Ryan Murray | soc: apple: rtkit: Handle oslog notifications quietly | Active — RTKIT; `drivers/soc/apple/rtkit.c` | B,F | +| [9a4cf171ba9e](https://github.com/aurora-silicon/linux/commit/9a4cf171ba9edabdd0f0f715468d42689791adb7), [aa18e91ac6fc](https://github.com/aurora-silicon/linux/commit/aa18e91ac6fce29dcc8e69aac619fab3fda7ee85), [904945425463](https://github.com/aurora-silicon/linux/commit/904945425463c0450cb19582afeb4b935175ae51), [201afd84ae50](https://github.com/aurora-silicon/linux/commit/201afd84ae50008095068d028af22ecf4c1356c5) | DjDeveloperr | drm/asahi: use const float bit conversion supported by Rust minimum [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — MEM; `drivers/gpu/drm/asahi/float.rs` | M,D | +| [c9be90533ae0](https://github.com/aurora-silicon/linux/commit/c9be90533ae06ac64dfe5e35b121cf30a1cc37f0), [6b610ee95063](https://github.com/aurora-silicon/linux/commit/6b610ee95063a8d047c1ee49c65ee3599ee24a92), [94b86f96bd9e](https://github.com/aurora-silicon/linux/commit/94b86f96bd9ef2275c99ddf5238374e615b2f510), [12f0a07a6166](https://github.com/aurora-silicon/linux/commit/12f0a07a61666305a1efe1a1f4c4c88a013abafb) | DjDeveloperr | rust: drm: sched: Fix job accounting when cancelling jobs [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — DRM; `rust/kernel/drm/sched.rs` | C,H,M,F | +| [7ba04b60d2ca](https://github.com/aurora-silicon/linux/commit/7ba04b60d2caf298bb9119b359629feedfec38c5), [43d53c0d4f56](https://github.com/aurora-silicon/linux/commit/43d53c0d4f5660bc6d300e6626d1f9a05d832c79), [15a7eae7964f](https://github.com/aurora-silicon/linux/commit/15a7eae7964f94a16fd273fc3b25a22ad2c27bab), [f8102be460b6](https://github.com/aurora-silicon/linux/commit/f8102be460b66b12af865d6250e73ffc6f99c73a) | DjDeveloperr | rust: drm: sched: Keep the scheduler alive while jobs reference it [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — DRM; `rust/kernel/drm/sched.rs` | C,H,M,F | +| [d1ebc542fe8e](https://github.com/aurora-silicon/linux/commit/d1ebc542fe8e1082a1c6e311b72d4d6c609c940d), [e4c63b3dbab0](https://github.com/aurora-silicon/linux/commit/e4c63b3dbab04bda2a798b913dd5e46b8799290a), [7d650853ea8e](https://github.com/aurora-silicon/linux/commit/7d650853ea8e5de3f512a36b69f8653014de83b4), [f1f985d57681](https://github.com/aurora-silicon/linux/commit/f1f985d576810c7183ea9bcd1b701cb284927ac6) | DjDeveloperr | rust: drm: sched: Do not stop the scheduler before tearing it down [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — DRM; `rust/kernel/drm/sched.rs` | C,H,M,F | +| [45ade507d593](https://github.com/aurora-silicon/linux/commit/45ade507d593cb69929a4daf5d1d50159d729126), [1bc200c44668](https://github.com/aurora-silicon/linux/commit/1bc200c446687bc2eb2b6abc19f1d9b5a3ad59ee), [9171e855e6d5](https://github.com/aurora-silicon/linux/commit/9171e855e6d58a12059e265a7e3907f44ffa70a9), [14d7b3c379da](https://github.com/aurora-silicon/linux/commit/14d7b3c379da4c764581d4c34aff495d7346af72) | DjDeveloperr | rust: drm: sched: let drivers handle timeouts without a reset [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — DRM; `rust/kernel/drm/sched.rs` | C,H,M,F | +| [996d255ea104](https://github.com/aurora-silicon/linux/commit/996d255ea104c999554b5c5584d2b59c6eed3cd9), [59b1e2a30011](https://github.com/aurora-silicon/linux/commit/59b1e2a300116dec7170b0b4a44e4b94a39c372b), [630c43dac20e](https://github.com/aurora-silicon/linux/commit/630c43dac20e9621d3c2cd687c98ab85d51c8b9e), [e0a5c5ad84cb](https://github.com/aurora-silicon/linux/commit/e0a5c5ad84cbc6e48c0194882b8c1c67d72538a8) | DjDeveloperr | drm/asahi: uapi: Add the G17 additions [#171](https://github.com/aurora-silicon/linux/pull/171) | Active translated data — FW; `drivers/gpu/drm/asahi/file.rs` | B,C,R | +| [2f298100e9d5](https://github.com/aurora-silicon/linux/commit/2f298100e9d59b05f3c71d9e27d8079a63a0b6bc), [4a9cf4453a2d](https://github.com/aurora-silicon/linux/commit/4a9cf4453a2d0349848c89bd2028e8e6757263a7), [e46d8cfd391b](https://github.com/aurora-silicon/linux/commit/e46d8cfd391b00b302d5a9ff4608b82c3714c910), [aae9f7d3525f](https://github.com/aurora-silicon/linux/commit/aae9f7d3525f93a7a5b5ae0529f8e85219735d08) | DjDeveloperr | drm/asahi: separate DRM-facing GPU operations from the firmware manager [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — MEM; `drivers/gpu/drm/asahi/driver.rs` | M,D | +| [3291ed172be2](https://github.com/aurora-silicon/linux/commit/3291ed172be2a63252d34367f8f5d8baebf2e537), [cac96891a49f](https://github.com/aurora-silicon/linux/commit/cac96891a49fd1416cb3bb0f68922cbebb7618c1), [54721b0f0bee](https://github.com/aurora-silicon/linux/commit/54721b0f0bee7be5b3094112ffcfa36e76dec7ed), [7d3ddb6c35e1](https://github.com/aurora-silicon/linux/commit/7d3ddb6c35e1c478f68c78ac8bb8b3ee9d314e58) | DjDeveloperr | drm/asahi: mmu: support per-SoC UAT geometry and user VA range [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — MEM; `drivers/gpu/drm/asahi/file.rs` | M,D | +| [a0ae891ae8a0](https://github.com/aurora-silicon/linux/commit/a0ae891ae8a00e183ddbc753347a7ea2ef26d7dc), [79aa2038d24c](https://github.com/aurora-silicon/linux/commit/79aa2038d24c495775338c6d72b638885e2cd5e1), [49e3d35945c3](https://github.com/aurora-silicon/linux/commit/49e3d35945c397f7af5a6477540e2ee254b3bd76), [5e21536704b5](https://github.com/aurora-silicon/linux/commit/5e21536704b56c38b478516d1f65bdcfef748a17) | DjDeveloperr | drm/asahi: pgtable: track ownership below firmware-shared roots [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — MEM; `drivers/gpu/drm/asahi/mmu.rs` | M,D | +| [524c57538f14](https://github.com/aurora-silicon/linux/commit/524c57538f142ef38c8b081f5148ffb515e96266), [52ef95b9e763](https://github.com/aurora-silicon/linux/commit/52ef95b9e7635d564449ec08b33a62c6ed26dd12), [87cab74a2498](https://github.com/aurora-silicon/linux/commit/87cab74a2498f5df62f7697786a2743e84125b99), [c0e36ff2a57f](https://github.com/aurora-silicon/linux/commit/c0e36ff2a57f2a461a96b80cebb741f5b76de7ad) | DjDeveloperr | drm/asahi: add the T8140 firmware interface [#171](https://github.com/aurora-silicon/linux/pull/171) | Active translated data; MCache gated by V25G83.mcache=false — FW; `drivers/gpu/drm/asahi/g17/buffer.rs` | B,C,R | +| [ed303abfff0f](https://github.com/aurora-silicon/linux/commit/ed303abfff0fd9e29e8094b828cd3054969bcf97), [0386eb2184a2](https://github.com/aurora-silicon/linux/commit/0386eb2184a268ed6178a6a93a12880806a8022f), [d7d77474e459](https://github.com/aurora-silicon/linux/commit/d7d77474e4590fcd9c1d0e30ab16dc9ca0c73346), [bc4c50abf8ed](https://github.com/aurora-silicon/linux/commit/bc4c50abf8ede16fec7f2a79546a310134b208df) | DjDeveloperr | drm/asahi: request temporary CPU capacity for sustained GPU feeds [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — POWER; `drivers/gpu/drm/asahi/g17/completion.rs` | I,C,R,P | +| [4a77604eb003](https://github.com/aurora-silicon/linux/commit/4a77604eb003ed141f28781769b28953fdabfb1f), [9fdd32f35641](https://github.com/aurora-silicon/linux/commit/9fdd32f3564105f1d012ef20e1c64de912cade1d), [15d553b170f1](https://github.com/aurora-silicon/linux/commit/15d553b170f157558da0343d477c39161f9870f1), [e1e0c5c90faf](https://github.com/aurora-silicon/linux/commit/e1e0c5c90faf0307f95374c83efe3009d58f0bdb) | DjDeveloperr | drm/asahi: observe compute completions without a queue mask [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — SHARED; `drivers/gpu/drm/asahi/g17/runtime/events.rs` | C,H | +| [593e44ca35f7](https://github.com/aurora-silicon/linux/commit/593e44ca35f708e48e0d9b9efde35bfe1052ef35), [e32d6efe3891](https://github.com/aurora-silicon/linux/commit/e32d6efe3891821dce2e6c0e27219ed676ea4f2f), [7dcd8d1467e2](https://github.com/aurora-silicon/linux/commit/7dcd8d1467e2bc1e90eeb04489d4ab7b392351e8), [0c69741cdc26](https://github.com/aurora-silicon/linux/commit/0c69741cdc26f2dbb26754e87f1b53db1f4bed38) | DjDeveloperr | drm/asahi: avoid repeated submission bookkeeping [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/mod.rs` | C,R,H | +| [0ee6827563de](https://github.com/aurora-silicon/linux/commit/0ee6827563deb2e2519534dd938334c90606fb33), [ce33535500cc](https://github.com/aurora-silicon/linux/commit/ce33535500cc78ed2f6695050ce21487902806bc), [ffa3234a92d7](https://github.com/aurora-silicon/linux/commit/ffa3234a92d789514f3e6b05213f417454b73639), [e0d2761bd854](https://github.com/aurora-silicon/linux/commit/e0d2761bd854136076d642b614c9b6078699243e) | DjDeveloperr | drm/asahi: settle unpublished jobs discarded by entity teardown [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — SHARED; `drivers/gpu/drm/asahi/g17/job.rs` | C,R,H | +| [7eb278622690](https://github.com/aurora-silicon/linux/commit/7eb27862269055d524de513e380572b59171a390), [aa659e9a3710](https://github.com/aurora-silicon/linux/commit/aa659e9a3710765dc87d06e50227bdd8476cbd72), [0a72b08543fd](https://github.com/aurora-silicon/linux/commit/0a72b08543fdec99aa8117c8b4983eeee9b8c856), [d41b760af0bc](https://github.com/aurora-silicon/linux/commit/d41b760af0bcc10744b4145cfdbc19dab7625e8e) | DjDeveloperr | drm/asahi: reserve completion capacity for global compute replay [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — REC; `drivers/gpu/drm/asahi/g17/queue/compute.rs` | F,Q | +| [06c4e4a3193e](https://github.com/aurora-silicon/linux/commit/06c4e4a3193e5b0630e343d9730cad097471440a), [1a53f784e14f](https://github.com/aurora-silicon/linux/commit/1a53f784e14f219a44f5faa7a33947c3f4dedcb1), [945285b800d3](https://github.com/aurora-silicon/linux/commit/945285b800d30fcc9a60461b70249b96b4c053ad), [868c80c92152](https://github.com/aurora-silicon/linux/commit/868c80c921527154df5e73a271583a4b0b63fdeb) | DjDeveloperr | drm/asahi: filter compute completion identities before payload reads [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — SHARED; `drivers/gpu/drm/asahi/g17/initdata.rs` | C,H | +| [304da56f2b1b](https://github.com/aurora-silicon/linux/commit/304da56f2b1b927ac837671897273835eeccb91b), [dc74efcea722](https://github.com/aurora-silicon/linux/commit/dc74efcea72275420878d60c358b3af182006eef), [513f7568ad7e](https://github.com/aurora-silicon/linux/commit/513f7568ad7eb30b97ac1309f37bbb3a11229409), [f61c86ab2830](https://github.com/aurora-silicon/linux/commit/f61c86ab28302e46cba8fbdbaa8339aaa4e17b40) | DjDeveloperr | drm/asahi: sample opening counters at the polling deadline [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — REC; `drivers/gpu/drm/asahi/g17/mod.rs` | F,Q | +| [d012beaabd09](https://github.com/aurora-silicon/linux/commit/d012beaabd0985acd8fa07a5e605f9d0c5b50590), [ff5769e16bea](https://github.com/aurora-silicon/linux/commit/ff5769e16bea501f52ff724effb309f107da39e9), [26ba1aa06add](https://github.com/aurora-silicon/linux/commit/26ba1aa06add76aa7e97a843d752fe0d08afd617), [99dd8deec12f](https://github.com/aurora-silicon/linux/commit/99dd8deec12fe28abb844854059bce6a87764199) | DjDeveloperr | drm/asahi: commit canonical binding metadata with each user mapping [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — MEM; `drivers/gpu/drm/asahi/file.rs` | M,D | +| [fef7f56d2bb3](https://github.com/aurora-silicon/linux/commit/fef7f56d2bb33e5bb627a6dc8872b271a5c504cd), [6753db1b2896](https://github.com/aurora-silicon/linux/commit/6753db1b289605b5cc007a8dd8912d948897f86b), [906cacc20dfc](https://github.com/aurora-silicon/linux/commit/906cacc20dfcccb95c6ba7809f8d3ccac4624791), [11b50b74597c](https://github.com/aurora-silicon/linux/commit/11b50b74597c2c3e1f5687d5db50ce0eaa103c21) | DjDeveloperr | drm/asahi: order canonical object invalidation with mapping removal [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — MEM; `drivers/gpu/drm/asahi/file.rs` | M,D | +| [31b8abbea796](https://github.com/aurora-silicon/linux/commit/31b8abbea79664748c2e8a17745bda9e82b119d0), [477cea7fca6e](https://github.com/aurora-silicon/linux/commit/477cea7fca6eab5f5460c2c5e2459fa9cf6e1fa4), [eb1d380fc62d](https://github.com/aurora-silicon/linux/commit/eb1d380fc62d3f8febbcf3a19d173eca44e49f19), [f594e2be7f49](https://github.com/aurora-silicon/linux/commit/f594e2be7f492d0cb4fdbdf5197122418f330036) | DjDeveloperr | drm/asahi: advertise G17 queue and input-sync capabilities [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — SHARED; `drivers/gpu/drm/asahi/g17/mod.rs` | C,R,H | +| [ee8060be8a84](https://github.com/aurora-silicon/linux/commit/ee8060be8a84b43d483ac9d4f76d51f1f5a5e35c), [80c1cad754d8](https://github.com/aurora-silicon/linux/commit/80c1cad754d8711b46ece6be4d7e00c6fd27b6df), [4810fcc3be49](https://github.com/aurora-silicon/linux/commit/4810fcc3be491f1eaa0c78a183c800d08fecbc1d), [98783a0fc38d](https://github.com/aurora-silicon/linux/commit/98783a0fc38dbac9eecb4164ed6c090e3adf591d) | DjDeveloperr | drm/asahi: acquire event records after observing their producer [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — SHARED; `drivers/gpu/drm/asahi/g17/channel.rs` | C,R,H | +| [eaebe2b1f691](https://github.com/aurora-silicon/linux/commit/eaebe2b1f691e89c991bafee6013b2e0d0b5070d), [63c2157467ec](https://github.com/aurora-silicon/linux/commit/63c2157467ec2530afd39ac823a75b74c5d210c9), [7703d07ccd6d](https://github.com/aurora-silicon/linux/commit/7703d07ccd6dbb8c50cbd993a325e0d6a61cad33), [267dc9ba9ea8](https://github.com/aurora-silicon/linux/commit/267dc9ba9ea8e068a3d9d0683a58b72a51291ece) | DjDeveloperr | drm/asahi: discard partial G17 mappings when binding fails [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — MEM; `drivers/gpu/drm/asahi/mmu.rs` | M,D | +| [7f498ba60f1a](https://github.com/aurora-silicon/linux/commit/7f498ba60f1a44401e434975d8d9433f57212539), [56d0e6d6c8b3](https://github.com/aurora-silicon/linux/commit/56d0e6d6c8b340a20cab5869ab6c516170614aa4), [bdfbf517e1fb](https://github.com/aurora-silicon/linux/commit/bdfbf517e1fb478c202e1531a8a084dc7557d4c5), [9f204371cd61](https://github.com/aurora-silicon/linux/commit/9f204371cd6118ecaa4635b76a763e6c4d53b728) | DjDeveloperr | drm/asahi: reuse the reserved output fence for submitted work [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — SHARED; `drivers/gpu/drm/asahi/g17/fence.rs` | C,R,H | +| [1c200975b7c1](https://github.com/aurora-silicon/linux/commit/1c200975b7c1ccbe890e6bdeb097fb9c5d59bfac), [7c55851883a6](https://github.com/aurora-silicon/linux/commit/7c55851883a670690b2e354802b0e265c0430930), [c97c4be0ce4e](https://github.com/aurora-silicon/linux/commit/c97c4be0ce4e1a265fa915b9ec4e634a7db2c3af), [fa771fbd7629](https://github.com/aurora-silicon/linux/commit/fa771fbd7629800795268b560e9e2ff1b0cb806c) | DjDeveloperr | drm/asahi: wake render growth when its prerequisites become ready [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — SHARED; `drivers/gpu/drm/asahi/g17/runtime/events.rs` | R,H | +| [3b92fcd8c068](https://github.com/aurora-silicon/linux/commit/3b92fcd8c068445739b69393a29c7ee7de0bc5b4), [7d4176528d08](https://github.com/aurora-silicon/linux/commit/7d4176528d08bba02e84e23f43b006a7fdbda51f), [4fe42ca7a853](https://github.com/aurora-silicon/linux/commit/4fe42ca7a85341ec777ca2f19e5ed0eb8fdafa82), [956a8e2fd2dd](https://github.com/aurora-silicon/linux/commit/956a8e2fd2ddcb7e9dc596796b79f62b4e017c55) | DjDeveloperr | drm/asahi: skip frontier scans without a cross-engine prefix [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/mod.rs` | C,R,H | +| [cc1601720b58](https://github.com/aurora-silicon/linux/commit/cc1601720b58ceedb0a76606922d7a554da13e17), [63a3c2d02dac](https://github.com/aurora-silicon/linux/commit/63a3c2d02dac4acb23d299c6a00d096f04bf5520), [75ea155f8b2e](https://github.com/aurora-silicon/linux/commit/75ea155f8b2ed4da38c39179a9a653fec0eb1c37), [5026a187d811](https://github.com/aurora-silicon/linux/commit/5026a187d811c441374d11c71a3fa0df58e118fd) | DjDeveloperr | drm/asahi: track CPU feed requests through policy lifetime [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — POWER; `drivers/gpu/drm/asahi/g17/feed.rs` | I,C,R,P | +| [848b36596112](https://github.com/aurora-silicon/linux/commit/848b36596112673711945126a51efd79d0cfa625), [547707b268e9](https://github.com/aurora-silicon/linux/commit/547707b268e96ecff8cadbc6f8b78652ab52d0ba), [a23fed471fe1](https://github.com/aurora-silicon/linux/commit/a23fed471fe1d97b7d75c738cb63d045ad434754), [c42fd8c45c3a](https://github.com/aurora-silicon/linux/commit/c42fd8c45c3a20ed2ec94c40bf5fbcf782220e7c) | DjDeveloperr | drm/asahi: bound dependency scans to the ordered command prefix [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/mod.rs` | C,R,H | +| [7a205aa6b8eb](https://github.com/aurora-silicon/linux/commit/7a205aa6b8eb17488b9882f49beece4f24b49030), [b1087fb75ab1](https://github.com/aurora-silicon/linux/commit/b1087fb75ab153f2cb017c3a08922ece0da18ca7), [b42e20b3a8d3](https://github.com/aurora-silicon/linux/commit/b42e20b3a8d3e03bdf184be067a0149ed6fbee79), [e4d41aaa9c0c](https://github.com/aurora-silicon/linux/commit/e4d41aaa9c0cdd145d5d07fe0b3d0eb209bf0879) | DjDeveloperr | drm/asahi: wait for host timestamp writes in compute prefixes [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — TIMESTAMP; `drivers/gpu/drm/asahi/g17/queue/mod.rs` | T,C | +| [23e316ffe7e5](https://github.com/aurora-silicon/linux/commit/23e316ffe7e5a3fa108eb271778222bdc7f21ee0), [2a2aff37cd8e](https://github.com/aurora-silicon/linux/commit/2a2aff37cd8e3771dea46ebdc3e45dc7b4415b4c), [55df4049246b](https://github.com/aurora-silicon/linux/commit/55df4049246b6da9482b15411e173a11cc7f04e8), [81d324022d0f](https://github.com/aurora-silicon/linux/commit/81d324022d0f97346639b06a6abaa8b86d480ead) | DjDeveloperr | drm/asahi: release successful submission work-state ownership [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — SHARED; `drivers/gpu/drm/asahi/g17/completion.rs` | C,R,H | +| [18ef3a813070](https://github.com/aurora-silicon/linux/commit/18ef3a813070620821371e437d1557a1dd1c79cf), [cfe7880a5d16](https://github.com/aurora-silicon/linux/commit/cfe7880a5d167059b871475fbc5abe60248fcf3f), [1660e75c7e3c](https://github.com/aurora-silicon/linux/commit/1660e75c7e3ccb4b906662db275d6c5ccaebd452), [e0f7410fd5b3](https://github.com/aurora-silicon/linux/commit/e0f7410fd5b368d06ab17d802720f9d76e12880c) | DjDeveloperr | drm/asahi: preserve exact historical command prefixes [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — SHARED; `drivers/gpu/drm/asahi/g17/command.rs` | C,R,H | +| [5c02e806d300](https://github.com/aurora-silicon/linux/commit/5c02e806d3004d2bb01706362298d8a6802e7433), [6507ba3ca670](https://github.com/aurora-silicon/linux/commit/6507ba3ca670d34720c6c8bfea44836b8228772b), [c7986b27e066](https://github.com/aurora-silicon/linux/commit/c7986b27e066b3d3b4fd81e95b6e174cd6c21807), [fbfb5d96f25e](https://github.com/aurora-silicon/linux/commit/fbfb5d96f25efbf2b1571ca635b195e0ad4a54be) | DjDeveloperr | drm/asahi: bring the cleanroom driver up on Neo hardware [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — SHARED; `drivers/gpu/drm/asahi/g17/buffer.rs` | C,R,H | +| [d0b66e8a8778](https://github.com/aurora-silicon/linux/commit/d0b66e8a877898097d40a380483c09e6be471532), [5fa0b934070c](https://github.com/aurora-silicon/linux/commit/5fa0b934070c615c3c745f5759d2fb7f90eaee09), [01d0b0bed7a7](https://github.com/aurora-silicon/linux/commit/01d0b0bed7a763fa775654a0a115c01f38b34584), [66af386568f7](https://github.com/aurora-silicon/linux/commit/66af386568f748d2499be3e3587ce1e7ca0ae9cd) | Ryan Murray | drm/asahi: report live GEM object accounting [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — MEM; `drivers/gpu/drm/asahi/asahi.rs` | M,D | +| [722064f1be0d](https://github.com/aurora-silicon/linux/commit/722064f1be0d171bc5d496cca352f2db6285c6c0), [f313fc4bd1a1](https://github.com/aurora-silicon/linux/commit/f313fc4bd1a1c91bad6657f7a136cc7b0f88dbe4), [2c076e0de6fe](https://github.com/aurora-silicon/linux/commit/2c076e0de6fe0a0a38a2041b02d5b9e75347375a), [f7fcc50568b9](https://github.com/aurora-silicon/linux/commit/f7fcc50568b9467f241a425b20b92ff1de10f641) | Ryan Murray | drm/asahi: port j700 queue accounting and compute recovery isolation [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — REC; `drivers/gpu/drm/asahi/g17/context.rs` | F,Q | +| [b1e288fb887d](https://github.com/aurora-silicon/linux/commit/b1e288fb887dc74d1b428d478493c6c568d3b02d), [b3b17a139f25](https://github.com/aurora-silicon/linux/commit/b3b17a139f25e8164dfdfe31d7a0c03fc2a4fa85), [2d84d7470c0f](https://github.com/aurora-silicon/linux/commit/2d84d7470c0f0d60c11c9df9e9665ee6a6a2bf71), [3c65418ee698](https://github.com/aurora-silicon/linux/commit/3c65418ee698735d33f299fe208aa8c22ff310a4) | DjDeveloperr | drm/asahi: account once per compute queue transition [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/compute.rs` | C,H | +| [4273fb6241bb](https://github.com/aurora-silicon/linux/commit/4273fb6241bb6f9083dd8feb263fee0924fffa94), [fa2e76747a7f](https://github.com/aurora-silicon/linux/commit/fa2e76747a7f3e264fc9c8dfaaa616627cf4e70f), [2ebb1eb92796](https://github.com/aurora-silicon/linux/commit/2ebb1eb927960a96456df1369294d3d6e392a591), [982df2dcc66e](https://github.com/aurora-silicon/linux/commit/982df2dcc66e2cb2012df476c4325c8fce016dda) | DjDeveloperr | drm: carry shared GPUVM and scheduler interfaces used by the G17 review [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — DRM; `drivers/gpu/drm/drm_gpuvm.c` | C,H,M,F | +| [c163a8df57f9](https://github.com/aurora-silicon/linux/commit/c163a8df57f986f9cb2f3c3275a1dd2b627d7dba), [751ee0b363e7](https://github.com/aurora-silicon/linux/commit/751ee0b363e7d2733cb452989b6c5fcca182f087), [84f4e1fd0eaf](https://github.com/aurora-silicon/linux/commit/84f4e1fd0eaf13c985e6535d2906b68f1c519947), [ca389fba9b6c](https://github.com/aurora-silicon/linux/commit/ca389fba9b6c7c9354c2f765fe81c5ef5f069f3b) | DjDeveloperr | drm/asahi: correct reviewed lifetime, failure and submission paths [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — RTKIT; `drivers/gpu/drm/asahi/g17/channel.rs` | B,F | +| [3a61b700f19d](https://github.com/aurora-silicon/linux/commit/3a61b700f19d1b2a63017c02359a0eb30360c496), [d297d20ba207](https://github.com/aurora-silicon/linux/commit/d297d20ba207abd5b9c4d7b80aa370dd5a379a4f), [ceb8f96ba823](https://github.com/aurora-silicon/linux/commit/ceb8f96ba8232067893d3956bcc4c335bda7e268), [d9b7b82975e2](https://github.com/aurora-silicon/linux/commit/d9b7b82975e2dad4b9a5941ade2f4debadd61c6c) | DjDeveloperr | drm/asahi: order render timestamp publication independently of GPU retirement [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — TIMESTAMP; `drivers/gpu/drm/asahi/g17/completion.rs` | T,C | +| [0f89327272be](https://github.com/aurora-silicon/linux/commit/0f89327272be80ecf44b643c4198614255024367), [212a7b2ccc2d](https://github.com/aurora-silicon/linux/commit/212a7b2ccc2d7a972478f4feed4c19d77cf7e01d), [3bf578fd8958](https://github.com/aurora-silicon/linux/commit/3bf578fd8958a40af5ab8b3ea892749a1dc5b4bf), [e22376a0ca67](https://github.com/aurora-silicon/linux/commit/e22376a0ca679b5359fdef009b2479616619677b) | DjDeveloperr | drm/sched: separate Rust job timeout state from submission state [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — DRM; `drivers/gpu/drm/asahi/g17/job.rs` | C,H,M,F | +| [ca7ec4ac8767](https://github.com/aurora-silicon/linux/commit/ca7ec4ac8767714b3a4c28a8bc4e3121e5a346bf), [3810bcb5e35c](https://github.com/aurora-silicon/linux/commit/3810bcb5e35cb34abd3e80d5998c94ddc747cb34), [20560fe15a23](https://github.com/aurora-silicon/linux/commit/20560fe15a23c2dd8db188a074e245c20bcf9858), [3e464649d372](https://github.com/aurora-silicon/linux/commit/3e464649d37234b0f47dfe3c22d3f73cbb6ef45b) | DjDeveloperr | drm/asahi: observe completed render work before a deferred VM failure [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — SHARED; `drivers/gpu/drm/asahi/g17/runtime/render/submit.rs` | R,H | +| [6a57525c17e8](https://github.com/aurora-silicon/linux/commit/6a57525c17e8cdf0fcb5965e367c8bca29f6a2f4), [7b30ae9dd4eb](https://github.com/aurora-silicon/linux/commit/7b30ae9dd4eb5a5da852deae9709ef4c10ac09c8), [d20cdd0dba5a](https://github.com/aurora-silicon/linux/commit/d20cdd0dba5a51899ea79cb8ac2df9e14cd3cd8a), [83cf9f5ad0ba](https://github.com/aurora-silicon/linux/commit/83cf9f5ad0baf71001cc0aa9c25c83f27f4a219a) | DjDeveloperr | drm/asahi: publish child page tables before linking them [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — MEM; `drivers/gpu/drm/asahi/pgtable.rs` | M,D | +| [c6754b181576](https://github.com/aurora-silicon/linux/commit/c6754b181576b5fb5467f9735e7abe4451271681), [03f169480b45](https://github.com/aurora-silicon/linux/commit/03f169480b452a052c707ddc07b36c2be478d645), [da35a7e8b950](https://github.com/aurora-silicon/linux/commit/da35a7e8b9502f0afb30feac9aa41b03b0b7d080), [b321f0e70539](https://github.com/aurora-silicon/linux/commit/b321f0e7053949d9144028d0a465a4f03eb6cc83) | DjDeveloperr | drm/asahi: retire deferred mapping cleanup without allocating [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — DRM; `drivers/gpu/drm/asahi/mmu.rs` | C,H,M,F | +| [26b6f8056c9a](https://github.com/aurora-silicon/linux/commit/26b6f8056c9a913656c75753e1d719f5771a2496), [85b9304050b9](https://github.com/aurora-silicon/linux/commit/85b9304050b9331ed8206ab69532c1bb42fe9582), [982da05e5f83](https://github.com/aurora-silicon/linux/commit/982da05e5f831a54800a778df91464ab6d6372b2), [7d57556a8962](https://github.com/aurora-silicon/linux/commit/7d57556a8962dd49aadbb24af772ee5240b8c1c1) | DjDeveloperr | drm/asahi: keep compute timestamps outside shared queue aliases [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — TIMESTAMP; `drivers/gpu/drm/asahi/g17/completion.rs` | T,C | +| [a244ed25307c](https://github.com/aurora-silicon/linux/commit/a244ed25307c3d03df0a57def5fffa80d19209fa), [ee1b5a0211c1](https://github.com/aurora-silicon/linux/commit/ee1b5a0211c14b5a799304948bd91fbfebaed35c), [733634bcb0c4](https://github.com/aurora-silicon/linux/commit/733634bcb0c4de6e9885878dc8b1aad744353c83), [e3cfe6674874](https://github.com/aurora-silicon/linux/commit/e3cfe66748744b6aadf6b863b57818adb0721cdd) | DjDeveloperr | drm/shmem-helper: add revocable CPU mappings and idle backing release [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — DRM; `drivers/gpu/drm/asahi/gem.rs` | C,H,M,F | +| [9afbd440e531](https://github.com/aurora-silicon/linux/commit/9afbd440e5316ca0831289cd5dec955003554737), [97bab816b9d2](https://github.com/aurora-silicon/linux/commit/97bab816b9d20dc0b7433baf2077bc2225d9e36a), [449bb6888c6f](https://github.com/aurora-silicon/linux/commit/449bb6888c6feeb79e6390cfe00dd7d4d58b6261), [e2b08ebbcadc](https://github.com/aurora-silicon/linux/commit/e2b08ebbcadc3a9ebe2733140e1a88c62af87ba1) | DjDeveloperr | drm/asahi: reclaim idle private VM backing under memory pressure [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — MEM; `drivers/gpu/drm/asahi/file.rs` | M,D | +| [18e1bce60e1a](https://github.com/aurora-silicon/linux/commit/18e1bce60e1a82f3872012d13fe6f5b51684eb38), [babe2ba0c5a6](https://github.com/aurora-silicon/linux/commit/babe2ba0c5a68f0d3b81085736f52f57f32cbbfe), [717d6fe9e152](https://github.com/aurora-silicon/linux/commit/717d6fe9e152af9bdda4a73a81151145cb2681ca), [e82b27902f91](https://github.com/aurora-silicon/linux/commit/e82b27902f91d8c833395a966366d0ffc30769c3) | DjDeveloperr | drm/asahi: free a closed context's render pool once its release is consumed [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — GMEM; `drivers/gpu/drm/asahi/g17/context.rs` | M,C,R | +| [177e22791d7c](https://github.com/aurora-silicon/linux/commit/177e22791d7cfecb3b6c6b6315adf674ef1c9b30), [431b18cf9072](https://github.com/aurora-silicon/linux/commit/431b18cf9072a8618cbab77cfcbde3952cdb5623), [20a55bacd2fb](https://github.com/aurora-silicon/linux/commit/20a55bacd2fb91e52b6bb5cbb3e88e5218b2f726), [fa210660c43c](https://github.com/aurora-silicon/linux/commit/fa210660c43c1e8ffc4c0705792febb70c29e779) | DjDeveloperr | drm/asahi: hold idle descent for every first render pair install [#171](https://github.com/aurora-silicon/linux/pull/171) | Gated — MMIO; `drivers/gpu/drm/asahi/g17/runtime/render.rs` | B,I | +| [06175c4cc1bc](https://github.com/aurora-silicon/linux/commit/06175c4cc1bccd44765a75a70278fedb27ed9fe5), [2f10cc462301](https://github.com/aurora-silicon/linux/commit/2f10cc462301b9d1a1e64bd31468268d90a5d96d), [e94491e80ac1](https://github.com/aurora-silicon/linux/commit/e94491e80ac12de2fb9b741739b560ab17ecbb34), [d7715ebbd2d3](https://github.com/aurora-silicon/linux/commit/d7715ebbd2d3397958f08442d096a2476d32cb6a) | DjDeveloperr | drm/asahi: add GEM_MADVISE and purge advised objects under memory pressure [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — DRM; `drivers/gpu/drm/asahi/driver.rs` | C,H,M,F | +| [f338bb052975](https://github.com/aurora-silicon/linux/commit/f338bb05297509f7b87714c5727fb99a8fea373a), [c2e8f1ce84f1](https://github.com/aurora-silicon/linux/commit/c2e8f1ce84f1f72a14e6395fa5246a133211432c), [85271d321506](https://github.com/aurora-silicon/linux/commit/85271d321506ec35cc7e6a82665649e7fd2e4f8c), [c8c442ae6a83](https://github.com/aurora-silicon/linux/commit/c8c442ae6a83502a90fca56d1b8e08e669ce03cb) | DjDeveloperr | drm/asahi: drop the sysfs object and queue-slot counters [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — MEM; `drivers/gpu/drm/asahi/g17/context.rs` | M,D | +| [3bb318ea84a1](https://github.com/aurora-silicon/linux/commit/3bb318ea84a1cd2d7661948b347270c8eaf71931), [8b1bcc708f82](https://github.com/aurora-silicon/linux/commit/8b1bcc708f820baa04c2998acf8513a0146d59cf), [ed235501b653](https://github.com/aurora-silicon/linux/commit/ed235501b6531eb218d3b058bab11a807ae17713), [152092e32a7f](https://github.com/aurora-silicon/linux/commit/152092e32a7fd7a7b3a96b8e45be7db78934b7d8) | DjDeveloperr | drm/asahi: drop the stalled scheduler-release warning [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — SHARED; `drivers/gpu/drm/asahi/g17/runtime/teardown.rs` | C,R,H | +| [110dd19ab7e0](https://github.com/aurora-silicon/linux/commit/110dd19ab7e0263538c8301f903471b1700eac12), [815912e331c3](https://github.com/aurora-silicon/linux/commit/815912e331c32ab6468a108ab34c6b26d60faecb), [9d8357e27590](https://github.com/aurora-silicon/linux/commit/9d8357e275907651adbb7022b812f789ece7d040), [24ce1bcdd8eb](https://github.com/aurora-silicon/linux/commit/24ce1bcdd8ebf0f8664c043a639c0201286f94e5) | DjDeveloperr | drm/asahi: build constant firmware objects from word tables [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — SHARED; `drivers/gpu/drm/asahi/g17/fw/initdata.rs` | C,R,H | +| [b5a72b8cb8a5](https://github.com/aurora-silicon/linux/commit/b5a72b8cb8a581d84a4c35c2b4ecc11cd50fabfb), [cea457b72a30](https://github.com/aurora-silicon/linux/commit/cea457b72a308addcffbef0555408e3c2f021c7b), [8e98d0432927](https://github.com/aurora-silicon/linux/commit/8e98d0432927d780061fcc55516769f3d830291f), [7e317cd1218e](https://github.com/aurora-silicon/linux/commit/7e317cd1218e5461124fbfc8db7dd72e71253f8e) | DjDeveloperr | drm/asahi: grow the scheduler-release table instead of failing a close [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — SHARED; `drivers/gpu/drm/asahi/g17/teardown.rs` | C,R,H | +| [8afbe186d27a](https://github.com/aurora-silicon/linux/commit/8afbe186d27aab81bc71008838387799f1d9d4df), [c4f9aff1f720](https://github.com/aurora-silicon/linux/commit/c4f9aff1f720075ed3b059e6d82a5d8be5663768), [be6a3b5d938f](https://github.com/aurora-silicon/linux/commit/be6a3b5d938f2c51ef5b6d60990dcefd64cb677f), [18dbc2de6f21](https://github.com/aurora-silicon/linux/commit/18dbc2de6f21c92c49ffafce39a38c8d6439a5b5) | DjDeveloperr | drm/asahi: add a render flag for passes with at most four known primitives [#171](https://github.com/aurora-silicon/linux/pull/171) | Gated — FEW; `drivers/gpu/drm/asahi/g17/command.rs` | R | +| [7d34f2f65865](https://github.com/aurora-silicon/linux/commit/7d34f2f658658c1fd829700f2e14211a1b150ccd), [b1ec7881bf69](https://github.com/aurora-silicon/linux/commit/b1ec7881bf69ab6b1678761dec5552b28aa414e1), [3318d3e23f8e](https://github.com/aurora-silicon/linux/commit/3318d3e23f8e49a5721f9e2da0eea674eece1d2e), [c00f3a06ed2d](https://github.com/aurora-silicon/linux/commit/c00f3a06ed2d0400a5ec210c856f3a382971f033) | DjDeveloperr | drm/asahi: add a render flag that orders only the fragment stage on barriers [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — SHARED; `drivers/gpu/drm/asahi/g17/command.rs` | R,H | +| [e2b1485ec599](https://github.com/aurora-silicon/linux/commit/e2b1485ec599011654ac1f6ccc4dc78350b50566), [763771c2f3d0](https://github.com/aurora-silicon/linux/commit/763771c2f3d0613ecacaeb20d1cfd21809ead7d5), [0751d1c543c9](https://github.com/aurora-silicon/linux/commit/0751d1c543c933f65ec2ce2606d5effd25206721), [64820e0cb952](https://github.com/aurora-silicon/linux/commit/64820e0cb952592fff5708712a4b4766a8a12f4c) | DjDeveloperr | drm/asahi: give back the USC backing of idle retained compute queues [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — POWER; `drivers/gpu/drm/asahi/g17/freelist.rs` | I,C,R,P | +| [6825b2e8b6f7](https://github.com/aurora-silicon/linux/commit/6825b2e8b6f7d51be5f21a46821541ae8828c6e5), [9e693050e446](https://github.com/aurora-silicon/linux/commit/9e693050e44633bcf0503ec5b864a292d513b563), [ffbf4b465de9](https://github.com/aurora-silicon/linux/commit/ffbf4b465de966c4a5bf3b2e2881fd5678f98bfa), [3db48c0ac600](https://github.com/aurora-silicon/linux/commit/3db48c0ac600b5e919c4d2713dbd84e9b12b4ec4) | DjDeveloperr | drm/asahi: drop an exited owner's context from idle retained compute queues [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — POWER; `drivers/gpu/drm/asahi/g17/context.rs` | I,C,R,P | +| [dbea31c253f3](https://github.com/aurora-silicon/linux/commit/dbea31c253f3d1845a4998abed2db88177e82d54), [e85d3c35771a](https://github.com/aurora-silicon/linux/commit/e85d3c35771a27fc3721f13d572c2538e898837f), [9964d01e50ad](https://github.com/aurora-silicon/linux/commit/9964d01e50add98a41985adc69ace3bc5dc867f1), [5427403e7135](https://github.com/aurora-silicon/linux/commit/5427403e7135dd886ee52a78d73639bd5a2ccb17) | DjDeveloperr | drm/asahi: keep reclaim away from VMs that are in use or were just restored [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — MEM; `drivers/gpu/drm/asahi/mmu/lifetime.rs` | M,D | +| [34de15489113](https://github.com/aurora-silicon/linux/commit/34de15489113f71dd93695681d735afaddef10db), [c5fdbf93b7c8](https://github.com/aurora-silicon/linux/commit/c5fdbf93b7c8280dd1d266df8b4298b6b5c0645c), [5eb07ab2539b](https://github.com/aurora-silicon/linux/commit/5eb07ab2539bf7628366644c96a4742c934b9a78), [1c62cd0853b6](https://github.com/aurora-silicon/linux/commit/1c62cd0853b670104424bcfddeee7f7d9279304e) | DjDeveloperr | drm/asahi: count bindings and VM creation as reclaim activity [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — MEM; `drivers/gpu/drm/asahi/mmu/lifetime.rs` | M,D | +| [9bca711b6542](https://github.com/aurora-silicon/linux/commit/9bca711b65427b7dc0c384e6de2a783cde6e0160), [84e172f23883](https://github.com/aurora-silicon/linux/commit/84e172f23883c27a8eb0cdc85fa81de9c8879eb1), [3202a8b8912f](https://github.com/aurora-silicon/linux/commit/3202a8b8912f39ff7b7c06acc5fd604d5d0c4769), [dec3bc9eb622](https://github.com/aurora-silicon/linux/commit/dec3bc9eb622df05eca30716834c35506ee31f9f) | DjDeveloperr | drm/asahi: set the idle-VM reclaim quiet window to 2000 ms [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — MEM; `drivers/gpu/drm/asahi/mmu/residency.rs` | M,D | +| [f1018934472b](https://github.com/aurora-silicon/linux/commit/f1018934472b655ba44f1fcbfa2f3be2fdedb1fd), [d2046dcecf61](https://github.com/aurora-silicon/linux/commit/d2046dcecf61aceec6af3d013da811e147e3c410), [4963fa334b24](https://github.com/aurora-silicon/linux/commit/4963fa334b2439a0fc831ee3a5a8db008712a307), [1c1abce16316](https://github.com/aurora-silicon/linux/commit/1c1abce16316282e15f5e471b8ef0b5a33acc038) | DjDeveloperr | drm/asahi: release the VM status mirror object when the VM handle closes [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — MEM; `drivers/gpu/drm/asahi/g17/status.rs` | M,D | +| [cc0e08ecbe61](https://github.com/aurora-silicon/linux/commit/cc0e08ecbe61eae09d5be1619a0e6373888f7d02), [503bc0e39d66](https://github.com/aurora-silicon/linux/commit/503bc0e39d665ac97ac53e0b78b2851ca61d2242), [8057982690f1](https://github.com/aurora-silicon/linux/commit/8057982690f1f1954a83f62382873a8baf03bb8e), [6af79e6e57ef](https://github.com/aurora-silicon/linux/commit/6af79e6e57ef80d2b93d4c428956ec6cdfb11a3a) | DjDeveloperr | drm/asahi: unmap detached compute owners and free vacated pools off-lock [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — GMEM; `drivers/gpu/drm/asahi/g17/freelist.rs` | M,C,R | +| [90e691e8fe49](https://github.com/aurora-silicon/linux/commit/90e691e8fe49fa13756b6a64d22074c6e4e4a6cb), [b00ba3dcbfeb](https://github.com/aurora-silicon/linux/commit/b00ba3dcbfebf63d761a8018109bef6067a2cf8b), [6f6bef1b9fd7](https://github.com/aurora-silicon/linux/commit/6f6bef1b9fd7c46f0885eea0ece01a811e08c7d1), [0b4bd1537cc7](https://github.com/aurora-silicon/linux/commit/0b4bd1537cc71a1796b976c05cdab24e76549303) | DjDeveloperr | drm/asahi: retire a compute command on its record when its stamp never follows [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/compute.rs` | C,H | +| [f227c7671c0f](https://github.com/aurora-silicon/linux/commit/f227c7671c0fa54ab73b1477aaf6bbc482e29b8b), [2ceb94e6a1a7](https://github.com/aurora-silicon/linux/commit/2ceb94e6a1a7939d520401fcbb0f41ce5ce9f47a), [e978503f0047](https://github.com/aurora-silicon/linux/commit/e978503f00475864733aefc433d758f0254f401f), [f647d69cc504](https://github.com/aurora-silicon/linux/commit/f647d69cc5043bf1d3389c8e250e78935e5afa24) | DjDeveloperr | drm/asahi: log the first GPU work failure reported to each G17 VM [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — OBS; `drivers/gpu/drm/asahi/g17/status.rs` | O,F | +| [0685cf3905b7](https://github.com/aurora-silicon/linux/commit/0685cf3905b73081560c7c1adc28f76efda774fa), [e8e17790b150](https://github.com/aurora-silicon/linux/commit/e8e17790b150da70debb5e9a1c31027883ad90d3), [85ab080b193f](https://github.com/aurora-silicon/linux/commit/85ab080b193fa481276ebcb9393a93b25024384f), [c8ea00a9ed62](https://github.com/aurora-silicon/linux/commit/c8ea00a9ed621e2e5c7c84461091fda3a9b7cb7b) | DjDeveloperr | drm/asahi: name the completion path in the first-failure log line [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — OBS; `drivers/gpu/drm/asahi/g17/completion.rs` | O,F | +| [ef4fa278cd1a](https://github.com/aurora-silicon/linux/commit/ef4fa278cd1abc81d229ec5853175c35537a962c), [bbbbb8aba0a3](https://github.com/aurora-silicon/linux/commit/bbbbb8aba0a3a08da0b31f31dd0fa1655089c2b0), [ec743d4d34f8](https://github.com/aurora-silicon/linux/commit/ec743d4d34f89fa2ea639801062c957f807c54aa), [5d2a444327af](https://github.com/aurora-silicon/linux/commit/5d2a444327afc38543beb78a252e23661e4d4ecc) | DjDeveloperr | drm/asahi: use one monotonic clock helper across the G17 runtime [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — SHARED; `drivers/gpu/drm/asahi/g17/mod.rs` | C,R,H | +| [860e86da2994](https://github.com/aurora-silicon/linux/commit/860e86da2994a4584ba5ce013c5ef5d79cbda111), [bca35f80a993](https://github.com/aurora-silicon/linux/commit/bca35f80a99330132651e21c61f4d0e5da583cd9), [388a083cca9a](https://github.com/aurora-silicon/linux/commit/388a083cca9a1ff7b98f14dc0e598a63494e7d89), [6850c91310a9](https://github.com/aurora-silicon/linux/commit/6850c91310a913b5ff9646261250e62b129f4ce8) | DjDeveloperr | drm/asahi: grow the scheduler-release table silently [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — SHARED; `drivers/gpu/drm/asahi/g17/teardown.rs` | C,R,H | +| [3cd28842a502](https://github.com/aurora-silicon/linux/commit/3cd28842a502864d73cbfeb7018d6f6e2e00dcdc), [033ec9a3b717](https://github.com/aurora-silicon/linux/commit/033ec9a3b71756814007305dfaf437f3e26c1195), [0c8194353943](https://github.com/aurora-silicon/linux/commit/0c8194353943469158f8651c5ec51723e130afa3), [e60f49eede0e](https://github.com/aurora-silicon/linux/commit/e60f49eede0ecf38cb206c4f59ff832b2aacc1d6) | DjDeveloperr | drm/asahi: give back the descriptor ring of vacated compute queues [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — GMEM; `drivers/gpu/drm/asahi/g17/queue/compute.rs` | M,C,R | +| [5a98c966960a](https://github.com/aurora-silicon/linux/commit/5a98c966960a4310f741538fea1fc04724acffdb), [c9c1859181ab](https://github.com/aurora-silicon/linux/commit/c9c1859181ab17b15b76c74dc36ef00ad7cac96e), [75535b36cc0e](https://github.com/aurora-silicon/linux/commit/75535b36cc0ea75d394b20e11b993bf22e1efe20), [5d630414e13f](https://github.com/aurora-silicon/linux/commit/5d630414e13f4e017628430820fff73a86e97de9) | DjDeveloperr | drm/asahi: return killed compute queues to service with fresh backing [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — GMEM; `drivers/gpu/drm/asahi/g17/queue/compute.rs` | M,C,R | +| [6cae1d77a10a](https://github.com/aurora-silicon/linux/commit/6cae1d77a10aee3e2cd7f52c92eb62b957f6e7ff), [b138b1c78a0e](https://github.com/aurora-silicon/linux/commit/b138b1c78a0ebbba6cef6b3976fc39bfd8b1e3d4), [4457298833f1](https://github.com/aurora-silicon/linux/commit/4457298833f1001a90c15d9277dee87ad6e11927), [16822976f9d3](https://github.com/aurora-silicon/linux/commit/16822976f9d375ddbdc788d968077a15660be5d7) | DjDeveloperr | drm/asahi: give back the render pool backing of evicted idle clients [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — MEM; `drivers/gpu/drm/asahi/g17/context.rs` | M,D | +| [8d77b4f20652](https://github.com/aurora-silicon/linux/commit/8d77b4f20652f18c7fb4e6ae9184eb88ab7e779e), [39c1fc51692c](https://github.com/aurora-silicon/linux/commit/39c1fc51692c3bfcb37c274ce84dfe812bceaaae), [7e8fe19766fd](https://github.com/aurora-silicon/linux/commit/7e8fe19766fd1b895120a8e7bd5fe78f7aca2a7c), [bff3853f260b](https://github.com/aurora-silicon/linux/commit/bff3853f260b02bdd9605bc6690905cb70aca29f) | DjDeveloperr | drm/asahi: carry a scheduling profile from DRM queue priority to firmware [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — PRIORITY; `drivers/gpu/drm/asahi/g17/context.rs` | Q | +| [a15128aa41ec](https://github.com/aurora-silicon/linux/commit/a15128aa41ecb760c5669ec03d8ffee55213bd6c), [87744d8c30b2](https://github.com/aurora-silicon/linux/commit/87744d8c30b28d86a4cc2c45b2e5009b730ed6b4), [fe676c6f4915](https://github.com/aurora-silicon/linux/commit/fe676c6f4915ec15bb3ea0880101eb5b29cfc255), [009b0fb93ba3](https://github.com/aurora-silicon/linux/commit/009b0fb93ba35380563055aa8bc725f36c505262) | DjDeveloperr | drm/asahi: drop two defensive clippy allowances in the VM binding paths [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — MEM; `drivers/gpu/drm/asahi/mmu.rs` | M,D | +| [1567322c2517](https://github.com/aurora-silicon/linux/commit/1567322c251710c365592380a195ceb22979d781), [408df60b8f35](https://github.com/aurora-silicon/linux/commit/408df60b8f35a1f510750df51f5e46bfb51d51a4), [3d1be9ce9669](https://github.com/aurora-silicon/linux/commit/3d1be9ce966988190d72dcad1d0fa4dd1b9204c9), [410d8f80e9ab](https://github.com/aurora-silicon/linux/commit/410d8f80e9ab6b405d7f918901fcbe883bb2d9b2) | DjDeveloperr | drm/asahi: build the G17 boot objects region by region [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — SHARED; `drivers/gpu/drm/asahi/g17/initdata.rs` | C,R,H | +| [0e3bd3afd897](https://github.com/aurora-silicon/linux/commit/0e3bd3afd897637d7c42d54c8ca9adc25e5a691c), [2fa38fc95677](https://github.com/aurora-silicon/linux/commit/2fa38fc956777c4e357d44db60d1b7aafaac273f), [456e6601e69d](https://github.com/aurora-silicon/linux/commit/456e6601e69d15067bcccde93498fd58e22977a1), [f5bd61786e3b](https://github.com/aurora-silicon/linux/commit/f5bd61786e3b537fd811f0ea27d2ec33874232b0) | DjDeveloperr | drm/asahi: split the G17 render pair install paths out of build_render_pair [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — SHARED; `drivers/gpu/drm/asahi/g17/runtime/render/prepare.rs` | R,H | +| [fe926de1f191](https://github.com/aurora-silicon/linux/commit/fe926de1f191b798b49cbe57e36dfba0e0cc4ac7), [e89e97a5241c](https://github.com/aurora-silicon/linux/commit/e89e97a5241cd22b95cb60304f1e2426698c45af), [ef1251b62428](https://github.com/aurora-silicon/linux/commit/ef1251b62428a647c7ed4311cc6223ca1c8fc80a), [e0fa28b70ca9](https://github.com/aurora-silicon/linux/commit/e0fa28b70ca92d7942809827a7cff7e230d68826) | DjDeveloperr | drm/asahi: split the G17 render publication into preflight, barriers and announce [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/render.rs` | R,H | +| [311b9ea8bf83](https://github.com/aurora-silicon/linux/commit/311b9ea8bf835bc7a53866f34b8520ffdc17ef01), [1235a9ee13fe](https://github.com/aurora-silicon/linux/commit/1235a9ee13fe9ed0bf846970e5f5cf9a3049e290), [f76a38f8511f](https://github.com/aurora-silicon/linux/commit/f76a38f8511fd9df3d53e30b899a780c17353d28), [74bc2f09ab74](https://github.com/aurora-silicon/linux/commit/74bc2f09ab74832be661557cacf374ee5fff11d7) | DjDeveloperr | drm/asahi: split the G17 compute publication preflight and queue configuration [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/compute.rs` | C,H | +| [f22f4cfb5b97](https://github.com/aurora-silicon/linux/commit/f22f4cfb5b97b17cf49e7ca9feb877313156f246), [74e91a7f9ad5](https://github.com/aurora-silicon/linux/commit/74e91a7f9ad5d2f61bd5846713e27838c6f5324a), [49e9337b68a4](https://github.com/aurora-silicon/linux/commit/49e9337b68a41b5f9a3a26d8d72e8b0eecc16844), [5ad7b191f7e6](https://github.com/aurora-silicon/linux/commit/5ad7b191f7e698e3ef140163c6ecf15d3e57ff85) | DjDeveloperr | drm/asahi: size the compute descriptor ring to the kicks that can be in flight [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — GMEM; `drivers/gpu/drm/asahi/g17/queue/compute.rs` | M,C,R | +| [bb5149df6a74](https://github.com/aurora-silicon/linux/commit/bb5149df6a74ead7da49be6953c63f4033229ff3), [9d92af630ad2](https://github.com/aurora-silicon/linux/commit/9d92af630ad2f55fe898d653e29c363c131ee4e3), [6d54c0fd885c](https://github.com/aurora-silicon/linux/commit/6d54c0fd885ceded70867de874f27987fc8f69b4), [5f6490414a68](https://github.com/aurora-silicon/linux/commit/5f6490414a683eef149ba6de38cbc27ba62f9a26) | DjDeveloperr | drm/asahi: pack G17 tiler heap blocks contiguously in their backing [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — GMEM; `drivers/gpu/drm/asahi/g17/buffer.rs` | M,C,R | +| [46c70ecebde4](https://github.com/aurora-silicon/linux/commit/46c70ecebde4be2fbd3e82e8311946a156504bf1), [f024202d33e8](https://github.com/aurora-silicon/linux/commit/f024202d33e8832b01e0cff6006fbb9e2b1178a3), [29ff1f70b488](https://github.com/aurora-silicon/linux/commit/29ff1f70b4880e79a8b980adc4d44d4eacb7c7e1), [d2a2c2fa97b9](https://github.com/aurora-silicon/linux/commit/d2a2c2fa97b994595209843720ad78b99b93f16a) | DjDeveloperr | drm/asahi: allocate only the G17 boot pool blocks that are used [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — GMEM; `drivers/gpu/drm/asahi/g17/initdata.rs` | M,C,R | +| [56b8769a1364](https://github.com/aurora-silicon/linux/commit/56b8769a1364bc6c46ab48463afa203a5aec92bb), [614f11f2eb30](https://github.com/aurora-silicon/linux/commit/614f11f2eb30471e02d42f7eab08e9bf4ec4cd41), [c304acb009f0](https://github.com/aurora-silicon/linux/commit/c304acb009f0264d1485596f651495d66efb0fec), [272955b009fe](https://github.com/aurora-silicon/linux/commit/272955b009fe844d4d06361365f166d75b6b451b) | DjDeveloperr | drm/asahi: give back idle G17 tiler heap growth beyond recent use [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — POWER; `drivers/gpu/drm/asahi/g17/buffer.rs` | I,C,R,P | +| [3434c1aab8a6](https://github.com/aurora-silicon/linux/commit/3434c1aab8a6c5d305198347040b8237c4bdce06), [35fe32db0e80](https://github.com/aurora-silicon/linux/commit/35fe32db0e806643f201fc6871095da0e68cfaf8), [1464c4bb1ade](https://github.com/aurora-silicon/linux/commit/1464c4bb1ade2f3fe0625eb93776b26a7f91db96), [c01af7a9e73c](https://github.com/aurora-silicon/linux/commit/c01af7a9e73c789c6a9f98547585a7048927af30) | DjDeveloperr | drm/asahi: give back warm G17 compute pools after ten idle seconds | Active — POWER; `drivers/gpu/drm/asahi/g17/runtime.rs` | I,C,R,P | +| [43a409830c67](https://github.com/aurora-silicon/linux/commit/43a409830c673f190213d2643d50f7500bbf69ab), [882abd400823](https://github.com/aurora-silicon/linux/commit/882abd400823eedce71e3def3c17a3143cd7318b), [57e6637d9f08](https://github.com/aurora-silicon/linux/commit/57e6637d9f087775ee87dcab22dd6c4bb0d4f685), [2d7de7690cc0](https://github.com/aurora-silicon/linux/commit/2d7de7690cc0f9c8d3a74b08c33999593b720aac) | DjDeveloperr | drm/asahi: keep compaction away from GEM pages held for the GPU | Active — MEM; `drivers/gpu/drm/asahi/gem.rs` | M,D | +| [dd109eec40ac](https://github.com/aurora-silicon/linux/commit/dd109eec40ace8e09c436eed1997e302e2b26842), [8bc791526797](https://github.com/aurora-silicon/linux/commit/8bc791526797872caece1891ed792e69dce48438), [76b1755a0610](https://github.com/aurora-silicon/linux/commit/76b1755a06109a66416af0282ec8310dc8a3292b), [6dd0f73fd3b2](https://github.com/aurora-silicon/linux/commit/6dd0f73fd3b2bade5415b31346aadeb2f38d446e) | DjDeveloperr | drm/asahi: complete page-table stores before GPU TLB invalidation | Active — MEM; `drivers/gpu/drm/asahi/g17/kick.rs` | M,D | +| [bec1201676a6](https://github.com/aurora-silicon/linux/commit/bec1201676a6f3b073e8d93791d8c9c7e6c99d7f), [36b80077e7c4](https://github.com/aurora-silicon/linux/commit/36b80077e7c45c57469f9d8cd6ba3e0aa6007cd6), [91e15ed8e7a6](https://github.com/aurora-silicon/linux/commit/91e15ed8e7a681ca293f94e337f59563cd03879d), [019ad857ddfa](https://github.com/aurora-silicon/linux/commit/019ad857ddfa08ed2473eae34db5b8b8d98b068e) | DjDeveloperr | drm/asahi: remove tiler-shrink diagnostic logging | Active — OBS; `drivers/gpu/drm/asahi/g17/buffer.rs` | O,F | +| [692752fb7bf6](https://github.com/aurora-silicon/linux/commit/692752fb7bf60c9e5f83e80dba56779fa48961c1), [3f6a8c31626f](https://github.com/aurora-silicon/linux/commit/3f6a8c31626fc077dbfcea147cd6e30cb28c5e70), [27178c4e373a](https://github.com/aurora-silicon/linux/commit/27178c4e373a23ba1015647f774a3102265c1b27), [d128b5e48465](https://github.com/aurora-silicon/linux/commit/d128b5e484658066c82504df550f6f13abcbd1cc) | DjDeveloperr | drm/asahi: reuse the render state mapping for auxiliary framebuffers | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/render/memory.rs` | R,H | +| [a2e5af9f5f03](https://github.com/aurora-silicon/linux/commit/a2e5af9f5f0334dbcdd069cc2944f11313f5d67c), [54101717e1de](https://github.com/aurora-silicon/linux/commit/54101717e1dee1b6fd64f32ae8888ec8429b821e), [c0e47197c71c](https://github.com/aurora-silicon/linux/commit/c0e47197c71c80b57974130a06386e101bbdc7ee) | Ryan Murray | drm/asahi: allocate imported and dumb GEM objects as asahi objects | Active — MEM; `drivers/gpu/drm/asahi/gem.rs` | M,D | +| [c29c639e0934](https://github.com/aurora-silicon/linux/commit/c29c639e09349ce24a2bf768c33476e2e27b2512), [c3a21491589b](https://github.com/aurora-silicon/linux/commit/c3a21491589bb62b2279867805b8e87fd2629e4d), [f2760d2f42ec](https://github.com/aurora-silicon/linux/commit/f2760d2f42ec4ede63abb18918ef4abeb4b3de35) | Dj | drm/asahi: preserve TA prefix dependencies across render pairs | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/render.rs` | R,H | +| [7598420667b6](https://github.com/aurora-silicon/linux/commit/7598420667b6be8788d768316e4218ff3c3fc1ff), [d2f283ac0589](https://github.com/aurora-silicon/linux/commit/d2f283ac058952ea84da562a7f6e6a911ebf879a), [1af8de012d22](https://github.com/aurora-silicon/linux/commit/1af8de012d2221144395f900729619b5065b14fd) | Dj | drm/asahi: provision G17P ray traversal pools from Gen1 SCS demand | Gated — RT; `drivers/gpu/drm/asahi/g17/command.rs` | F | +| [e864801fe49c](https://github.com/aurora-silicon/linux/commit/e864801fe49c9dc6d1fd6e4d1b6c2a607e672f71), [a075a1fab446](https://github.com/aurora-silicon/linux/commit/a075a1fab446a49270d387274f7793d3e9ee6560), [6ffb2dc7a658](https://github.com/aurora-silicon/linux/commit/6ffb2dc7a658e2e6bcaf48a75461b8d7e84549bf) | DjDeveloperr | drm/asahi: recycle settled quarantined G17 compute queues [#305](https://github.com/aurora-silicon/linux/pull/305) | Active — REC; `drivers/gpu/drm/asahi/g17/queue/compute.rs` | F,Q | +| [ae76590d6ea0](https://github.com/aurora-silicon/linux/commit/ae76590d6ea0313a4741fdce5025f6c336eaf90c), [b9fd334a9c96](https://github.com/aurora-silicon/linux/commit/b9fd334a9c96f58640e0ef6ce2010749b85e7c4f), [8ad79fc25869](https://github.com/aurora-silicon/linux/commit/8ad79fc25869a9a5cd36262cc8a702b4327cf437) | DjDeveloperr | drm/asahi: g17: replay render groups firmware retired without executing [#307](https://github.com/aurora-silicon/linux/pull/307) | Active — REC; `drivers/gpu/drm/asahi/g17/job.rs` | F,Q | +| [41786756b43d](https://github.com/aurora-silicon/linux/commit/41786756b43da732fa85c6ac3e23a33e3baa9335), [0a2cdd456013](https://github.com/aurora-silicon/linux/commit/0a2cdd456013347efd1bc2a73d79e291d2d77fbc), [167852510065](https://github.com/aurora-silicon/linux/commit/1678525100652694b0aaee977ea2012b487d6a51) | DjDeveloperr | drm/asahi: g17: never kill compute commands with ray traversal scratch [#308](https://github.com/aurora-silicon/linux/pull/308) | Gated — RT; `drivers/gpu/drm/asahi/g17/queue/compute/exit.rs` | F | +| [d3f4f5dd1b59](https://github.com/aurora-silicon/linux/commit/d3f4f5dd1b592ba925f469133ebac876a101ddb7), [ec3aa2ff51b8](https://github.com/aurora-silicon/linux/commit/ec3aa2ff51b8ad6f9f39c70bf563866ee81baecb), [48b2631fdaab](https://github.com/aurora-silicon/linux/commit/48b2631fdaab66dc7bd850b3006c0f85a73aba0e) | DjDeveloperr | drm/asahi: g17: ray queries in render stages [#309](https://github.com/aurora-silicon/linux/pull/309) | Gated — RT; `drivers/gpu/drm/asahi/g17/command.rs` | F | +| [079f81d5a637](https://github.com/aurora-silicon/linux/commit/079f81d5a637e459a33e8f75a97b91f2454f1499), [2d1fcbe1a980](https://github.com/aurora-silicon/linux/commit/2d1fcbe1a9808f52f2427e4c1237b2b0eba7b7fb), [03426fb46c9c](https://github.com/aurora-silicon/linux/commit/03426fb46c9ce29e5b58db52cfefd09fac156fea) | Ryan Murray | soc: apple: mailbox, rtkit: Allow holding received messages | Active — MBOX; `drivers/soc/apple/mailbox.c` | B,O | +| [a98bb5de4b2e](https://github.com/aurora-silicon/linux/commit/a98bb5de4b2e6dd5d54150008bd322f97c8dbd0d), [c623f821fdfa](https://github.com/aurora-silicon/linux/commit/c623f821fdfaada5df34d2a79fcc4586b0d7323b), [23b2b6f4a1c9](https://github.com/aurora-silicon/linux/commit/23b2b6f4a1c93dbc90768e271c92bc914e5d676f) | Ryan Murray | soc: apple: pmp: log tunables the boot loader does not provide at debug level [#317](https://github.com/aurora-silicon/linux/pull/317) | Active — PMP; `drivers/soc/apple/pmp.rs` | P,B | +| [689592f3219f](https://github.com/aurora-silicon/linux/commit/689592f3219f4733c038cdf72e5a911084d22521), [bc1d13129d6d](https://github.com/aurora-silicon/linux/commit/bc1d13129d6d88ee93515eb547967d87170e5da3), [f80758084279](https://github.com/aurora-silicon/linux/commit/f80758084279064eb1c60e6bfa56f7649bb86a4d) | Ryan Murray | iommu: apple-dart: Demote informational probe messages to debug [#314](https://github.com/aurora-silicon/linux/pull/314) | Active — DART; `drivers/iommu/apple-dart.c` | D,B | +| [e28469b8a38e](https://github.com/aurora-silicon/linux/commit/e28469b8a38e4102fd5c5b877bf51439fc00fca3), [00baae48e464](https://github.com/aurora-silicon/linux/commit/00baae48e464c44cb754d52552d3bd1f431d52f7) | Ryan Murray | iommu: apple-dart: Invalidate every stream in a domain bitmap | Active — DART; `drivers/iommu/Kconfig` | D,B | +| [aade7be0a12a](https://github.com/aurora-silicon/linux/commit/aade7be0a12a38ae8bb276fac80df27eaea97e32), [b13ede3059b0](https://github.com/aurora-silicon/linux/commit/b13ede3059b0bc51bca6033e405606e525ec2259), [4cf2113dc363](https://github.com/aurora-silicon/linux/commit/4cf2113dc36329544edc54b09dc775c5888c3975) | Ryan Murray | soc: apple: mcc: factor out disabling and flushing planes [#317](https://github.com/aurora-silicon/linux/pull/317) | Active — MCC; `drivers/soc/apple/mcc.c` | M,P | +| [ef010a009b3f](https://github.com/aurora-silicon/linux/commit/ef010a009b3ffb15af9794ed37083d1ad2c4facb), [0cba4dd60674](https://github.com/aurora-silicon/linux/commit/0cba4dd60674aa9ded2c27eaae1ff7bf4f1e243e), [8bf158a74ea7](https://github.com/aurora-silicon/linux/commit/8bf158a74ea7e2a5a7e42082fc882d3508119a09) | Ryan Murray | soc: apple: mcc: disable the planes already enabled when enabling fails [#317](https://github.com/aurora-silicon/linux/pull/317) | Active — MCC; `drivers/soc/apple/mcc.c` | M,P | +| [a099186592d6](https://github.com/aurora-silicon/linux/commit/a099186592d6aa010ab7da6d71c7c88d1801345d), [e46e30ff7457](https://github.com/aurora-silicon/linux/commit/e46e30ff7457d01b6e9cb97e8d7a5b0fd8fe20e6), [4653ef5a873f](https://github.com/aurora-silicon/linux/commit/4653ef5a873f16c68a1e9e9080a41395628ca9d4) | Ryan Murray | pmdomain: apple: pmc: undo a partial agent enable [#317](https://github.com/aurora-silicon/linux/pull/317) | Active — PMC; `drivers/pmdomain/apple/pmc.c` | P,C,R | +| [49bb592b9e9f](https://github.com/aurora-silicon/linux/commit/49bb592b9e9f626d06af3296172be8d3aa99a320), [43f4894e7c18](https://github.com/aurora-silicon/linux/commit/43f4894e7c18a46f5e379dd994f0fbd5def67ed7), [a86e8e5637c0](https://github.com/aurora-silicon/linux/commit/a86e8e5637c07504ca09b7bfa5639492af065d8c) | Ryan Murray | pmdomain: apple: pmc: restore the AP's own DCS vote when a limit is lifted [#317](https://github.com/aurora-silicon/linux/pull/317) | Gated — DCS; `drivers/pmdomain/apple/Kconfig` | P | +| [75984fc70c8f](https://github.com/aurora-silicon/linux/commit/75984fc70c8f252d96910dad4cd8635f4977b74f), [6351fe2e5e29](https://github.com/aurora-silicon/linux/commit/6351fe2e5e29f4447d997214d785f10885ef7538), [dba445e67326](https://github.com/aurora-silicon/linux/commit/dba445e6732685313e036e73dc2753c7ed8cbcb6) | Ryan Murray | soc: apple: clpc: remove the debugfs override of the floor [#317](https://github.com/aurora-silicon/linux/pull/317) | Active — CLPC; `drivers/soc/apple/clpc.c` | P,C,R,I | +| [9e4e936ed101](https://github.com/aurora-silicon/linux/commit/9e4e936ed10105188f37f3f8b2c27042e2e2ec58), [c47472a2343b](https://github.com/aurora-silicon/linux/commit/c47472a2343b6c35235e48db178cf76ae94c1f73), [9dfd51203aa8](https://github.com/aurora-silicon/linux/commit/9dfd51203aa80ce8867d98ba03c79fbb283039c2) | Ryan Murray | soc: apple: clpc: make the demand policy per-SoC data, drop the GPU input [#317](https://github.com/aurora-silicon/linux/pull/317) | Active — CLPC; `drivers/soc/apple/clpc.c` | P,C,R,I | +| [64290b216821](https://github.com/aurora-silicon/linux/commit/64290b21682151a3eb4c93d5f666d07f56aae87c), [d19ce09e3508](https://github.com/aurora-silicon/linux/commit/d19ce09e35089ec81bcc82f55198498901df10ac), [10e182579152](https://github.com/aurora-silicon/linux/commit/10e1825791523747c5f0d4ac44570fe4418ff586) | Ryan Murray | pmdomain: apple: pmgr-pwrstate: log always-on domains left off at info level [#317](https://github.com/aurora-silicon/linux/pull/317) | Active — PMGR; `drivers/pmdomain/apple/pmgr-pwrstate.c` | B,F | +| [57dafd4b412f](https://github.com/aurora-silicon/linux/commit/57dafd4b412f8b49be5362a98503be11138cd388), [f86db4089cee](https://github.com/aurora-silicon/linux/commit/f86db4089cee44d2f54de73f60aa32c8a07ffd3a), [1ef9c2113ecd](https://github.com/aurora-silicon/linux/commit/1ef9c2113ecdf2851908e4bd9793876d92152d27) | Ryan Murray | soc: apple: rtkit: Do not warn when buffers are retained on purpose [#314](https://github.com/aurora-silicon/linux/pull/314) | Active — RTKIT; `drivers/soc/apple/rtkit.c` | B,F | +| [5ebe92d17a32](https://github.com/aurora-silicon/linux/commit/5ebe92d17a32112b13e74553bab5b0e0b7c0e147), [7b29e7caccc4](https://github.com/aurora-silicon/linux/commit/7b29e7caccc4896949b0dff4dd8caf7c5fa3f0c0), [9779b2457e8f](https://github.com/aurora-silicon/linux/commit/9779b2457e8f13b0f257964570c9883f079e65f6) | Ryan Murray | pmdomain: apple: pmc: keep the DCS limit state when the rail enable fails [#345](https://github.com/aurora-silicon/linux/pull/345) | Gated — DCS; `drivers/pmdomain/apple/pmc.c` | P | +| [962dacdbb6ad](https://github.com/aurora-silicon/linux/commit/962dacdbb6ad38d9fc723d9f8ea3b3d8dee0e6a9), [4209657d7d94](https://github.com/aurora-silicon/linux/commit/4209657d7d948671c9872e1cbb53f66774793607), [1c797d3eb5cb](https://github.com/aurora-silicon/linux/commit/1c797d3eb5cb79f7e80696b4d7c93800172771c0) | Ryan Murray | cpufreq: apple-soc: say why a cluster with verified transitions fails init [#345](https://github.com/aurora-silicon/linux/pull/345) | Active — CPU; `drivers/cpufreq/apple-soc-cpufreq.c` | C,I,P | +| [854b2e6ec82e](https://github.com/aurora-silicon/linux/commit/854b2e6ec82ea030f3dcfd8ff024393019dd3818), [5a02a0cd2865](https://github.com/aurora-silicon/linux/commit/5a02a0cd28651e4eb3f2eede40af991d8d265a51), [13857139ed46](https://github.com/aurora-silicon/linux/commit/13857139ed4643ef532281808bc8035ad64c9a10) | Ryan Murray | soc: apple: mcc: print the boot DMA device before dropping its node [#345](https://github.com/aurora-silicon/linux/pull/345) | Active — MCC; `drivers/soc/apple/mcc.c` | M,P | +| [e157c990ad98](https://github.com/aurora-silicon/linux/commit/e157c990ad98e3008b552e45d0c85448f95e4266), [ec4fc264985c](https://github.com/aurora-silicon/linux/commit/ec4fc264985c5505be71434f96cc949d0037801f), [0b65493ccd0f](https://github.com/aurora-silicon/linux/commit/0b65493ccd0f4a779e592258032cb40ce03d1a1c) | Ryan Murray | pmdomain: apple: pmgr-pwrstate: tidy the rate-limited error messages [#345](https://github.com/aurora-silicon/linux/pull/345) | Active — PMGR; `drivers/pmdomain/apple/pmgr-pwrstate.c` | B,F | +| [3a9f56606ec3](https://github.com/aurora-silicon/linux/commit/3a9f56606ec3001c7cd7df137cf49a6ae2db0157), [3762bb8f345d](https://github.com/aurora-silicon/linux/commit/3762bb8f345de262f1efca74de864457a3adc9aa), [a516c3f7c3f8](https://github.com/aurora-silicon/linux/commit/a516c3f7c3f85aeb05efe5e3aff5b7890c7d022c) | Ryan Murray | iommu: apple-dart: Invalidate every DART even if a TLB sync step fails [#345](https://github.com/aurora-silicon/linux/pull/345) | Active — DART; `drivers/iommu/apple-dart-test.c` | D,B | +| [8a2875a6789d](https://github.com/aurora-silicon/linux/commit/8a2875a6789d2a4f1452beafd50e4de134b30683), [0f997e1c9354](https://github.com/aurora-silicon/linux/commit/0f997e1c9354b9aa18c7ae13cdf61328b1f50d9d), [df5213734e31](https://github.com/aurora-silicon/linux/commit/df5213734e31c372fc36a2375539e4d69b5f815a) | Ryan Murray | soc: apple: rtkit: Only parse an initial crash log in inherited sessions [#345](https://github.com/aurora-silicon/linux/pull/345) | Active — RTKIT; `drivers/soc/apple/rtkit.c` | B,F | +| [d6caa25b65d2](https://github.com/aurora-silicon/linux/commit/d6caa25b65d2ca7fc7315ee568e0cbd250a9593c), [f501cf5f491d](https://github.com/aurora-silicon/linux/commit/f501cf5f491da7527d71b86cd164e094aac050fd), [82494bf5c6b0](https://github.com/aurora-silicon/linux/commit/82494bf5c6b07739782eeac837b9f6a96c70ca36) | Ryan Murray | iommu/apple-dart: Do not reset a DART that kept its state across sleep [#336](https://github.com/aurora-silicon/linux/pull/336) | Active — DART; `drivers/iommu/apple-dart-test.c` | D,B | +| [91d9f93cf842](https://github.com/aurora-silicon/linux/commit/91d9f93cf842a892913e7ce56218ddc27292a6c1), [dc02d89a1465](https://github.com/aurora-silicon/linux/commit/dc02d89a1465719e6ab3b3a5e31dbea08e1a8f68), [39bc76211b30](https://github.com/aurora-silicon/linux/commit/39bc76211b30ca23b29eb740ffdf0ae8cf2ed5d3) | Ryan Murray | iommu/apple-dart: Keep a retained DART in bypass from being reset [#336](https://github.com/aurora-silicon/linux/pull/336) | Active — DART; `drivers/iommu/apple-dart-test.c` | D,B | +| [b6b28c111cff](https://github.com/aurora-silicon/linux/commit/b6b28c111cff20b5312fee34ca544ce6f7fd00a5), [469fd41db0a3](https://github.com/aurora-silicon/linux/commit/469fd41db0a33df014d0523648c5e1f052895c9a), [d9fc21447419](https://github.com/aurora-silicon/linux/commit/d9fc214474191909db140a5ba565a014819bd6de) | Ryan Murray | iommu/apple-dart: Restore the translations when the reset on resume fails [#336](https://github.com/aurora-silicon/linux/pull/336) | Active — DART; `drivers/iommu/apple-dart-test.c` | D,B | +| [4bfe19d0ad80](https://github.com/aurora-silicon/linux/commit/4bfe19d0ad8042e95b784fe2e439e15c8f869ac5), [cd1ff978d660](https://github.com/aurora-silicon/linux/commit/cd1ff978d6600a354b93fd829d9c9588715c44f1), [43c7fc8f82a6](https://github.com/aurora-silicon/linux/commit/43c7fc8f82a65f2e3807f41f03421df2b27ed3de) | Ryan Murray | irqchip/apple-aic: Allow interrupts to wake the system | Active — AIC; `drivers/irqchip/irq-apple-aic.c` | O,C,I | +| [345f6329c88b](https://github.com/aurora-silicon/linux/commit/345f6329c88b1ea97ecf1c373bc573a9c20947e7), [eee098196369](https://github.com/aurora-silicon/linux/commit/eee098196369f3a2aebe4f99333c53d538caa1c5) | Ryan Murray | drm/asahi: queue background work on system_dfl_wq | Active — SHARED; `drivers/gpu/drm/asahi/g17/mod.rs` | C,R,H | +| [e04141b6136c](https://github.com/aurora-silicon/linux/commit/e04141b6136c63f61679eda781cabe349297d58e), [a741120e3ca0](https://github.com/aurora-silicon/linux/commit/a741120e3ca0d349dffab6cc32086935ecb98d81) | Chris Kearney | soc: apple: validate PMP boot arguments before patching | Active — PMP; `drivers/soc/apple/pmp.rs` | P,B | +| [8a74c2db9581](https://github.com/aurora-silicon/linux/commit/8a74c2db9581fd106dc90be11fb1a1e075bf07fa), [d67dbeedca63](https://github.com/aurora-silicon/linux/commit/d67dbeedca63d1389fb5754d419fffae0c678155) | Chris Kearney | soc: apple: bound PMP arguments by the mapped SRAM size | Active — PMP; `drivers/soc/apple/pmp.rs` | P,B | +| [6c53c0ff9715](https://github.com/aurora-silicon/linux/commit/6c53c0ff9715f055de8af928ed57277f1f0f30a2), [687f1c4f2190](https://github.com/aurora-silicon/linux/commit/687f1c4f21906c698f38c29c467f4aa74425509e) | Eryk Wieliczko | soc: apple: acknowledge serialized PMP device power commands | Active — PMP; `drivers/pmdomain/apple/pmp-report.c` | P,B | +| [5a68774e87a8](https://github.com/aurora-silicon/linux/commit/5a68774e87a8baa711c211757b709966b2193be5), [fefec69b9fca](https://github.com/aurora-silicon/linux/commit/fefec69b9fca69f98a026a4dbdf9bde89b64544b) | Chris Kearney | soc: apple: retry unsent RTKit endpoint starts once | Active — RTKIT; `drivers/soc/apple/rtkit.c` | B,F | +| [dc7d0b49a07d](https://github.com/aurora-silicon/linux/commit/dc7d0b49a07df88aab2ee9cbb3fae17db8694f2b), [5bd0a91f565c](https://github.com/aurora-silicon/linux/commit/5bd0a91f565cf815b9f999b3e704edba2563aac8) | Marius Gehrmann | soc: apple: rtkit: Honour apple,sync-syslog-ack | Active — RTKIT; `drivers/soc/apple/rtkit-internal.h` | B,F | +| [d67d380d1519](https://github.com/aurora-silicon/linux/commit/d67d380d1519babe4b9d080830e31bca56da4d8f), [9d0290148561](https://github.com/aurora-silicon/linux/commit/9d029014856181eb6190324b8300d82590ab2e8b) | Chris Kearney | soc: apple: rtkit: cap the kdebug buffer, and take a late completion | Active — RTKIT; `drivers/soc/apple/rtkit.c` | B,F | +| [1369d18b4ec7](https://github.com/aurora-silicon/linux/commit/1369d18b4ec7bf0a631bc68fbdf7d894f2a4c542), [0d9ac5799dd4](https://github.com/aurora-silicon/linux/commit/0d9ac5799dd4dffe26513b100e93930bb3833a53) | Eryk Wieliczko | soc: apple: acknowledge OSLog progress in negotiated and inherited sessions | Active — RTKIT; `drivers/soc/apple/rtkit-internal.h` | B,F | +| [e47d06ceb56f](https://github.com/aurora-silicon/linux/commit/e47d06ceb56fd73444f3e17954f961dce5edfdcf), [47e137e98667](https://github.com/aurora-silicon/linux/commit/47e137e986675ca44843155e66b731c167932749) | Chris Kearney | iommu/apple-dart: reserve retained firmware tables and root slots | Active — DART; `drivers/iommu/apple-dart.c` | D,B | +| [e663049674c8](https://github.com/aurora-silicon/linux/commit/e663049674c81f6818a88b594f029d0bf9f45ccc), [058d2b7355cf](https://github.com/aurora-silicon/linux/commit/058d2b7355cffe72dad9053486a26ae2822d51a2) | DjDeveloperr | soc: apple: finish the merge of DART, QSPI and SMC hwmon | Active — DART; `drivers/iommu/apple-dart.c` | D,B | +| [d97a27e94fd8](https://github.com/aurora-silicon/linux/commit/d97a27e94fd8967ce1c3846dcc71628a8b39db36), [6dbfbcb21eb0](https://github.com/aurora-silicon/linux/commit/6dbfbcb21eb09d07ae59037a1ceca8cc3500841f) | Eryk Wieliczko | drm/asahi: validate GEM size before page rounding | Active — MEM; `drivers/gpu/drm/asahi/gem.rs` | M,D | +| [6d457ae0a70c](https://github.com/aurora-silicon/linux/commit/6d457ae0a70c583dc760d05ac552829d7370060f), [b038d2767d6e](https://github.com/aurora-silicon/linux/commit/b038d2767d6e08c0dae43c87da89dfd77d719f7c) | Chris Kearney | arm64: dts: apple: t8140: drop the t6000 fallback from the GFX report | Active translated data — FW | B,P | +| [496040e469aa](https://github.com/aurora-silicon/linux/commit/496040e469aa1ea3457c89cbd677d9f964614cca), [17bf156987c5](https://github.com/aurora-silicon/linux/commit/17bf156987c5f7be583bd5efc86430b1055efc36) | Chris Kearney | pmdomain: apple: pmp-report: match T8140 report entries by compatible | Active — PMP; `drivers/pmdomain/apple/pmp-report.c` | P,B | +| [e033fe71b24d](https://github.com/aurora-silicon/linux/commit/e033fe71b24da2b2aabc90b9c5faa5bd0f497d9f), [5f7925d83ca6](https://github.com/aurora-silicon/linux/commit/5f7925d83ca680ba7760dbd16b9e0cb61029161a) | Chris Kearney | drm/asahi: keep the range TLBI base address in its field | Gated — UAT; `drivers/gpu/drm/asahi/mem.rs` | D | +| [6582e3d5ef6f](https://github.com/aurora-silicon/linux/commit/6582e3d5ef6f47ff7e0a11d3fffa47f15ef22a44), [f7132eb38e81](https://github.com/aurora-silicon/linux/commit/f7132eb38e81e5af14711b3da87aa6cf96b3ba98) | Ryan Murray | iommu: apple-dart: Factor out the DT DMA window parse | Active — DART; `drivers/iommu/apple-dart.c` | D,B | +| [fc7981e78871](https://github.com/aurora-silicon/linux/commit/fc7981e78871a2ec91b38835bc84e03611b4e860), [ff11ec1083af](https://github.com/aurora-silicon/linux/commit/ff11ec1083af04c76c1d91fcebc78333681cf519) | Chris Kearney | pmdomain: apple: validate PMP startup tables before seeding | Active — PMP; `drivers/pmdomain/apple/.kunitconfig.apple-pmp` | P,B | +| [4c673adf2c11](https://github.com/aurora-silicon/linux/commit/4c673adf2c110c8264da8b0130eaf181fd6bd560), [782906a65540](https://github.com/aurora-silicon/linux/commit/782906a65540c569f6afa8b49b8c6ac22c9e8dc5) | Ryan Murray | pmdomain: apple: pmp-report: select OF_DYNAMIC | Active — PMP; `drivers/pmdomain/apple/Kconfig` | P,B | +| [f78d7690bfd2](https://github.com/aurora-silicon/linux/commit/f78d7690bfd2d927462c7af34242d6476a0cf59c), [a41d634596e5](https://github.com/aurora-silicon/linux/commit/a41d634596e536031e719b5ea8a3082220e8ec61) | Ryan Murray | drm/asahi: mmu: preallocate enough GPUVAs to unmap across two mappings | Active — MEM; `drivers/gpu/drm/asahi/mmu.rs` | M,D | +| [10fc7af5e0b8](https://github.com/aurora-silicon/linux/commit/10fc7af5e0b897112afaa5fc9ab791f241244969), [755ac9ed1425](https://github.com/aurora-silicon/linux/commit/755ac9ed142576a6e27da54a39b68290e861b34b) | DjDeveloperr | drm/asahi: g17: move the G17P UAPI past the M3 and J613 numbers | Active translated data — FW; `drivers/gpu/drm/asahi/g17/command.rs` | B,C,R | +| [6d5b51d6e818](https://github.com/aurora-silicon/linux/commit/6d5b51d6e818fc0b8915b43a030854d6d96ff330), [62aae2f0f8cb](https://github.com/aurora-silicon/linux/commit/62aae2f0f8cb52a40b2cf4ea21212ad2badd267c) | Ryan Murray | drm/asahi: uapi: use integration/aurora's wording | Active translated data — FW | B,C,R | +| [ff25cb1691ff](https://github.com/aurora-silicon/linux/commit/ff25cb1691ffa6e285dbcb014992e3838f4ad6fe) | Ryan Murray | drm/asahi: Adapt the lifetime and module-ownership changes to Linux 7.3 [#375](https://github.com/aurora-silicon/linux/pull/375) | Active — DRM; `drivers/gpu/drm/asahi/g17/job.rs` | C,H,M,F | +| [2348e8c3acd2](https://github.com/aurora-silicon/linux/commit/2348e8c3acd2920ca583d801645e8387fd80c7f2) | Ryan Murray | drm/asahi: regs: Hold the G17 SGX mapping as a DevresIoMem [#375](https://github.com/aurora-silicon/linux/pull/375) | Active — MEM; `drivers/gpu/drm/asahi/regs.rs` | M,D | +| [d4128b7c28ce](https://github.com/aurora-silicon/linux/commit/d4128b7c28ce177d227ff5488145e8eedd970610) | Ryan Murray | drm/asahi: make SG backing leases revocable [#375](https://github.com/aurora-silicon/linux/pull/375) | Active — DRM; `drivers/gpu/drm/asahi/gem.rs` | C,H,M,F | +| [464cb2e30ae3](https://github.com/aurora-silicon/linux/commit/464cb2e30ae395d13723d161f2aef41066dbb79d) | Ryan Murray | drm/asahi: attribute GPU memory to its creator and limit it [#375](https://github.com/aurora-silicon/linux/pull/375) | Active — MEM; `drivers/gpu/drm/asahi/asahi_core.rs` | M,D | +| [47f408da5996](https://github.com/aurora-silicon/linux/commit/47f408da5996da161de66282da83550bf436450e) | Ryan Murray | drm/asahi: clean up Clippy diagnostics in Aurora additions [#375](https://github.com/aurora-silicon/linux/pull/375) | Active — OBS; `drivers/gpu/drm/asahi/g17/command.rs` | O,F | +| [e44a6b59f5d2](https://github.com/aurora-silicon/linux/commit/e44a6b59f5d248ee1a026b9dbb6dbd244e35a1d7) | Ryan Murray | soc: apple: pmp: use lossless command and bootargs conversions | Active — PMP; `drivers/soc/apple/pmp.rs` | P,B | +| [761626d47af3](https://github.com/aurora-silicon/linux/commit/761626d47af3eb6da3ff8c998373646349aa7169) | Ryan Murray | drm/apple: quiet routine H17P external run-mode syslog records | Active — DISPLAY; `drivers/gpu/drm/apple/Makefile` | B | +| [ac38eb0f57a1](https://github.com/aurora-silicon/linux/commit/ac38eb0f57a17f59eb2ce8158df1fb81aeff2955) | Ryan Murray | drm/asahi: g17: wait for a compute command's stamp before releasing it [#375](https://github.com/aurora-silicon/linux/pull/375) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/compute.rs` | C,H | +| [1e3b9caebc7b](https://github.com/aurora-silicon/linux/commit/1e3b9caebc7b1924acc3643222ff95b85e62bdb7) | Ryan Murray | drm/asahi: memacct: Read the module parameters by value on Linux 7.3 [#375](https://github.com/aurora-silicon/linux/pull/375) | Active — MEM; `drivers/gpu/drm/asahi/memacct.rs` | M,D | +| [db2943a27f88](https://github.com/aurora-silicon/linux/commit/db2943a27f88dcd011ae97498cbf2250a4245463) | Jonathan Goyvaerts | soc: apple: rtkit: Log unknown oslog messages at debug level | Active — RTKIT; `drivers/soc/apple/rtkit.c` | B,F | +| [1797c344df7f](https://github.com/aurora-silicon/linux/commit/1797c344df7f7631b2f9be7c9be1a55d8852b164) | Jonathan Goyvaerts | pmdomain: apple: pmp-report: Add T6040 support [#374](https://github.com/aurora-silicon/linux/pull/374) | Active — PMP; `drivers/pmdomain/apple/pmp-report.c` | P,B | +| [927c31856676](https://github.com/aurora-silicon/linux/commit/927c318566760e7b4845d36904022c38271b925d) | Jonathan Goyvaerts | soc: apple: pmp: Add T6040 support [#374](https://github.com/aurora-silicon/linux/pull/374) | Active — PMP; `drivers/soc/apple/pmp.rs` | P,B | +| [7fcc7fd38c43](https://github.com/aurora-silicon/linux/commit/7fcc7fd38c43b6aa64ea2270dcd32cdd57d19f37) | Ryan Murray | drm/asahi: carry G17 admission prerequisites to 7.3 [#377](https://github.com/aurora-silicon/linux/pull/377) | Active — MEM; `drivers/gpu/drm/asahi/g17/freelist.rs` | M,D | +| [c9f72dc51116](https://github.com/aurora-silicon/linux/commit/c9f72dc51116c12b3aa840185eee5a45f4b298b5) | Ryan Murray | drm/asahi: carry G17 pool-release recovery and stamp-wait fixup to 7.3 [#377](https://github.com/aurora-silicon/linux/pull/377) | Active — REC; `drivers/gpu/drm/asahi/g17/queue/compute.rs` | F,Q | +| [10e9da2303fc](https://github.com/aurora-silicon/linux/commit/10e9da2303fc79dc205989075855350020bf115b) | Ryan Murray | drm/asahi: hand abandoned compute growth back to witness settlement [#377](https://github.com/aurora-silicon/linux/pull/377) | Active — SHARED; `drivers/gpu/drm/asahi/g17/freelist.rs` | C,H | +| [e933ad436de0](https://github.com/aurora-silicon/linux/commit/e933ad436de06981d95e9de6ce5626f4ba99e6ff) | Ryan Murray | drm/asahi: borrow the compute pool mutably for header replacement [#377](https://github.com/aurora-silicon/linux/pull/377) | Active — GMEM; `drivers/gpu/drm/asahi/g17/freelist.rs` | M,C,R | +| [04358c1ebe86](https://github.com/aurora-silicon/linux/commit/04358c1ebe86cfbfdc926cfb41ff3e3549248279) | Ryan Murray | drm/asahi: hold failed growth reservations until aliases have gone [#377](https://github.com/aurora-silicon/linux/pull/377) | Active — SHARED; `drivers/gpu/drm/asahi/g17/runtime.rs` | C,R,H | +| [d765e50ada90](https://github.com/aurora-silicon/linux/commit/d765e50ada90e7f00a4ad608c9ff027d0a07a82a) | Ryan Murray | drm/asahi: drop tentative growth aliases before returning a fresh QID [#377](https://github.com/aurora-silicon/linux/pull/377) | Active — SHARED; `drivers/gpu/drm/asahi/g17/runtime.rs` | C,R,H | +| [780956abdb5f](https://github.com/aurora-silicon/linux/commit/780956abdb5fe59c76ca7343bc7f19770df658dd) | Ryan Murray | drm/asahi: compose shared capacity admission with accepted recovery port [#377](https://github.com/aurora-silicon/linux/pull/377) | Active — MEM; `drivers/gpu/drm/asahi/g17/buffer.rs` | M,D | +| [b52bd36805e0](https://github.com/aurora-silicon/linux/commit/b52bd36805e0c35bac5c8255aae8cfad7cce5dd7) | Ryan Murray | drm/asahi: give native ray-query jobs a bounded execution allowance [#377](https://github.com/aurora-silicon/linux/pull/377) | Gated — RT; `drivers/gpu/drm/asahi/g17/job.rs` | F | +| [a5bc25b2ce19](https://github.com/aurora-silicon/linux/commit/a5bc25b2ce1997452676894fc8c9b401c7396e6c) | Ryan Murray | drm/asahi: cover render helpers and AS builds with bounded execution [#377](https://github.com/aurora-silicon/linux/pull/377) | Gated — RT; `drivers/gpu/drm/asahi/g17/command.rs` | F | +| [9827b6bb277a](https://github.com/aurora-silicon/linux/commit/9827b6bb277af5912490229f6fe56d3c575dafe3) | Ryan Murray | drm/asahi: bound repeated timeout recovery and G17 mailbox stalls [#377](https://github.com/aurora-silicon/linux/pull/377) | Active — MBOX; `drivers/gpu/drm/asahi/g17/dependency.rs` | B,O | +| [c314dbb5bd42](https://github.com/aurora-silicon/linux/commit/c314dbb5bd4284697bd065db87eb038021b18125) | Ryan Murray | drm/asahi: expire background pool-release and render-replay polls [#377](https://github.com/aurora-silicon/linux/pull/377) | Active — REC; `drivers/gpu/drm/asahi/g17/queue/compute.rs` | F,Q | +| [6c2d17ff4079](https://github.com/aurora-silicon/linux/commit/6c2d17ff40796899d7190580cabaeea749c73d0a) | Ryan Murray | drm/sched: initialise free_guilty in drm_sched_init() [#381](https://github.com/aurora-silicon/linux/pull/381) | Active — DRM; `drivers/gpu/drm/scheduler/sched_main.c` | C,H,M,F | +| [0d1871c3cfde](https://github.com/aurora-silicon/linux/commit/0d1871c3cfde9cc45b8bc244dde4775dbf4e05c6) | Ryan Murray | rust: drm: sched: zero the scheduler before drm_sched_init() [#381](https://github.com/aurora-silicon/linux/pull/381) | Active — DRM; `rust/kernel/drm/sched.rs` | C,H,M,F | +| [24399672582c](https://github.com/aurora-silicon/linux/commit/24399672582c155df35cadf8cb81a3397958f9e2) | Ryan Murray | drm/asahi: retry G17 retained compute publication pressure [#382](https://github.com/aurora-silicon/linux/pull/382) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/compute.rs` | C,H | +| [5ae95851753d](https://github.com/aurora-silicon/linux/commit/5ae95851753d377e13b7e3ac9012452a7fdaccc9) | Jonathan Goyvaerts | pmdomain: apple: pmc: Add the T6040 voter interface [#385](https://github.com/aurora-silicon/linux/pull/385) | Active — PMC; `drivers/pmdomain/apple/pmc-test.c` | P,C,R | +| [7bbd2ad37554](https://github.com/aurora-silicon/linux/commit/7bbd2ad375543683c070aca6487fae75a79fc747) | Jonathan Goyvaerts | soc: apple: clpc: Add the T6040 demand table [#385](https://github.com/aurora-silicon/linux/pull/385) | Active — CLPC; `drivers/soc/apple/clpc.c` | P,C,R,I | +| [b3765bd38ecb](https://github.com/aurora-silicon/linux/commit/b3765bd38ecbfaa5368b760c9d908964abf8c7a5) | Jonathan Goyvaerts | soc: apple: mcc: Allow up to 16 cache planes [#385](https://github.com/aurora-silicon/linux/pull/385) | Active — MCC; `drivers/soc/apple/Kconfig` | M,P | +| [f236c33daca6](https://github.com/aurora-silicon/linux/commit/f236c33daca696a326bd652799bd189ac2b6421f) | Jonathan Goyvaerts | soc: apple: clpc: Request a state only after sustained load [#385](https://github.com/aurora-silicon/linux/pull/385) | Active — CLPC; `drivers/soc/apple/clpc.c` | P,C,R,I | +| [32960bbd0e9c](https://github.com/aurora-silicon/linux/commit/32960bbd0e9cf24931f18d25131eef24f74f3d31) | Jonathan Goyvaerts | iommu/apple-dart: Mirror the firmware's display mappings on T6040 [#386](https://github.com/aurora-silicon/linux/pull/386) | Active — DART; `drivers/iommu/apple-dart.c` | D,B | +| [833c6ed10ffc](https://github.com/aurora-silicon/linux/commit/833c6ed10ffc2ac2d38b7bbb0d85dcc2b8e171df) | DjDeveloperr | drm/asahi: reduce minimum G17 target record backing [#390](https://github.com/aurora-silicon/linux/pull/390) | Active — MEM; `drivers/gpu/drm/asahi/hw/t8140.rs` | M,D | +| [145361fdc90e](https://github.com/aurora-silicon/linux/commit/145361fdc90e82f4bddf6234ec46d0e023f41f86) | DjDeveloperr | drm/asahi: order G17 queue owner changes after scheduler releases [#390](https://github.com/aurora-silicon/linux/pull/390) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/compute.rs` | C,R,H | +| [1d478ee6c06b](https://github.com/aurora-silicon/linux/commit/1d478ee6c06b56b48782d08b693d9aa9e5c43435) | DjDeveloperr | drm/asahi: keep G17 render kick ring storage for the life of the device [#390](https://github.com/aurora-silicon/linux/pull/390) | Active — SHARED; `drivers/gpu/drm/asahi/g17/kick.rs` | R,H | +| [7304ddbf6d4d](https://github.com/aurora-silicon/linux/commit/7304ddbf6d4d47b7976efcd20623524c9c53b0b1) | DjDeveloperr | drm/asahi: keep G17 render capacity waits from stopping the runtime [#390](https://github.com/aurora-silicon/linux/pull/390) | Active — SHARED; `drivers/gpu/drm/asahi/g17/freelist.rs` | R,H | +| [72d57c0b9ac9](https://github.com/aurora-silicon/linux/commit/72d57c0b9ac9b8d5fcd58c59eebfa7f9c605399c) | DjDeveloperr | drm/asahi: wait for G17 contexts of closing queues at queue creation [#390](https://github.com/aurora-silicon/linux/pull/390) | Active — REC; `drivers/gpu/drm/asahi/g17/capacity.rs` | F,Q | +| [a4351b50cd8d](https://github.com/aurora-silicon/linux/commit/a4351b50cd8d6f194bb71252acf9c9e2cf636db4) | DjDeveloperr | drm/asahi: keep G17 publication waits out of the submit ioctl [#390](https://github.com/aurora-silicon/linux/pull/390) | Active — SHARED; `drivers/gpu/drm/asahi/g17/completion.rs` | C,R,H | +| [c556dacd085b](https://github.com/aurora-silicon/linux/commit/c556dacd085b148da86b47792dc34d14f02ace86) | DjDeveloperr | drm/asahi: rerun the G17 event pass for completions missed while a pair is borrowed [#390](https://github.com/aurora-silicon/linux/pull/390) | Active — SHARED; `drivers/gpu/drm/asahi/g17/runtime/render.rs` | C,R,H | +| [8744e9417249](https://github.com/aurora-silicon/linux/commit/8744e9417249175511480765dbcc99f0a09c0fb9) | DjDeveloperr | drm/asahi: reuse G17 compute timestamp pages within a context [#390](https://github.com/aurora-silicon/linux/pull/390) | Active — TIMESTAMP; `drivers/gpu/drm/asahi/g17/completion.rs` | T,C | +| [6f7faeecea0e](https://github.com/aurora-silicon/linux/commit/6f7faeecea0eccc8ee317ba163867df339d54fa5) | Jonathan Goyvaerts | drm/asahi: g17: do not advertise soft faults [#391](https://github.com/aurora-silicon/linux/pull/391) | Active — SHARED; `drivers/gpu/drm/asahi/g17/mod.rs` | C,R,H | +| [b0912f542fd0](https://github.com/aurora-silicon/linux/commit/b0912f542fd0655f10631977a8c63abc51a85ada) | Jonathan Goyvaerts | drm/asahi: g17: reread a torn borrowed render snapshot [#391](https://github.com/aurora-silicon/linux/pull/391) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/render/retirement.rs` | R,H | +| [30ea4b130473](https://github.com/aurora-silicon/linux/commit/30ea4b13047377972cb75b78a4cbe5885cf46d1d) | Jonathan Goyvaerts | drm/asahi: g17: do not fail a blamed render that attribution cleared [#391](https://github.com/aurora-silicon/linux/pull/391) | Gated — KEY; `drivers/gpu/drm/asahi/g17/recovery.rs` | F | +| [e3d8d1b2fc1f](https://github.com/aurora-silicon/linux/commit/e3d8d1b2fc1feada69ec028203f727f642a150ac) | Jonathan Goyvaerts | drm/asahi: g17: skip already-signalled successful input fences [#391](https://github.com/aurora-silicon/linux/pull/391) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/mod.rs` | C,R,H | +| [2321a1bc6ea6](https://github.com/aurora-silicon/linux/commit/2321a1bc6ea6c8a62a275685c0542a293c9b5438) | Jonathan Goyvaerts | drm/asahi: allow HIGH queue priority with CAP_SYS_NICE or DRM master [#391](https://github.com/aurora-silicon/linux/pull/391) | Active — DRM; `drivers/gpu/drm/asahi/g17/mod.rs` | B,M | +| [77a2bbe1a951](https://github.com/aurora-silicon/linux/commit/77a2bbe1a9512960e8c9ba17b466b2c9ab51fb5f) | Ryan Murray | soc: apple: rtkit: Keep warning about unknown oslog messages | Active — RTKIT; `drivers/soc/apple/rtkit.c` | B,F | +| [b2adcfc94046](https://github.com/aurora-silicon/linux/commit/b2adcfc94046a10a989c6fd61fdb1bf7131ef78a) | Ryan Murray | drm/asahi: fail large G17 backing population without direct reclaim [#397](https://github.com/aurora-silicon/linux/pull/397) | Active — DRM; `drivers/gpu/drm/asahi/file.rs` | C,H,M,F | +| [632e4676500b](https://github.com/aurora-silicon/linux/commit/632e4676500bab6fd88817b1cf767afb1bf5da52) | Ryan Murray | drm/asahi: expose memory admission capacities in GET_PARAMS [#397](https://github.com/aurora-silicon/linux/pull/397) | Active — MEM; `drivers/gpu/drm/asahi/file.rs` | M,D | +| [ffe8011a479c](https://github.com/aurora-silicon/linux/commit/ffe8011a479ccd74dde747225c9baf67e2a02ba6) | Chris Kearney | drm/asahi: g15: reject errored input dependencies before publication [#395](https://github.com/aurora-silicon/linux/pull/395) | Active — SHARED; `drivers/gpu/drm/asahi/g17/mod.rs` | C,R,H | +| [faef7fd12e22](https://github.com/aurora-silicon/linux/commit/faef7fd12e22a4d56873b91981d412fd981cfd21) | Chris Kearney | drm/asahi: expose the inherited-error policy only for native G15 [#395](https://github.com/aurora-silicon/linux/pull/395) | Active — SHARED; `drivers/gpu/drm/asahi/g17/mod.rs` | C,R,H | +| [8736001f2b13](https://github.com/aurora-silicon/linux/commit/8736001f2b130a9e477fdef7d1db45f31591bfec) | Eryk Wieliczko | pmdomain: apple: pmgr-pwrstate: Honour apple,no-auto-pm [#395](https://github.com/aurora-silicon/linux/pull/395) | Active — PMGR; `drivers/pmdomain/apple/pmgr-pwrstate.c` | B,F | +| [2aff1b95c550](https://github.com/aurora-silicon/linux/commit/2aff1b95c55039846beb6451c737154254aa3ae0) | Eryk Wieliczko | soc: apple: rtkit: describe and adopt retained system buffers | Active — RTKIT; `drivers/soc/apple/rtkit-internal.h` | B,F | +| [71500694f45b](https://github.com/aurora-silicon/linux/commit/71500694f45b1fd27b05c9ed030b2cc803fb0f2f) | Eryk Wieliczko | drm/asahi: share tracked VM lifetime and native query support [#400](https://github.com/aurora-silicon/linux/pull/400) | Active — MEM; `drivers/gpu/drm/asahi/g17/completion.rs` | M,D | +| [444c52644826](https://github.com/aurora-silicon/linux/commit/444c52644826e99eecf65e9f643744c9e380bca9) | Ryan Murray | soc: apple: pmp: Keep power commands after an unhandled message on other boards | Active — PMP; `drivers/soc/apple/pmp.rs` | P,B | +| [76c60f146885](https://github.com/aurora-silicon/linux/commit/76c60f1468853b4d28e3fed6e9c207c8a2f28410) | Ryan Murray | iommu/apple-dart: Map retained DARTs non-posted like every other DART [#400](https://github.com/aurora-silicon/linux/pull/400) | Active — DART; `drivers/iommu/apple-dart.c` | D,B | +| [d5df65ffcf66](https://github.com/aurora-silicon/linux/commit/d5df65ffcf66e54d9f23de05635512a2cf0ad41d) | Jonathan Goyvaerts | soc: apple: rtkit: add apple_rtkit_resume_idle() | Active — RTKIT; `drivers/soc/apple/rtkit.c` | B,F | +| [f82267ce1172](https://github.com/aurora-silicon/linux/commit/f82267ce117278fb915879abbbd0f355286cecf3) | Jonathan Goyvaerts | soc: apple: rtkit: resume the AP power state before the IOP's | Active — RTKIT; `drivers/soc/apple/rtkit.c` | B,F | +| [e772e7f87659](https://github.com/aurora-silicon/linux/commit/e772e7f87659243ab7b94c2b5bb77ab485dc1128) | DjDeveloperr | iommu/apple-dart: accept mirrored pages in handed-off firmware slots [#404](https://github.com/aurora-silicon/linux/pull/404) | Active — DART; `drivers/iommu/apple-dart.c` | D,B | +| [ff5c39ae1782](https://github.com/aurora-silicon/linux/commit/ff5c39ae1782cd1d826eddf48e0b08b1b2af61eb) | DjDeveloperr | iommu/apple-dart: keep stream invalidates out of handed-off slots [#404](https://github.com/aurora-silicon/linux/pull/404) | Active — DART; `drivers/iommu/apple-dart.c` | D,B | +| [c2bace5bb285](https://github.com/aurora-silicon/linux/commit/c2bace5bb28512471318d71abd5073b2bc7e2af1) | Ryan Murray | soc: apple: pmp: start endpoint 7 on every T6041 PMP [#402](https://github.com/aurora-silicon/linux/pull/402) | Active — PMP; `drivers/soc/apple/pmp.rs` | P,B | +| [2d8c0fe879e2](https://github.com/aurora-silicon/linux/commit/2d8c0fe879e26583b62655534a3aba86b1dfc80f) | Jonathan Goyvaerts | drm/asahi: mmu: make the secondary root of PublishedRoots optional [#420](https://github.com/aurora-silicon/linux/pull/420) | Active — MEM; `drivers/gpu/drm/asahi/g17/mod.rs` | M,D | +| [40507bad232c](https://github.com/aurora-silicon/linux/commit/40507bad232c7539f3c7c2540bac818a7aa90f26) | Jonathan Goyvaerts | drm/asahi: g17: support a single firmware coprocessor [#420](https://github.com/aurora-silicon/linux/pull/420) | Active — SHARED; `drivers/gpu/drm/asahi/g17/event.rs` | C,R,H | +| [750dd1a8e590](https://github.com/aurora-silicon/linux/commit/750dd1a8e5902e4e7478ce9213278b2131b63266) | Jonathan Goyvaerts | drm/asahi: g17: describe the firmware build in a variant table [#420](https://github.com/aurora-silicon/linux/pull/420) | Active translated data; MCache gated by V25G83.mcache=false — FW; `drivers/gpu/drm/asahi/g17/command.rs` | B,C,R | +| [9038ef8d328c](https://github.com/aurora-silicon/linux/commit/9038ef8d328cef14e038513ccaf55dc03869006b) | Jonathan Goyvaerts | drm/asahi: g17: take the per-SoC tables from Config [#420](https://github.com/aurora-silicon/linux/pull/420) | Active translated data — FW; `drivers/gpu/drm/asahi/g17/initdata.rs` | B,C,R | +| [24d1e5f4d8d3](https://github.com/aurora-silicon/linux/commit/24d1e5f4d8d31dd320048546cd53230a61203a9f) | Jonathan Goyvaerts | drm/asahi: g17: operating points with one voltage per cluster [#420](https://github.com/aurora-silicon/linux/pull/420) | Active — SHARED; `drivers/gpu/drm/asahi/g17/fw/initdata.rs` | C,R,H | +| [1ef423a08b3c](https://github.com/aurora-silicon/linux/commit/1ef423a08b3cbc81856afbe3c5854ac25b0893b6) | Jonathan Goyvaerts | drm/asahi: g17: register windows with several instances [#420](https://github.com/aurora-silicon/linux/pull/420) | Active — SHARED; `drivers/gpu/drm/asahi/g17/fw/initdata.rs` | C,R,H | +| [f23df96e697b](https://github.com/aurora-silicon/linux/commit/f23df96e697bc05a47029efd4fe786f900b08a83) | Jonathan Goyvaerts | drm/asahi: g17: let the variant choose the init acknowledgement [#420](https://github.com/aurora-silicon/linux/pull/420) | Active translated data — FW; `drivers/gpu/drm/asahi/g17/fw/variant.rs` | B,C,R | +| [39e990b44482](https://github.com/aurora-silicon/linux/commit/39e990b44482f46fb6bed6c3b7eb2413e0f0331a) | Jonathan Goyvaerts | drm/asahi: mmu: firmware tables in no-map reserved memory [#420](https://github.com/aurora-silicon/linux/pull/420) | Active — MEM; `drivers/gpu/drm/asahi/g17/mod.rs` | M,D | +| [54eccc50cc77](https://github.com/aurora-silicon/linux/commit/54eccc50cc770bdbe9306be4337c548f399e2eb8) | Jonathan Goyvaerts | drm/asahi: mmu: per-SoC UAT cache, TLB and handoff policy [#420](https://github.com/aurora-silicon/linux/pull/420) | Gated — UAT; `drivers/gpu/drm/asahi/mem.rs` | D | +| [abb36e8aa320](https://github.com/aurora-silicon/linux/commit/abb36e8aa3203f4cb28c1b2498d636617d01111d) | Jonathan Goyvaerts | drm/asahi: g17: 25G83 initialization layout [#420](https://github.com/aurora-silicon/linux/pull/420) | Active translated data — FW; `drivers/gpu/drm/asahi/g17/fw/initdata.rs` | B,C,R | +| [0d9efa515188](https://github.com/aurora-silicon/linux/commit/0d9efa515188d93cd775e240b20bf4ad29a258c8) | Jonathan Goyvaerts | drm/asahi: g17: render lead commands for the 25G83 firmware [#420](https://github.com/aurora-silicon/linux/pull/420) | Active translated data — FW; `drivers/gpu/drm/asahi/g17/buffer.rs` | B,C,R | +| [f8dc19862078](https://github.com/aurora-silicon/linux/commit/f8dc19862078a7ad72cd3e10fe22052a02609e76) | Jonathan Goyvaerts | drm/asahi: g17: 25G83 render and compute commands [#420](https://github.com/aurora-silicon/linux/pull/420) | Active translated data — FW; `drivers/gpu/drm/asahi/g17/buffer/target.rs` | B,C,R | +| [20e51646e036](https://github.com/aurora-silicon/linux/commit/20e51646e036432fa9498a24e5330b8d02068612) | Jonathan Goyvaerts | drm/asahi: g17: add the T6040 (M4 Pro) [#420](https://github.com/aurora-silicon/linux/pull/420) | Active translated data — FW; `drivers/gpu/drm/asahi/g17/fw/variant.rs` | B,C,R | +| [244f347d7dee](https://github.com/aurora-silicon/linux/commit/244f347d7deedb8a67eda1f0175ce001a5e7509c) | Jonathan Goyvaerts | soc: apple: rtkit: drop apple_rtkit_resume_idle() | Active — RTKIT; `drivers/soc/apple/rtkit.c` | B,F | +| [a214e242a034](https://github.com/aurora-silicon/linux/commit/a214e242a03404fc7324ff0efdc595a6b37a53c5) | Jonathan Goyvaerts | drm/asahi: g17: bounded cross-VM grace for render timeouts [#413](https://github.com/aurora-silicon/linux/pull/413) | Active — REC; `drivers/gpu/drm/asahi/g17/job.rs` | F,Q | +| [24349f4a4593](https://github.com/aurora-silicon/linux/commit/24349f4a4593fa4e02ce731f5e73a228aa27aed8) | Jonathan Goyvaerts | drm/asahi: g17: retire abandoned spared renders without failing clients [#413](https://github.com/aurora-silicon/linux/pull/413) | Active — REC; `drivers/gpu/drm/asahi/g17/completion.rs` | F,Q | +| [9af21ada7212](https://github.com/aurora-silicon/linux/commit/9af21ada7212737042705f3081d153332516a182) | Jonathan Goyvaerts | drm/asahi: g17: spare innocent renders once the cross-VM grace runs out [#413](https://github.com/aurora-silicon/linux/pull/413) | Active — SHARED; `drivers/gpu/drm/asahi/g17/runtime/backend.rs` | R,H | +| [29f38d83f6a6](https://github.com/aurora-silicon/linux/commit/29f38d83f6a6208d4ff3c4187042b3b0b476d242) | Jonathan Goyvaerts | drm/asahi: g17: keep a VM usable behind its spared render quarantine [#413](https://github.com/aurora-silicon/linux/pull/413) | Active — REC; `drivers/gpu/drm/asahi/g17/completion.rs` | F,Q | +| [c71f1177e773](https://github.com/aurora-silicon/linux/commit/c71f1177e7730db49e9a7ec9cdf150a0feeb05ab) | Jonathan Goyvaerts | drm/asahi: g17: hold the GPU powered for idle_off_delay_ms after work [#413](https://github.com/aurora-silicon/linux/pull/413) | Active — POWER; `drivers/gpu/drm/asahi/g17/mod.rs` | I,C,R,P | +| [645b4e55cf16](https://github.com/aurora-silicon/linux/commit/645b4e55cf16c1cedffba87258bdaafd5b15dcd8) | Jonathan Goyvaerts | drm/asahi: g17: replay every render group firmware dropped unexecuted [#413](https://github.com/aurora-silicon/linux/pull/413) | Active — REC; `drivers/gpu/drm/asahi/g17/completion.rs` | F,Q | +| [94e719a0cec2](https://github.com/aurora-silicon/linux/commit/94e719a0cec2a65502ce5413d2ee9a105c1cc8d9) | Ryan Murray | drm/asahi: g17: never replay a render group a recovery blamed [#418](https://github.com/aurora-silicon/linux/pull/418) | Active — REC; `drivers/gpu/drm/asahi/g17/completion.rs` | F,Q | +| [1054f6763014](https://github.com/aurora-silicon/linux/commit/1054f6763014fe4b4032a08ae7ccce721be4d385) | Ryan Murray | drm/asahi: g17: fix the render timeout head and replay documentation [#418](https://github.com/aurora-silicon/linux/pull/418) | Active — REC; `drivers/gpu/drm/asahi/g17/queue/render.rs` | F,Q | +| [45ac35fa2796](https://github.com/aurora-silicon/linux/commit/45ac35fa279642f98ea7326144716755fb3a39a2) | Ryan Murray | drm/asahi: g17: move the fw-data crash log mapping to its own file [#420](https://github.com/aurora-silicon/linux/pull/420) | Active — SHARED; `drivers/gpu/drm/asahi/g17/data_segment.rs` | C,R,H | +| [fd21bf6faed6](https://github.com/aurora-silicon/linux/commit/fd21bf6faed6f978223e61444424ee918c199838) | Ryan Murray | drm/asahi: g17: take the core-layout buffers from Config [#420](https://github.com/aurora-silicon/linux/pull/420) | Active translated data — FW; `drivers/gpu/drm/asahi/g17/fw/compute.rs` | B,C,R | +| [c0b9fbfa9fb0](https://github.com/aurora-silicon/linux/commit/c0b9fbfa9fb048d5bc67ffb9e17168b686b1962d) | Ryan Murray | drm/asahi: g17: check the voltage cells of the GPU operating points [#420](https://github.com/aurora-silicon/linux/pull/420) | Active translated data — FW; `drivers/gpu/drm/asahi/g17/initdata.rs` | B,C,R | +| [1c5a2ef5783a](https://github.com/aurora-silicon/linux/commit/1c5a2ef5783a565e6b27da871d30e4e637e1ce69) | Ryan Murray | drm/asahi: g17: accept only the two host words of a pointer block [#420](https://github.com/aurora-silicon/linux/pull/420) | Active translated data — FW; `drivers/gpu/drm/asahi/g17/fw/queue.rs` | B,C,R | +| [df6b70b85cbf](https://github.com/aurora-silicon/linux/commit/df6b70b85cbfada36604f511b9ba8da21b13eb7d) | Ryan Murray | drm/asahi: g17: pass the fragment ID to the barrier unchanged [#420](https://github.com/aurora-silicon/linux/pull/420) | Active — SHARED; `drivers/gpu/drm/asahi/g17/fw/queue.rs` | C,R,H | +| [496a59f89601](https://github.com/aurora-silicon/linux/commit/496a59f8960191dc511b92328b13ccc7e31ad1ff) | Ryan Murray | drm/asahi: g17: do not spare a timeout behind work that already failed [#418](https://github.com/aurora-silicon/linux/pull/418) | Active — REC; `drivers/gpu/drm/asahi/g17/queue/compute.rs` | F,Q | +| [fc1a16b4722c](https://github.com/aurora-silicon/linux/commit/fc1a16b4722ce9a9eb6b77fc0af4163873cacc9e) | Ryan Murray | drm/asahi: g17: name the blocking work in the watchdog lines [#418](https://github.com/aurora-silicon/linux/pull/418) | Active — OBS; `drivers/gpu/drm/asahi/g17/queue/compute.rs` | O,F | +| [894ec76dc892](https://github.com/aurora-silicon/linux/commit/894ec76dc89219caae27f9d6845521337b16e8bf) | Ryan Murray | drm/asahi: g17: treat a torn render sample in the timeout fallback as no proof [#418](https://github.com/aurora-silicon/linux/pull/418) | Active — REC; `drivers/gpu/drm/asahi/g17/queue/render.rs` | F,Q | +| [7d167e382f40](https://github.com/aurora-silicon/linux/commit/7d167e382f40542b25ffceb596d0982ee002b7ca) | Ryan Murray | drm/asahi: g17: fail the dropped successors of an exhausted render replay [#418](https://github.com/aurora-silicon/linux/pull/418) | Active — REC; `drivers/gpu/drm/asahi/g17/queue/render.rs` | F,Q | +| [5ee97891f61f](https://github.com/aurora-silicon/linux/commit/5ee97891f61f552c8c1d677db50725c413fb3ed7) | Ryan Murray | drm/asahi: g17: retire finished renders before attributing a render timeout [#418](https://github.com/aurora-silicon/linux/pull/418) | Active — REC; `drivers/gpu/drm/asahi/g17/runtime/backend.rs` | F,Q | +| [25f4c6c3ab18](https://github.com/aurora-silicon/linux/commit/25f4c6c3ab18f329ba2af41b102c30d53e361ae6) | Ryan Murray | thermal: apple: Describe the PMP policy KUnit test module [#417](https://github.com/aurora-silicon/linux/pull/417) | Active — PMP; `drivers/thermal/apple-pmp-policy-test.c` | P,B | +| [b9a8f3938f3d](https://github.com/aurora-silicon/linux/commit/b9a8f3938f3daf5926b7c86204113f6730aec136) | Ryan Murray | drm/asahi: g17: pass the queue count to Pool::new() in the fault admission test [#420](https://github.com/aurora-silicon/linux/pull/420) | Active — GMEM; `drivers/gpu/drm/asahi/g17/queue/admission.rs` | M,C,R | +| [bec833e0bc58](https://github.com/aurora-silicon/linux/commit/bec833e0bc581f74744265f0613832e8291db0cc) | Jonathan Goyvaerts | drm/asahi: pgtable: check the struct page of a table outside the reserved regions [#427](https://github.com/aurora-silicon/linux/pull/427) | Active — MEM; `drivers/gpu/drm/asahi/mmu.rs` | M,D | +| [a335c77de52a](https://github.com/aurora-silicon/linux/commit/a335c77de52a274029bb4e42f96d6afde5712a2f) | Jonathan Goyvaerts | drm/asahi: identify the T6040 GPU firmware by its loaded text [#428](https://github.com/aurora-silicon/linux/pull/428) | Active translated data — FW; `drivers/gpu/drm/asahi/g17/fw/variant.rs` | B,C,R | +| [d69f14f4e4fb](https://github.com/aurora-silicon/linux/commit/d69f14f4e4fbe9af21b70ee5c1b3ff9125c7a9cf) | DjDeveloperr | soc: apple: clpc: raise the T8140 fabric floor for GPU work [#440](https://github.com/aurora-silicon/linux/pull/440) | Active after port; 0d63308820ab — GPUFLOOR; `drivers/soc/apple/clpc.c` | P,C,R,H,I | +| [d1468a5f9a50](https://github.com/aurora-silicon/linux/commit/d1468a5f9a50a23cf741f56d83e6690441b90482) | Ryan | drm/asahi: gpu: drop the unused module_parameters import [#395](https://github.com/aurora-silicon/linux/pull/395) | Active — MEM; `drivers/gpu/drm/asahi/gpu.rs` | M,D | +| [26f5eb68dbce](https://github.com/aurora-silicon/linux/commit/26f5eb68dbce039de470b789d754d2ec3541c15e) | Ryan Murray | drm/asahi: admit the alternate T6040 loaded 25G83 image [#453](https://github.com/aurora-silicon/linux/pull/453) | Active translated data — FW; `drivers/gpu/drm/asahi/g17/fw/variant.rs` | B,C,R | +| [88aebfe0c656](https://github.com/aurora-silicon/linux/commit/88aebfe0c656676c530faa8f85df8d7b69a7ddb0) | DjDeveloperr | irqchip/apple-aic: Report the effective affinity of AICv2+ IRQs [#447](https://github.com/aurora-silicon/linux/pull/447) | Active — AIC; `drivers/irqchip/irq-apple-aic.c` | O,C,I | +| [eb346c1a25d8](https://github.com/aurora-silicon/linux/commit/eb346c1a25d813a1411a34fafc7d4bdee418e0d4), [42a176b951d9](https://github.com/aurora-silicon/linux/commit/42a176b951d90e746e33d27d6eb06a1773579afe) | Haoxiang Li | cpufreq: apple-soc: Fix OPP table cleanup | Active — CPU; `drivers/cpufreq/apple-soc-cpufreq.c` | C,I,P | +| [ae37f03d4163](https://github.com/aurora-silicon/linux/commit/ae37f03d4163df8143b2144e742916b91bf90654) | Ryan Murray | thermal: apple-smc-cpu: Add the J700 CPU thermal policy [#22](https://github.com/aurora-silicon/linux/pull/22) | Active — CPU; `drivers/cpufreq/apple-soc-cpufreq.c` | C,I,P | +| [583b9fa52fbf](https://github.com/aurora-silicon/linux/commit/583b9fa52fbf6a47f150736ad4a1aaa0d616ac02), [a8872354b695](https://github.com/aurora-silicon/linux/commit/a8872354b695a066ac6411791a691f26fcde55bf) | Oliver Lukschander | pmdomain: apple: pmp-report: initialize the report lock [#56](https://github.com/aurora-silicon/linux/pull/56) | Active — PMP; `drivers/pmdomain/apple/pmp-report.c` | P,B | +| [8ca5289bec3a](https://github.com/aurora-silicon/linux/commit/8ca5289bec3a2b6f3629c9df516b357778273a72) | Joshua Warren | drm/asahi: connect the snapshot pointer to the C sysfs static [#197](https://github.com/aurora-silicon/linux/pull/197) [#380](https://github.com/aurora-silicon/linux/pull/380) [#470](https://github.com/aurora-silicon/linux/pull/470) | Active; carried/refined form — MEM; `drivers/gpu/drm/asahi/gpu.rs` | M,D | +| [628bf1df4312](https://github.com/aurora-silicon/linux/commit/628bf1df43123ead994b028fd7b82a209e67aa30), [983575d7b492](https://github.com/aurora-silicon/linux/commit/983575d7b492f6824401cd21fe02278812211e27) | Ryan Murray | drm/asahi: initialize driver data before claiming destructor ownership [#171](https://github.com/aurora-silicon/linux/pull/171) [#380](https://github.com/aurora-silicon/linux/pull/380) [#470](https://github.com/aurora-silicon/linux/pull/470) | Active; carried/refined form — MEM; `drivers/gpu/drm/asahi/driver.rs` | M,B | +| [b10d60ce3916](https://github.com/aurora-silicon/linux/commit/b10d60ce3916c44dfb19b1f1b197bdca40bbac34) | Ryan Murray | drm/asahi: retain two-phase data ownership with stats registration [#380](https://github.com/aurora-silicon/linux/pull/380) [#470](https://github.com/aurora-silicon/linux/pull/470) | Active; carried/refined form — MEM; `drivers/gpu/drm/asahi/driver.rs` | M,B | +| [9266541b8d35](https://github.com/aurora-silicon/linux/commit/9266541b8d35c1cd7146eec9a9b31b39fb7f29cc) | Ryan Murray | treewide: describe hardware behaviour in comments [#233](https://github.com/aurora-silicon/linux/pull/233) [#380](https://github.com/aurora-silicon/linux/pull/380) [#470](https://github.com/aurora-silicon/linux/pull/470) | Active; carried/refined form — MBOX; `drivers/gpu/drm/asahi/g17/buffer.rs` | B,O | +| [8533f072dc30](https://github.com/aurora-silicon/linux/commit/8533f072dc30e076206631f68f41fb4f8d7512a7) | Janne Grunau | iommu: apple-dart: Support specifying the DMA aperture in the DT | Active; carried/refined form — DART; `drivers/iommu/apple-dart.c` | D,B | +| [a9e24ae824ec](https://github.com/aurora-silicon/linux/commit/a9e24ae824ec205cf90f3545b0125631adf1326a) | Ryan Murray | merge: retain the 7.3 implementations where older ports duplicate them [#470](https://github.com/aurora-silicon/linux/pull/470) | Active; carried/refined form — PMGR; `drivers/bluetooth/hci_bcm4377.c` | B,F | +| [00248157ec1f](https://github.com/aurora-silicon/linux/commit/00248157ec1fc4e6369cd26cb8304bb56df7415d), [b2258cfadd69](https://github.com/aurora-silicon/linux/commit/b2258cfadd697cbe10c1e29b54f512c8f297bbe6) | Joshua Warren | soc: apple: mailbox: Make the send-empty IRQ optional (poll TX) | Active — MBOX; `drivers/soc/apple/mailbox.c` | B,O | +| [00e21c12c05a](https://github.com/aurora-silicon/linux/commit/00e21c12c05af1d540a2dc256a8cea3d17d63edc) | Lyude Paul | rust: drm: gem: Add export() callback | Active — DRM; `drivers/gpu/drm/nova/gem.rs` | C,H,M,F | +| [03329eff23bd](https://github.com/aurora-silicon/linux/commit/03329eff23bd3a72bdcfa09a3b98d0ac15107e6e), [35b057e57222](https://github.com/aurora-silicon/linux/commit/35b057e572224c39f79367df9e7c464f1822d15a), [e68c60f81285](https://github.com/aurora-silicon/linux/commit/e68c60f81285248fb50578f268343b06a6452a67) | DjDeveloperr | rust: drm: gpuvm: add non-blocking private mapping traversal [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — DRM; `rust/kernel/drm/gpuvm.rs` | C,H,M,F | +| [033943b2b6ed](https://github.com/aurora-silicon/linux/commit/033943b2b6edb176aaebffa917e2273c3bf8f625) | Ryan Murray | drm: apple: parser: cap the number of timing modes [#345](https://github.com/aurora-silicon/linux/pull/345) | Active; carried/refined form — DISPLAY; `drivers/gpu/drm/apple/parser.c` | B | +| [08e4dd7596c9](https://github.com/aurora-silicon/linux/commit/08e4dd7596c91cdb5b761a0d272e40d75320306b), [29db1d8ab517](https://github.com/aurora-silicon/linux/commit/29db1d8ab517933c2e0114fb3c6bf9beda5417aa), [437f51c11728](https://github.com/aurora-silicon/linux/commit/437f51c11728f3183b0c17af6264547e44a60c26) | Ryan Murray | soc: apple: synchronize the ASCWrap mailbox provider with j700 [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — MBOX; `drivers/soc/apple/mailbox.c` | B,O | +| [0db024825eb1](https://github.com/aurora-silicon/linux/commit/0db024825eb15d5ffd51ae6bb6b71ea4899ff3c2) | Ryan Murray | rust: drm: update shmem KUnit fixtures for the 7.2 file API | Active — DRM; `rust/kernel/drm/gem/shmem.rs` | C,H,M,F | +| [17f9a8193f55](https://github.com/aurora-silicon/linux/commit/17f9a8193f553f12afb69a14c849e12889842e8b) | Ryan Murray | rust: drm: gem: shmem: leave an imported object's SG table to the attachment | Active — DRM; `rust/kernel/drm/gem/shmem.rs` | C,H,M,F | +| [1f6c1fbb7057](https://github.com/aurora-silicon/linux/commit/1f6c1fbb7057db9206dce7187cfd3ec73233cd67) | Ryan Murray | drm/apple: dcp: Release the fixed display route with the device [#345](https://github.com/aurora-silicon/linux/pull/345) | Active; carried/refined form — DISPLAY; `drivers/gpu/drm/apple/dcp.c` | B | +| [29a2e2acec42](https://github.com/aurora-silicon/linux/commit/29a2e2acec42f8361d72bfd9506e3a0527afaf40) | DjDeveloperr | sched/core: add scoped utilization floors for kernel waiters [#171](https://github.com/aurora-silicon/linux/pull/171) | Active; carried/refined form — DRM | C,H,M,F | +| [29a913a5c222](https://github.com/aurora-silicon/linux/commit/29a913a5c222f2bec2ecae62a4dc4af223e5962d) | Jonathan Goyvaerts | soc: apple: mailbox: support the T6040 ASCWrap v6 [#420](https://github.com/aurora-silicon/linux/pull/420) | Active — MBOX; `drivers/soc/apple/mailbox.c` | B,O | +| [2b59aed7191c](https://github.com/aurora-silicon/linux/commit/2b59aed7191cccf0f6239e50e9a165f49b649529) | Ryan Murray | drm/asahi: avoid fence locks when pruning G17 frontiers [#201](https://github.com/aurora-silicon/linux/pull/201) | Active; carried/refined form — SHARED; `drivers/gpu/drm/asahi/g17/fence.rs` | C,R,H | +| [2dc22a191a5c](https://github.com/aurora-silicon/linux/commit/2dc22a191a5cf3109fb18dcfda0fec94bd4df18b), [a45a44cc6e2b](https://github.com/aurora-silicon/linux/commit/a45a44cc6e2b6bbdb189debda045a2174d07e039) | Ryan Murray | rust: drm: gem: shmem: let drivers allocate the objects the core creates | Active — DRM; `rust/kernel/drm/gem/mod.rs` | C,H,M,F | +| [32418c60d4cd](https://github.com/aurora-silicon/linux/commit/32418c60d4cdd6b37e2e9c7e88c7e299d473cf19) | Ryan Murray | drm/apple: dcp: Do not hold the mailbox of an unbound DCP for sleep [#345](https://github.com/aurora-silicon/linux/pull/345) | Active; carried/refined form — DISPLAY; `drivers/gpu/drm/apple/dcp.c` | B | +| [351fc1af825c](https://github.com/aurora-silicon/linux/commit/351fc1af825c49264de7e171a983d2c0c83f1601) | Ryan Murray | dt-bindings: power: apple,pmc: describe the DCS limit [#219](https://github.com/aurora-silicon/linux/pull/219) | Gated — DCS | P | +| [3b9a0383f589](https://github.com/aurora-silicon/linux/commit/3b9a0383f589ebaaa5bcd5c3242e294d6051f0e0) | Janne Grunau | FIXME: drm/gpuvm: Add DRM_GPUVA_REPEAT flag and logic | Active — DRM; `rust/kernel/drm/gpuvm/sm_ops.rs` | C,H,M,F | +| [3c102dba66df](https://github.com/aurora-silicon/linux/commit/3c102dba66df1119625c11c80dca301fb308563c) | Asahi Lina | rust: drm: mm: Add DRM MM Range Allocator abstraction | Active — DRM; `rust/kernel/drm/mm.rs` | C,H,M,F | +| [3d0c9c889919](https://github.com/aurora-silicon/linux/commit/3d0c9c8899198a8ad6d0d0241283ba04d2286fd7) | Ryan Murray | drm: apple: parser: bound the timing catalog allocation [#345](https://github.com/aurora-silicon/linux/pull/345) | Active; carried/refined form — DISPLAY; `drivers/gpu/drm/apple/parser.c` | B | +| [3d3fe96509d1](https://github.com/aurora-silicon/linux/commit/3d3fe96509d102770f72ce5f7829baa3c0f7d1ce), [df2e3cd3b778](https://github.com/aurora-silicon/linux/commit/df2e3cd3b77882ec523b42184d05596e60094072) | Ryan Murray | rust: soc: apple: mailbox: Fix receiver teardown and expose the device | Active — MBOX; `rust/kernel/soc/apple/mailbox.rs` | B,O | +| [3fd71b22d86c](https://github.com/aurora-silicon/linux/commit/3fd71b22d86c3e2397ed1da8a15123053d46a423), [536484539477](https://github.com/aurora-silicon/linux/commit/53648453947771b7ca24a61a585bffdbe1853242) | Ryan Murray | soc: apple: mailbox: Add T8140 ASC and AOP setup mailboxes | Active — MBOX; `drivers/soc/apple/mailbox.c` | B,O | +| [43fc98be783b](https://github.com/aurora-silicon/linux/commit/43fc98be783b0e9833242c1d8d111b49366b134b), [7c99d8debcc5](https://github.com/aurora-silicon/linux/commit/7c99d8debcc54ebf3f76ede14ced9b845db18215), [b0cc4eeb554a](https://github.com/aurora-silicon/linux/commit/b0cc4eeb554a74c2db220e35ceb14955142a79ed) | DjDeveloperr | drm/syncobj: apply expiring capacity hints during blocking waits [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — DRM; `drivers/gpu/drm/drm_syncobj.c` | C,H,M,F | +| [4a577fcddddb](https://github.com/aurora-silicon/linux/commit/4a577fcddddbefec7d13bfd4ae3503893602d29b) | DjDeveloperr | rust: expose the DRM duplicate-reservation flag used by GPUVM [#171](https://github.com/aurora-silicon/linux/pull/171) | Active; carried/refined form — DRM | B,M | +| [4aa16bb5021e](https://github.com/aurora-silicon/linux/commit/4aa16bb5021e427507e15a9a48848d0484a4f3d6) | Eryk Wieliczko | rust: expose fence and DRM lifecycle helpers for native GPU backends | Active — DRM; `rust/kernel/dma_fence.rs` | C,H,M,F | +| [4f7a3272df96](https://github.com/aurora-silicon/linux/commit/4f7a3272df9627d98bc11a7da0c8189ca4aba196) | DjDeveloperr | drm/shmem-helper: populate revocable CPU mappings one page at a time [#171](https://github.com/aurora-silicon/linux/pull/171) | Active; carried/refined form — DRM; `drivers/gpu/drm/drm_gem_shmem_helper.c` | C,H,M,F | +| [5443fc6aeb7a](https://github.com/aurora-silicon/linux/commit/5443fc6aeb7a0fc574805b8b0b1513bbd02dcab4) | Ryan Murray | soc: apple: mailbox: only include reachable KUnit tests | Active — MBOX; `drivers/soc/apple/mailbox.c` | B,O | +| [5611637f6851](https://github.com/aurora-silicon/linux/commit/5611637f685115048c64231e7ac729f23ab20001), [5eff5323da02](https://github.com/aurora-silicon/linux/commit/5eff5323da0282c8fb9adfc57acdc9217e6d7995) | Ryan Murray | soc: apple: mailbox: drain only a present send-empty IRQ | Active — MBOX; `drivers/soc/apple/mailbox.c` | B,O | +| [56920e3e6752](https://github.com/aurora-silicon/linux/commit/56920e3e675230ced9a2ccab81bb7e9aa9c2ccf0) | Janne Grunau | rust: drm: gpuvm: Add sm_can_merge_flags | Active — DRM; `rust/kernel/drm/gpuvm/mod.rs` | C,H,M,F | +| [6154ed6d0f41](https://github.com/aurora-silicon/linux/commit/6154ed6d0f410dd69cbb3e218981b6dc549ac5df) | Janne Grunau | rust: drm: file: Use Registered device in DriverFile::open trait | Active — DRM; `drivers/gpu/drm/nova/file.rs` | C,H,M,F | +| [63d3b0f41939](https://github.com/aurora-silicon/linux/commit/63d3b0f419399d027c865d4ccffa12e3617a73b8) | Ryan Murray | drm: apple: afk: factor the receive bounds check into a helper [#345](https://github.com/aurora-silicon/linux/pull/345) | Active; carried/refined form — DISPLAY; `drivers/gpu/drm/apple/afk.c` | B | +| [6740c25f03c1](https://github.com/aurora-silicon/linux/commit/6740c25f03c1f552b0bdc0ad5b02926d96dd99ad) | Ryan Murray | drm/asahi: Log G17 firmware recovery causes before acknowledgement [#200](https://github.com/aurora-silicon/linux/pull/200) | Active; carried/refined form — OBS; `drivers/gpu/drm/asahi/g17/queue/compute.rs` | O,F | +| [6803b9328c48](https://github.com/aurora-silicon/linux/commit/6803b9328c483c492e0a26fadf0a7cd03a5fe7cc) | Janne Grunau | rust: drm: gpuvm: GpuVa flags and REPEAT logic | Active — DRM; `rust/kernel/drm/gpuvm/sm_ops.rs` | C,H,M,F | +| [6a127c6ff62c](https://github.com/aurora-silicon/linux/commit/6a127c6ff62cde78267225a7094cda2771504c28) | Janne Grunau | rust: drm: file: Add as_raw() | Active — DRM; `drivers/gpu/drm/nova/file.rs` | C,H,M,F | +| [6db05cf4b231](https://github.com/aurora-silicon/linux/commit/6db05cf4b231f020020f31a479bd45ccff83520f) | DjDeveloperr | drm/shmem-helper: index fault pages by object offset [#171](https://github.com/aurora-silicon/linux/pull/171) | Active; carried/refined form — DRM; `drivers/gpu/drm/drm_gem_shmem_helper.c` | C,H,M,F | +| [70d9b6f64ee7](https://github.com/aurora-silicon/linux/commit/70d9b6f64ee76e9da96b752ee3933e2d0a66af0b) | Ryan Murray | drm: apple: free property dictionaries the connector does not keep [#345](https://github.com/aurora-silicon/linux/pull/345) | Active; carried/refined form — DISPLAY; `drivers/gpu/drm/apple/connector.c` | B | +| [72c42b6ddae6](https://github.com/aurora-silicon/linux/commit/72c42b6ddae68c8c58c5f5d720fda5079ae24302) | Janne Grunau | rust: drm: driver: Add feature flags used by asahi | Active — DRM; `rust/kernel/drm/device.rs` | C,H,M,F | +| [765577e012da](https://github.com/aurora-silicon/linux/commit/765577e012da1ee83e6eb56029bdbb9f1f799dce) | Ryan Murray | drm/apple: dptxep: Remove call counters and tidy tunnel comments [#345](https://github.com/aurora-silicon/linux/pull/345) | Active; carried/refined form — DISPLAY; `drivers/gpu/drm/apple/dptxep.c` | B | +| [77a9ab842a02](https://github.com/aurora-silicon/linux/commit/77a9ab842a02e980873a2298caa4dfe8d56faaff) | Ryan Murray | rust: drm: gpuvm: do not leak the VM when exec_lock() fails | Active — DRM; `rust/kernel/drm/gpuvm/mod.rs` | C,H,M,F | +| [7a155e383d2d](https://github.com/aurora-silicon/linux/commit/7a155e383d2d8e70fd76075ffb7427f4c7a3c326), [c98d2b872a2b](https://github.com/aurora-silicon/linux/commit/c98d2b872a2b02241ce4a34d9fe6841cd32b38c3) | Ryan Murray | soc: apple: mailbox: Keep the send-empty IRQ enable balanced | Active — MBOX; `drivers/soc/apple/mailbox.c` | B,O | +| [7c57598ac57c](https://github.com/aurora-silicon/linux/commit/7c57598ac57cb7804a9f2e89d0975e2005985b96) | Janne Grunau | rust: drm: gem: Support locking gpuva.lock | Active — DRM; `rust/kernel/drm/gem/mod.rs` | C,H,M,F | +| [7dce231ad73d](https://github.com/aurora-silicon/linux/commit/7dce231ad73d98e102b4b9e0b362cbab21750854) | DjDeveloperr | drm/syncobj: skip wait hint lookup for ineligible waits [#171](https://github.com/aurora-silicon/linux/pull/171) | Active; carried/refined form — DRM; `drivers/gpu/drm/drm_syncobj.c` | C,H,M,F | +| [7ed160b9b5f2](https://github.com/aurora-silicon/linux/commit/7ed160b9b5f28df712e98088d4368188b2e28072), [c7df21582810](https://github.com/aurora-silicon/linux/commit/c7df2158281045e6fa9bfa6b7c170910f1089d2e) | Ryan Murray | soc: apple: mailbox: Tidy T8140 comments and resume error logging [#314](https://github.com/aurora-silicon/linux/pull/314) | Active — OBS; `drivers/soc/apple/mailbox.c` | O,F | +| [887533ef3bd8](https://github.com/aurora-silicon/linux/commit/887533ef3bd88de5ec05f93be23d2fefd3570cb5) | Ryan Murray | rust: drm: gem: shmem: wait for backing release instead of failing a lease | Active — DRM; `rust/kernel/drm/gem/shmem.rs` | C,H,M,F | +| [8cdeb29b542a](https://github.com/aurora-silicon/linux/commit/8cdeb29b542af3cd5ffb3b535330ad4fd67b97a7) | Janne Grunau | HACK: rust: drm: gem: shmem: Make VMap.ptr pub | Active — DRM; `rust/kernel/drm/gem/shmem.rs` | C,H,M,F | +| [9142d428cb69](https://github.com/aurora-silicon/linux/commit/9142d428cb6956aeb36ffebabe42e1a5c2396c2c) | Ryan Murray | drm: apple: afk: bound received queue entries without 32-bit wrap [#345](https://github.com/aurora-silicon/linux/pull/345) | Active; carried/refined form — DISPLAY; `drivers/gpu/drm/apple/afk.c` | B | +| [92d02170df1e](https://github.com/aurora-silicon/linux/commit/92d02170df1ecf2273d13ba63c0cedbb891996d3) | Jonathan Goyvaerts | arm64: dts: apple: t6040: Add the GPU [#420](https://github.com/aurora-silicon/linux/pull/420) | Active translated data — FW | B,P | +| [9429b186dc3e](https://github.com/aurora-silicon/linux/commit/9429b186dc3edb6658bcdd02e451a5cb1c5f0844) | Ryan Murray | rust: drm: report client memory in fdinfo | Active — DRM; `rust/kernel/drm/device.rs` | C,H,M,F | +| [9523159a7288](https://github.com/aurora-silicon/linux/commit/9523159a72886c5169e7bfd566de9c0aa545ba85) | Ryan Murray | soc: apple: clpc: drop the syscon select and the GPU from Kconfig [#345](https://github.com/aurora-silicon/linux/pull/345) | Active; carried/refined form — CLPC; `drivers/soc/apple/Kconfig` | P,C,R,I | +| [a3828ea8a63d](https://github.com/aurora-silicon/linux/commit/a3828ea8a63d2488f2080dd1f4f6ce8ebd133623) | Asahi Lina | rust: drm: syncobj: Add DRM Sync Object abstraction | Active — DRM; `rust/kernel/drm/mod.rs` | B,M | +| [a6f8817ebbf6](https://github.com/aurora-silicon/linux/commit/a6f8817ebbf6c420aee66897f717ab4e94a4a717) | Ryan Murray | drm/asahi: Reuse fully retired guilty G17 compute queues [#200](https://github.com/aurora-silicon/linux/pull/200) | Active; carried/refined form — REC; `drivers/gpu/drm/asahi/g17/queue/compute.rs` | F,Q | +| [b147e1f460b5](https://github.com/aurora-silicon/linux/commit/b147e1f460b56e398bc085b2457b1ba163cf4505) | Jonathan Goyvaerts | arm64: dts: apple: t6040-j616s: Add the gpu alias [#420](https://github.com/aurora-silicon/linux/pull/420) | Active translated data — FW | B,P | +| [b1f7d09dcf33](https://github.com/aurora-silicon/linux/commit/b1f7d09dcf33e3bb06e81d846783b698d604be5b) | Ryan Murray | drm/apple: dcp: Initialise the start completion before checking RTKit [#345](https://github.com/aurora-silicon/linux/pull/345) | Active; carried/refined form — DISPLAY; `drivers/gpu/drm/apple/dcp.c` | B | +| [b965be95b07f](https://github.com/aurora-silicon/linux/commit/b965be95b07f46ef0be02f47a3a02017d912e4cc) | Asahi Lina | rust: drm: gpuvm: Add part of the asahi downstream gpuvm abstractions | Active — DRM; `rust/kernel/drm/gpuvm/mod.rs` | B,M | +| [ba69d4dde975](https://github.com/aurora-silicon/linux/commit/ba69d4dde9751976c323055ef683d76febf82a2b) | Ryan Murray | drm: apple: afk: validate the receive write pointer and bound a drain [#345](https://github.com/aurora-silicon/linux/pull/345) | Active; carried/refined form — DISPLAY; `drivers/gpu/drm/apple/afk.c` | B | +| [c0e5730f8058](https://github.com/aurora-silicon/linux/commit/c0e5730f8058f14c8fdeb5cd96213e57822f7bbb) | Ryan Murray | drm/apple: dcp: Tidy RTKit start-up logging [#345](https://github.com/aurora-silicon/linux/pull/345) | Active; carried/refined form — DISPLAY; `drivers/gpu/drm/apple/dcp.c` | B | +| [c4722f38cdcc](https://github.com/aurora-silicon/linux/commit/c4722f38cdccca6dbbbc446dfa2d715ab96e9554) | Ryan Murray | arm64: dts: apple: t6040: Do not claim the GPU's PMP report entry at boot [#402](https://github.com/aurora-silicon/linux/pull/402) | Active — PMP | P,B | +| [cd71c1e7ff90](https://github.com/aurora-silicon/linux/commit/cd71c1e7ff901c3c35492ebe5b8bebb0702e5480) | Jonathan Goyvaerts | arm64: dts: apple: t6040-j616s: describe the GPU firmware text segment [#428](https://github.com/aurora-silicon/linux/pull/428) | Active translated data — FW | B,P | +| [d50642d23a7e](https://github.com/aurora-silicon/linux/commit/d50642d23a7edb9ace6837d6471981e84d0d4ef6) | Ryan Murray | iommu: apple-dart: Fix the KUnit test after deferred root publication [#314](https://github.com/aurora-silicon/linux/pull/314) | Active; carried/refined form — DART; `drivers/iommu/apple-dart-test.c` | D,B | +| [d6799a78db13](https://github.com/aurora-silicon/linux/commit/d6799a78db13ad06a2f26b66aad97c7e7626714d) | Ryan Murray | drm/apple: dcp: Initialise work items at probe [#345](https://github.com/aurora-silicon/linux/pull/345) | Active; carried/refined form — DISPLAY; `drivers/gpu/drm/apple/dcp.c` | B | +| [d8b45282c1e7](https://github.com/aurora-silicon/linux/commit/d8b45282c1e70cb4b947d2aadfb2636fd4ee763a) | Lyude Paul | rust: drm: gem: Add BaseObject::prime_export() | Active — DRM; `rust/kernel/drm/gem/mod.rs` | C,H,M,F | +| [defdcb801774](https://github.com/aurora-silicon/linux/commit/defdcb8017744fe0aa57061ed304e793935b52bc) | Ryan Murray | soc: apple: build the CLPC floors by default [#219](https://github.com/aurora-silicon/linux/pull/219) | Active; carried/refined form — CLPC; `drivers/soc/apple/Kconfig` | P,C,R,I | +| [e1468f83c1da](https://github.com/aurora-silicon/linux/commit/e1468f83c1daf236b69c6868a3a05a9d6f994f9b) | Janne Grunau | rust: drm: gem: Add missing trailing '//' for kernel vertical style | Active — DRM; `rust/kernel/drm/gem/mod.rs` | C,H,M,F | +| [e6d369606edf](https://github.com/aurora-silicon/linux/commit/e6d369606edff3c24dc3b1f5dda036cc77a8887c) | Ryan Murray | drm/shmem: revoke CPU PFNs before releasing purge backing [#171](https://github.com/aurora-silicon/linux/pull/171) | Active; carried/refined form — DRM; `drivers/gpu/drm/drm_gem_shmem_helper.c` | C,H,M,F | +| [f0a8afc04957](https://github.com/aurora-silicon/linux/commit/f0a8afc0495743e502fc860fa2bc0fc96e95b22a) | Sami Tolvanen | rust: drm: add a registration guard for the Normal device context | Active — DRM; `rust/kernel/drm/device.rs` | C,H,M,F | +| [fd2a0321db11](https://github.com/aurora-silicon/linux/commit/fd2a0321db11572ac201663697c83c9330ef65a3) | DjDeveloperr | driver core: track devres node membership for direct removal [#390](https://github.com/aurora-silicon/linux/pull/390) | Active — DRM; `drivers/base/base.h` | B,M | + +### N/A, superseded, other-backend and imported-neighbor records + +These scoped candidates are included to make exclusions reviewable. A shared +path query or an omnibus PR is not evidence of a Neo performance improvement. + +| Commits / encountered aliases | Author | Improvement / record | T6040 after ports — evidence key and changed source | Measurement | +| --- | --- | --- | --- | --- | +| [4f772d2c2d6c](https://github.com/aurora-silicon/linux/commit/4f772d2c2d6cb336d4df08ae6c554b8387510c20) | Janne Grunau | drm/asahi: initdata: Fold GlobalsSub struct into Globals | Not applicable — LEGACY; `drivers/gpu/drm/asahi/fw/initdata.rs` | V | +| [532ac90e08af](https://github.com/aurora-silicon/linux/commit/532ac90e08affc3bbcf59e273e00f1573b7a05d0) | Janne Grunau | drm/asahi: Avoid variable/field ref shadowing in pin-init | Not applicable — LEGACY; `drivers/gpu/drm/asahi/initdata.rs` | V | +| [253d85ab9854](https://github.com/aurora-silicon/linux/commit/253d85ab98543f838ecc6be70edb7a43d966d505) | Sasha Finkelstein | drm/asahi: starlight-debug | Not applicable — LEGACY; `drivers/gpu/drm/asahi/gpu.rs` | V | +| [83526ea43d4f](https://github.com/aurora-silicon/linux/commit/83526ea43d4fffdb10a3dff36a37a587ffaa6c14) | Janne Grunau | drm/asahi: workqueue: Drop completed work items | Not applicable — LEGACY; `drivers/gpu/drm/asahi/workqueue.rs` | V | +| [b1e32124e9e9](https://github.com/aurora-silicon/linux/commit/b1e32124e9e94ebd3e1345eb54f687fcfbfee509) | Janne Grunau | drm/asahi: rustfmt | Not applicable — VERIFY; `drivers/gpu/drm/asahi/mmu.rs` | V | +| [8508e5105997](https://github.com/aurora-silicon/linux/commit/8508e5105997a43be75e8a3ffe0c3d8ddc00155c), [e092d51bc9d7](https://github.com/aurora-silicon/linux/commit/e092d51bc9d773f7058fe578515c5f915c0adf2c) | Yureka Lilian | pmdomain: add pmp-report offsets for t6030,t8122 | Not applicable — G15; `drivers/pmdomain/apple/pmp-report.c` | V | +| [357978884c89](https://github.com/aurora-silicon/linux/commit/357978884c895e5fc35af84fed8adf0583bc3353), [a89f7da611ad](https://github.com/aurora-silicon/linux/commit/a89f7da611ade4b9d5f40dd45fe5b41d059e3399) | Yureka Lilian | arm64: dts: apple: add minimal MacBook Neo device tree | Not applicable — PERIPH | V | +| [dfe1431f40cf](https://github.com/aurora-silicon/linux/commit/dfe1431f40cf15ec4aebd1864b4c69707b3c43c2), [b796a047ec25](https://github.com/aurora-silicon/linux/commit/b796a047ec253074e2cd6b5f38a8f19949b50271), [9359ea48b718](https://github.com/aurora-silicon/linux/commit/9359ea48b7184cdee39b7c76b5f0d4a923807cf3) | Ryan Murray | arm64: dts: apple: Add T8140/J700 device trees with audio and camera | Not applicable — PERIPH | V | +| [0d31fa4418ec](https://github.com/aurora-silicon/linux/commit/0d31fa4418ec7c18ab984d6cfb0c4d10b80a0af1), [bffb1ed450d5](https://github.com/aurora-silicon/linux/commit/bffb1ed450d58d0c19f9e312c8b0c2314b9f1c53), [d575cca893bc](https://github.com/aurora-silicon/linux/commit/d575cca893bce9bedb5375be6bb8c209991faec6) | Ryan Murray | arm64: dts: apple: t8140: Add SPMI controller and PMIC NVMEM cells | Not applicable — PERIPH | V | +| [780ab337fd92](https://github.com/aurora-silicon/linux/commit/780ab337fd92e4511f4e4ee698820ae15d007cbc), [368b5343b9f7](https://github.com/aurora-silicon/linux/commit/368b5343b9f7c69683ee5a0c5239a6f96e8177fd), [c3874394cec6](https://github.com/aurora-silicon/linux/commit/c3874394cec613afc6b5c8615eae5cdd013e558c) | Ryan Murray | arm64: dts: apple: t8140: Add SMC node | Not applicable — PERIPH | V | +| [53296a0f9b6e](https://github.com/aurora-silicon/linux/commit/53296a0f9b6eae07ebce8a55c8b16309d87b2656), [985ee0213ef3](https://github.com/aurora-silicon/linux/commit/985ee0213ef3154c4d8c9763157f9620a7ce0243), [b8850ad18ff8](https://github.com/aurora-silicon/linux/commit/b8850ad18ff8d0a489c810fdafece6b8349f8694) | Ryan Murray | arm64: dts: apple: t8140: Add ANS2 NVMe controller | Not applicable — PERIPH | V | +| [4767bc0b221c](https://github.com/aurora-silicon/linux/commit/4767bc0b221cce5fb0c20dbacffe6f6bca7bc4a0), [5b9145de86c1](https://github.com/aurora-silicon/linux/commit/5b9145de86c13364ed3b9d465334674e7dc87083), [0b9461d4be5a](https://github.com/aurora-silicon/linux/commit/0b9461d4be5a77c0c74138900b8b92a7e193f8bb) | Ryan Murray | arm64: dts: apple: t8140-j700: Name the ALS calibration file | Not applicable — PERIPH | V | +| [2b45c6011879](https://github.com/aurora-silicon/linux/commit/2b45c601187962fca0b9289fdb1408e36ef5f838), [613b821cb4bb](https://github.com/aurora-silicon/linux/commit/613b821cb4bb48ccb999f75c4d5815f094f095be), [aefc56975b57](https://github.com/aurora-silicon/linux/commit/aefc56975b57d0e07a4a3e92d7e7af85f0a45cb4) | Ryan Murray | arm64: dts: apple: t8140-pmgr: Add ATC0 and nub SPMI A0 power domains | Not applicable — PERIPH | V | +| [e5992bd6f02c](https://github.com/aurora-silicon/linux/commit/e5992bd6f02c42e2e7404be54a41d8acaf685bb4), [dc6bc1f1613f](https://github.com/aurora-silicon/linux/commit/dc6bc1f1613f7e836b9514f40c07cb424622fca1), [0e335bcc4a79](https://github.com/aurora-silicon/linux/commit/0e335bcc4a799cc546ff5a6e9d5cdde644a53eea) | Ryan Murray | arm64: dts: apple: t8140: Add the MTP coprocessor and DockChannel HID nodes | Not applicable — PERIPH | V | +| [601fe7359e94](https://github.com/aurora-silicon/linux/commit/601fe7359e94f9f527487f2d1725cbfad8ea1ff2), [2d6ae0d0f507](https://github.com/aurora-silicon/linux/commit/2d6ae0d0f5073b6e29cb970926855de13d24a770), [895980a58c46](https://github.com/aurora-silicon/linux/commit/895980a58c46a6a1ec2a4e2bb2d5b9e815d4b349) | Ryan Murray | arm64: dts: apple: t8140: Add the USB-C controller, ATC PHY and SPMI A0 bus | Not applicable — PERIPH | V | +| [addbb17c2a81](https://github.com/aurora-silicon/linux/commit/addbb17c2a810bcaa1d68f6f9bc8e7fb401a508a), [7d1387799cba](https://github.com/aurora-silicon/linux/commit/7d1387799cba4442ba7ea99a95a962535cf26fc2), [7ded6b918bd3](https://github.com/aurora-silicon/linux/commit/7ded6b918bd3436fa2dc81b8b65f336bbe66ae85) | Ryan Murray | arm64: dts: apple: t8140-j700: Enable the keyboard and trackpad | Not applicable — PERIPH | V | +| [70f40a78d8ea](https://github.com/aurora-silicon/linux/commit/70f40a78d8ea8db8b300d3d084b4228b3e172f19), [c94e793fea2a](https://github.com/aurora-silicon/linux/commit/c94e793fea2ad398c3aade3d3649ebc377189ec2), [b8f170b9347b](https://github.com/aurora-silicon/linux/commit/b8f170b9347ba943a11a30f6642a9532758965e4) | Ryan Murray | arm64: dts: apple: t8140-j700: Enable the USB-C ports | Not applicable — PERIPH | V | +| [ea013658bf93](https://github.com/aurora-silicon/linux/commit/ea013658bf93d944617d01034cd7a5a3ece105a8), [e5a7b83611cc](https://github.com/aurora-silicon/linux/commit/e5a7b83611ccc434923524ea7bfb27f4e9c2a2ec), [09b788437876](https://github.com/aurora-silicon/linux/commit/09b788437876d50bf0d13d0d8cf7bfe9487a934b) | Ryan Murray | arm64: dts: apple: t8140-j700: Enable the USB DARTs | Not applicable — PERIPH | V | +| [a97766963d24](https://github.com/aurora-silicon/linux/commit/a97766963d247d039cd76be83fd896b895e1ad2b), [e499013c56ad](https://github.com/aurora-silicon/linux/commit/e499013c56ad475dd45866c64e28dd33f95235c3), [d880e057bc1e](https://github.com/aurora-silicon/linux/commit/d880e057bc1e250d54a0b7dc2db23928f802a1d0) | Ryan Murray | arm64: dts: apple: t8140: Add the SMC RTC | Not applicable — PERIPH | V | +| [50e24690d43e](https://github.com/aurora-silicon/linux/commit/50e24690d43e525c1b2b217aa828a18e4c6503b9), [21ab6edf3da3](https://github.com/aurora-silicon/linux/commit/21ab6edf3da3111ed771155a9dbd2c83118cd0dc), [e90d32748ea7](https://github.com/aurora-silicon/linux/commit/e90d32748ea7857d242afc714365949e9ad86a3b) | Ryan Murray | arm64: dts: apple: t8140-j700: Describe the remaining SMC temperatures | Not applicable — PERIPH | V | +| [2cb796ddf423](https://github.com/aurora-silicon/linux/commit/2cb796ddf423b6d76e9d2ab1231f0b48ddbc2614), [e8d16b77d690](https://github.com/aurora-silicon/linux/commit/e8d16b77d6901f18a468d3d1b636990aa97f89cd), [23b613dfe8e7](https://github.com/aurora-silicon/linux/commit/23b613dfe8e757e1b599dc7cdfb7f9717687dc21) | Ryan Murray | arm64: dts: apple: t8140-j700: Describe the SMC current, power and voltage rails | Not applicable — PERIPH | V | +| [0cd1a28f2113](https://github.com/aurora-silicon/linux/commit/0cd1a28f2113c72a53052fb2733c2aced2f54476), [c2845ec53a91](https://github.com/aurora-silicon/linux/commit/c2845ec53a915dcadc4e68bbd59f97ca0764ddd6) | DjDeveloperr | iommu: apple-dart: support tunneled PCIe-C DMA aliases | Not applicable — TUNNEL; `drivers/iommu/Kconfig` | V | +| [6ff009379755](https://github.com/aurora-silicon/linux/commit/6ff00937975526780a22aa763211ba612f042997), [ee60f6cdcff8](https://github.com/aurora-silicon/linux/commit/ee60f6cdcff8561bff5ec48b10ba2a536a23ed9f) | DjDeveloperr | iommu: apple-dart: support retained PCIe-C tunnel domains | Not applicable — TUNNEL; `drivers/iommu/apple-dart.c` | V | +| [05ae2b2dfc28](https://github.com/aurora-silicon/linux/commit/05ae2b2dfc2853d7ae0f797d6299661e957116f3), [5987e8a08203](https://github.com/aurora-silicon/linux/commit/5987e8a08203974fe539aed3ce2de8ad5a3990a7) | Ryan Murray | arm64: dts: apple: t8140: Describe observed behaviour, not other implementations | Not applicable — VERIFY | V | +| [61f979fc1825](https://github.com/aurora-silicon/linux/commit/61f979fc18256a03db72933dde7f356a44f31eb2), [bf3cb50f012f](https://github.com/aurora-silicon/linux/commit/bf3cb50f012f9ecaeb465e861d8e896e580a69a0), [4fa6c2b053a6](https://github.com/aurora-silicon/linux/commit/4fa6c2b053a65088ca20641480fd9ed8da55716e) | Ace | arm64: dts: apple: Add diagnostic J700 PCIe root-port topology | Not applicable — PERIPH | V | +| [c309c0ebb058](https://github.com/aurora-silicon/linux/commit/c309c0ebb058bf6cd1df2503d121ace539448ecc), [5a4974fde407](https://github.com/aurora-silicon/linux/commit/5a4974fde40748a151a4c23c0df730f1148bc2df) | Ace | arm64: dts: apple: t8140-j700: enable bounded PCIe endpoint bootstrap and enumeration | Not applicable — PERIPH | V | +| [56e442c7cbbe](https://github.com/aurora-silicon/linux/commit/56e442c7cbbe49c2613a5209fbcf772e90bec10e), [716b60de9620](https://github.com/aurora-silicon/linux/commit/716b60de96207e43d3c06eeb9c424f147a5d16a3), [8a03c5341e27](https://github.com/aurora-silicon/linux/commit/8a03c5341e27e9066e15f924e88aa44f848b5294) | DjDeveloperr | arm64: dts: apple: enable AVD on T8140 | Not applicable — PERIPH | V | +| [2b9ef0954320](https://github.com/aurora-silicon/linux/commit/2b9ef0954320320edbee5b75e742176aef3ee8b3), [6f57627a710e](https://github.com/aurora-silicon/linux/commit/6f57627a710e655f30ec73ece845d18c52663d78), [4088c3b1854c](https://github.com/aurora-silicon/linux/commit/4088c3b1854cdbaf106d8bfe72510551bf9fd43c) | Ryan Murray | arm64: dts: apple: t8140: add disabled display power domains | Not applicable — PERIPH | V | +| [7e14b8e9e9a2](https://github.com/aurora-silicon/linux/commit/7e14b8e9e9a2bcb983725fc9a0cb3c016a5ed12b), [1b05c65c4276](https://github.com/aurora-silicon/linux/commit/1b05c65c42763d955c7ca69ef849d6bf5fac7355), [004200ec1cc8](https://github.com/aurora-silicon/linux/commit/004200ec1cc8005fa692d797d2c5fb009ec9e05a) | Ryan Murray | arm64: dts: apple: t8140: add disabled internal display DARTs | Not applicable — PERIPH | V | +| [d7de8bb51e4f](https://github.com/aurora-silicon/linux/commit/d7de8bb51e4f654b1c1d33c8cabd3290a9e231ea), [0c29d4d4a4ea](https://github.com/aurora-silicon/linux/commit/0c29d4d4a4ea28e024596f605676dc0d1d1e3ba0), [d2f162f6d7c2](https://github.com/aurora-silicon/linux/commit/d2f162f6d7c24ed783657112086f7b56e1fb0cbe) | Ryan Murray | arm64: dts: apple: t8140: add disabled external display DARTs | Not applicable — PERIPH | V | +| [7a28ede30a1f](https://github.com/aurora-silicon/linux/commit/7a28ede30a1ff3894d8f2407ba657d51e8d9bd95), [042b2a224bf6](https://github.com/aurora-silicon/linux/commit/042b2a224bf671bacb56b6e5345b3eb399c7ec02), [aec8cb1999f7](https://github.com/aurora-silicon/linux/commit/aec8cb1999f731bbe1895509dbcc6e2fed977348) | Ryan Murray | arm64: dts: apple: t8140: Add the SEP, its DART and mailbox, and SPI2 | Not applicable — PERIPH | V | +| [07029412ae8d](https://github.com/aurora-silicon/linux/commit/07029412ae8d5832c1323d3eeb8a08b09f7b5be0), [1eb7bfe18b01](https://github.com/aurora-silicon/linux/commit/1eb7bfe18b01c0a088dd8e25a72ee5af3a09531a), [29987787b923](https://github.com/aurora-silicon/linux/commit/29987787b9238b15b41b92da87e5fcbf8a571e8c) | Ryan Murray | arm64: dts: apple: t8140-j700: Enable the SEP and the Touch ID sensor | Not applicable — PERIPH | V | +| [7d84216f74e8](https://github.com/aurora-silicon/linux/commit/7d84216f74e8e9e5827eabc9f173524ce5520c9b), [6049af950206](https://github.com/aurora-silicon/linux/commit/6049af950206644d95b8cd3ca11fe0722ff71257), [52f1ba512244](https://github.com/aurora-silicon/linux/commit/52f1ba51224431d256250c69784e1996f6ee63cc) | Acelogic | arm64: dts: apple: wire the J700 AVE encoder | Not applicable — PERIPH | V | +| [5f1fbb7bd670](https://github.com/aurora-silicon/linux/commit/5f1fbb7bd670ca8a26c303594f9ed54535e51f02), [519558a43644](https://github.com/aurora-silicon/linux/commit/519558a43644d79c2cc15135554748ffe6fadc81), [f898b116beaa](https://github.com/aurora-silicon/linux/commit/f898b116beaaab7add264a0f074b21743875d3ae) | Ryan Murray | arm64: dts: apple: Describe additional T8140 peripheral power states | Not applicable — PERIPH | V | +| [555932c62f03](https://github.com/aurora-silicon/linux/commit/555932c62f03ec2833c5db9b39e2d2dc956501cb), [ed2ef28706b7](https://github.com/aurora-silicon/linux/commit/ed2ef28706b7c2ed13aef2f90eb7191b4e34338e), [f75a1041fca4](https://github.com/aurora-silicon/linux/commit/f75a1041fca487774f29b133f736296f4d73a4e3) | Ryan Murray | arm64: dts: apple: Wire up remaining supported J700 peripherals | Not applicable — PERIPH | V | +| [a3d7465380ff](https://github.com/aurora-silicon/linux/commit/a3d7465380ffbf5f9cb4eddb550fabfdd71d3a17), [49fdcbcee4f4](https://github.com/aurora-silicon/linux/commit/49fdcbcee4f4569e6b65f7912e05502a38299743) | Ryan Murray | arm64: dts: apple: describe the J700 PMU alarm function | Not applicable — PERIPH | V | +| [d810cb82e82d](https://github.com/aurora-silicon/linux/commit/d810cb82e82de69013754b6031d00a83a6d15e41), [4e0543ee6caf](https://github.com/aurora-silicon/linux/commit/4e0543ee6cafb4250aaf332f894773412b86dfe2), [b9a339703e95](https://github.com/aurora-silicon/linux/commit/b9a339703e95a6a65e0e4515caa4c4f6afb0d9ce) | Ryan Murray | arm64: dts: apple: describe the disabled T8140 Neural Engine | Not applicable — PERIPH | V | +| [19f7cbdd84fe](https://github.com/aurora-silicon/linux/commit/19f7cbdd84fef12ca56aad9b654b46465cc2e198), [6b57d5423048](https://github.com/aurora-silicon/linux/commit/6b57d5423048ac4c70cd99d7473494ac3d37b2db), [93bc234385f5](https://github.com/aurora-silicon/linux/commit/93bc234385f5bf51b276e9d2e5abe8280e2ff92c) | Ryan Murray | arm64: dts: apple: t8140: Leave display coprocessor power to firmware | Not applicable — PERIPH | V | +| [0f5548d142b1](https://github.com/aurora-silicon/linux/commit/0f5548d142b1d73c44d270c91d5c359272dae499), [7c3c5ee5a070](https://github.com/aurora-silicon/linux/commit/7c3c5ee5a0702bb5b9f0ae4cde6b837cbafa3485), [9e6acbbd21ba](https://github.com/aurora-silicon/linux/commit/9e6acbbd21ba92409dd4f24074532b0bad997792) | Ryan Murray | arm64: dts: apple: t8140: Describe the internal display coprocessor | Not applicable — PERIPH | V | +| [100ff1ebedd3](https://github.com/aurora-silicon/linux/commit/100ff1ebedd337758a4ff3ba3fd3f2f46a9f5be9), [45ae8211efea](https://github.com/aurora-silicon/linux/commit/45ae8211efeafe3bff64b569370942c5218f1fec), [061370b4c70f](https://github.com/aurora-silicon/linux/commit/061370b4c70fd888ea6dc520a8988208f4aebca8) | Ryan Murray | arm64: dts: apple: t8140: Hand the J700 panel over to the DCP | Not applicable — PERIPH | V | +| [29f8091fe351](https://github.com/aurora-silicon/linux/commit/29f8091fe351a0cdb781cf18ac5748ca15feca42), [4f3906d225f4](https://github.com/aurora-silicon/linux/commit/4f3906d225f44739df672ac0b76715407f52e586), [a7fa3d8fbf56](https://github.com/aurora-silicon/linux/commit/a7fa3d8fbf5690df6553896a4f984b8cdc8069c2) | Ryan Murray | arm64: dts: apple: t8140: describe each power state once | Not applicable — PERIPH | V | +| [89c54797ae77](https://github.com/aurora-silicon/linux/commit/89c54797ae77fea8e8878a68e0b56f382cfb0409), [07969983cb1e](https://github.com/aurora-silicon/linux/commit/07969983cb1edefaec1b4b0aabb2307b0b14dacd), [953220ab6d74](https://github.com/aurora-silicon/linux/commit/953220ab6d748bd05db3854f374b701696f6c795) | Ryan Murray | arm64: dts: apple: t8140: describe the external display DARTs once | Not applicable — PERIPH | V | +| [55a5beb425ae](https://github.com/aurora-silicon/linux/commit/55a5beb425ae7c901851c2185e3219d8c0348fcb), [f0234a79d172](https://github.com/aurora-silicon/linux/commit/f0234a79d172479cb7dbcdb713daa5739ad87f74), [c80113dfaab9](https://github.com/aurora-silicon/linux/commit/c80113dfaab9e985b9f617bfde06c481fd17376c) | Ryan Murray | arm64: dts: apple: t8140-j700: give the two RTCs fixed numbers | Not applicable — PERIPH | V | +| [9e1fb240e1df](https://github.com/aurora-silicon/linux/commit/9e1fb240e1df4064a9232007daa5c8797dfbcda4), [19e2b5adfe69](https://github.com/aurora-silicon/linux/commit/19e2b5adfe6905ab2f3fff6ad1a12801d711c607), [ea044d49c76c](https://github.com/aurora-silicon/linux/commit/ea044d49c76c18079848672c2779c7521a6b8fc3) | Ryan Murray | arm64: dts: apple: t8140: Keep the GPIO power state on for the SEP | Not applicable — PERIPH | V | +| [4334bbd7162a](https://github.com/aurora-silicon/linux/commit/4334bbd7162af085ed1c8c7b63859bed8c6646ef), [f1c076a9bf8a](https://github.com/aurora-silicon/linux/commit/f1c076a9bf8a850bec0140fb3e91a9607aadcf4e), [30812f491575](https://github.com/aurora-silicon/linux/commit/30812f491575ede27e5be059fd6afc4e985166f1) | DjDeveloperr | arm64: dts: apple: t8140-j700: keep the DCS rail out of thermal control | Not applicable — DCSOFF | P,R | +| [baacaf604632](https://github.com/aurora-silicon/linux/commit/baacaf60463212b2270f0ad0b50cdabe4e27ab6d), [071651bd5c30](https://github.com/aurora-silicon/linux/commit/071651bd5c30330ecb9980c3c1f2cc4e2f04d1a6), [40a9c929c55f](https://github.com/aurora-silicon/linux/commit/40a9c929c55f2b13c39e732fc2ae3f10e1506634) | Ryan Murray | arm64: dts: apple: describe disabled J700 external resources | Not applicable — PERIPH | V | +| [13af1cb29b95](https://github.com/aurora-silicon/linux/commit/13af1cb29b95b1fc260870868d3c54a596cc483d), [f284458cdfc3](https://github.com/aurora-silicon/linux/commit/f284458cdfc33d2746bb4ede9174adc3afaa2f7f) | Ryan Murray | arm64: dts: apple: t8140: describe the external binding the display master recognizes | Not applicable — PERIPH | V | +| [a7f9b86ea187](https://github.com/aurora-silicon/linux/commit/a7f9b86ea18778f313b73cd174debe54e4dbc09c), [cffe838aca8e](https://github.com/aurora-silicon/linux/commit/cffe838aca8e275526730bd6f3fee5ebcaec0c47), [205a56d33cb0](https://github.com/aurora-silicon/linux/commit/205a56d33cb07184d7dfe17ab8aba0996cff3efe) | Ryan Murray | arm64: dts: apple: retain the inherited external display supply | Not applicable — PERIPH | V | +| [328dacb9d1ad](https://github.com/aurora-silicon/linux/commit/328dacb9d1adcbc1be84e8dae660c72bc12a3005), [3d16013f9795](https://github.com/aurora-silicon/linux/commit/3d16013f979572f7f7f6d1573f2af739e8ac25b4), [2e6003f6db07](https://github.com/aurora-silicon/linux/commit/2e6003f6db07b0e0a6b2bf2b977753bee47556b0) | Ryan Murray | arm64: dts: apple: add an explicit external display trial tree | Not applicable — PERIPH | V | +| [6b82a41053b8](https://github.com/aurora-silicon/linux/commit/6b82a41053b87c8bd0917f72f71778293aa7f276), [70cce5cd20ef](https://github.com/aurora-silicon/linux/commit/70cce5cd20efb8a71d49563b45481c32e0de23a6), [0a0d25f4a99b](https://github.com/aurora-silicon/linux/commit/0a0d25f4a99b66a08a2ab4a22314782aaad7b3e2) | DjDeveloperr | arm64: dts: apple: t8140-j700: drive the SPI NOR on a single data line | Not applicable — PERIPH | V | +| [d56b7ce044f2](https://github.com/aurora-silicon/linux/commit/d56b7ce044f274b153a3b1f9192d3fd4c7cb2d85), [df94160d7567](https://github.com/aurora-silicon/linux/commit/df94160d756756e14d4bc818fe0652bcdb9adc30) | DjDeveloperr | arm64: dts: apple: t8140-j700: enable the validated external display profile by default | Not applicable — PERIPH | V | +| [f9e24b02bc85](https://github.com/aurora-silicon/linux/commit/f9e24b02bc8547acba1453cf462f0f210e707ba7), [be5f3162c985](https://github.com/aurora-silicon/linux/commit/be5f3162c9854d6b5ea8ca9e8f29f764b2c6251b) | Eryk Wieliczko | arm64: dts: apple: t8140: mark firmware-owned power domains | Not applicable — PERIPH | V | +| [7df682a70733](https://github.com/aurora-silicon/linux/commit/7df682a707337025aad3c093252ea7f8504e8345), [b0714e8783f6](https://github.com/aurora-silicon/linux/commit/b0714e8783f685f213c15890c7942c1c03d6c1e3) | Chris Kearney | PCI: apple: enumerate Thunderbolt PCIe on t8103 | Not applicable — LEGACY; `drivers/iommu/apple-dart.c` | V | +| [4c02ff022799](https://github.com/aurora-silicon/linux/commit/4c02ff022799be8aef47108a61671a6a6cd86038), [b388a175a612](https://github.com/aurora-silicon/linux/commit/b388a175a612be6c5d10056c6a10a7219030272d) | Chris Kearney | PCI: apple: finish tunneled IOMMU commands before the port clock stops | Not applicable — TUNNEL; `drivers/iommu/apple-dart.c` | V | +| [9e8a900fee27](https://github.com/aurora-silicon/linux/commit/9e8a900fee275872aa1019df27a4944b73c8940a), [a2b13c1f0369](https://github.com/aurora-silicon/linux/commit/a2b13c1f03696ae69101c8ed51b8901d32dffcd4), [5a7b76f31c3a](https://github.com/aurora-silicon/linux/commit/5a7b76f31c3a8ccdc48fd586e5b646fa0c49499d) | Chris Kearney | iommu/apple-dart: enforce command gating during tunnel teardown | Not applicable — TUNNEL; `drivers/iommu/apple-dart-test.c` | V | +| [39e90a052954](https://github.com/aurora-silicon/linux/commit/39e90a052954d1305a45ca0065eda42ec4aff639), [d15623fd3261](https://github.com/aurora-silicon/linux/commit/d15623fd32614870a9eb62d25afdd823a39bcde7), [65e3e0ed688a](https://github.com/aurora-silicon/linux/commit/65e3e0ed688a5a933a60d92ca79b6136c357b876) | Chris Kearney | iommu/apple-dart: report tunnel command-resume failures | Not applicable — TUNNEL; `drivers/iommu/apple-dart-test.c` | V | +| [3a94483e259c](https://github.com/aurora-silicon/linux/commit/3a94483e259cbc8d0aab638eaa4af5b72c4db6e6), [6e0c3cdf7855](https://github.com/aurora-silicon/linux/commit/6e0c3cdf78550987513b0d4c15ffec90b442b787), [64dfa24ca989](https://github.com/aurora-silicon/linux/commit/64dfa24ca989fec06367a7199164a70de7d99e35) | Chris Kearney | PCI: apple: describe tunneled PM ordering by Linux dependencies | Not applicable — TUNNEL; `drivers/iommu/apple-dart.c` | V | +| [5d4dd51e2769](https://github.com/aurora-silicon/linux/commit/5d4dd51e27695b5d9570e56b050e3574dfc1fd38), [e7292daedcaa](https://github.com/aurora-silicon/linux/commit/e7292daedcaa2186e5b8b484343dc3ec239b2805), [615be505e22c](https://github.com/aurora-silicon/linux/commit/615be505e22c592c1a87c193b22c58dc4c064840) | Chris Kearney | thunderbolt: restore M1 tunneled PCIe state and recover late link loss | Not applicable — TUNNEL; `drivers/iommu/apple-dart-test.c` | V | +| [95caa2cf7ac7](https://github.com/aurora-silicon/linux/commit/95caa2cf7ac7244080527b90da0d43ec917e1513), [92f33b680514](https://github.com/aurora-silicon/linux/commit/92f33b6805149c955b543a6e2c29f88c42886f6d), [fc559d251fca](https://github.com/aurora-silicon/linux/commit/fc559d251fcac7e904607a36fb4b17770e29f239) | Acelogic | irqchip/apple-aic: use architectural guest timer masks on T8142 | Not applicable — OTHERCHIP; `drivers/irqchip/irq-apple-aic.c` | V | +| [4d1bcf62c1af](https://github.com/aurora-silicon/linux/commit/4d1bcf62c1af6fa29a32e94d821f4174098cbd29), [4ae1f9604a1a](https://github.com/aurora-silicon/linux/commit/4ae1f9604a1aa503dd1b452578bf694dbadaca25), [40bc3b97c1d6](https://github.com/aurora-silicon/linux/commit/40bc3b97c1d6a75ff5f8af9bc75322388a5381e1) | DjDeveloperr | soc: apple: rtkit-helper: adopt a running T8140 MTP session | Not applicable — PERIPH; `drivers/soc/apple/rtkit-helper.c` | V | +| [ad7993e04cd2](https://github.com/aurora-silicon/linux/commit/ad7993e04cd2e39c4fe011740268a537868bb8c0), [58a792863c4f](https://github.com/aurora-silicon/linux/commit/58a792863c4fdd79342e5e83ece8f652993084ab), [80cabfefcdc5](https://github.com/aurora-silicon/linux/commit/80cabfefcdc52036a7902268ff42f2a9370fe5c2) | Acelogic | cpufreq: apple: Add bounded T8152 E and P/M performance domains | Not applicable — OTHERCHIP; `drivers/cpufreq/apple-soc-cpufreq.c` | V | +| [81aefe80b586](https://github.com/aurora-silicon/linux/commit/81aefe80b586fa545b20eb90090202cde51718e7), [48f073156423](https://github.com/aurora-silicon/linux/commit/48f0731564236dc79c43bbcdadf6b05cfd807e8a), [296dbb45adff](https://github.com/aurora-silicon/linux/commit/296dbb45adff8654c6f1f8029e7b6b567f990e0b) | Acelogic | cpufreq: apple: add T8142 command-state transitions | Not applicable — OTHERCHIP; `drivers/cpufreq/apple-soc-cpufreq.c` | V | +| [4c3405d11d29](https://github.com/aurora-silicon/linux/commit/4c3405d11d29ec8a49a14bdc546cef4411153b32), [b08be27ef235](https://github.com/aurora-silicon/linux/commit/b08be27ef235dfedb3d18cf4a91a821be8a7effc) | Ryan Murray | cpufreq: apple: retain the unmanaged P-state ceiling on T8152 | Not applicable — OTHERCHIP; `drivers/cpufreq/apple-soc-cpufreq.c` | V | +| [24f4a32e8ec9](https://github.com/aurora-silicon/linux/commit/24f4a32e8ec9438b198e7ca97658154bcaedc6f7), [129471fc83b8](https://github.com/aurora-silicon/linux/commit/129471fc83b89e08275048a812779583ef197a93), [c0bd8b9fec16](https://github.com/aurora-silicon/linux/commit/c0bd8b9fec163274b9e6056ae94fe54d53b565fb), [b4ee9bd8d923](https://github.com/aurora-silicon/linux/commit/b4ee9bd8d923949b19cc22484262670948cafc57) | DjDeveloperr | drm/asahi: clarify the opening polling budget [#171](https://github.com/aurora-silicon/linux/pull/171) | Not applicable — VERIFY; `drivers/gpu/drm/asahi/g17/mod.rs` | V | +| [52f6e8610625](https://github.com/aurora-silicon/linux/commit/52f6e86106255b26e84671649c5f4acb0f434cde), [840488b0b60d](https://github.com/aurora-silicon/linux/commit/840488b0b60d3162e92f050cc59a2b3967036544), [66631bc63295](https://github.com/aurora-silicon/linux/commit/66631bc632956bb7dfd1b4fb44e48ae90a10140e), [d0aadc1e99e6](https://github.com/aurora-silicon/linux/commit/d0aadc1e99e6f11a54bbca5ee685d7c015866789) | DjDeveloperr | drm/asahi: describe the G17 word tables without their generation history [#171](https://github.com/aurora-silicon/linux/pull/171) | Not applicable — VERIFY; `drivers/gpu/drm/asahi/g17/fw/initdata.rs` | V | +| [1c771d73ac6c](https://github.com/aurora-silicon/linux/commit/1c771d73ac6c5e953be9582f1b74ccb358e6322d), [d24b1a49853f](https://github.com/aurora-silicon/linux/commit/d24b1a49853fe6949b44790761678cd2d38e623e), [a6e06b47b49d](https://github.com/aurora-silicon/linux/commit/a6e06b47b49d2666a53b5bc45ba8e0fa22e74944) | Joshua Warren | drm/asahi: Add stats_export module parameter | Not applicable — STAT; `drivers/gpu/drm/asahi/Makefile` | T | +| [3f096100f658](https://github.com/aurora-silicon/linux/commit/3f096100f658c1563dafaa0f0ca8b77ea2a87fe3), [f436b4da5480](https://github.com/aurora-silicon/linux/commit/f436b4da5480140e5ba95d68e46f1e7dd7615db8) | Ryan Murray | drm/asahi: tie stats registration to a per-device snapshot owner | Not applicable — STAT; `drivers/gpu/drm/asahi/g17/mod.rs` | T | +| [fd13851e08b6](https://github.com/aurora-silicon/linux/commit/fd13851e08b689661332ee823ad65eafd4418c1a), [c91a9274e4b2](https://github.com/aurora-silicon/linux/commit/c91a9274e4b2736d5589826b07c8dc54c0e35055) | Ryan Murray | drm/asahi: describe the G17 tiler-heap shrink policy in hardware terms | Not applicable — VERIFY; `drivers/gpu/drm/asahi/g17/buffer.rs` | V | +| [af5c7b24a0e5](https://github.com/aurora-silicon/linux/commit/af5c7b24a0e5932026c8cbab8633f2563bf761ba), [f0ec35d78e5a](https://github.com/aurora-silicon/linux/commit/f0ec35d78e5adf9988c1bc71418d0a764c8d4ba3), [337b14463d50](https://github.com/aurora-silicon/linux/commit/337b14463d5010296efb0b17dfaa11b8f7601aae) | Ryan Murray | arm64: dts: apple: t8140: Fold the trailing soc override into the soc node [#314](https://github.com/aurora-silicon/linux/pull/314) | Not applicable — PERIPH | V | +| [3cd89c634a60](https://github.com/aurora-silicon/linux/commit/3cd89c634a60071014683a27d497e46b8e2e2fc5), [f5ba7b6ee41c](https://github.com/aurora-silicon/linux/commit/f5ba7b6ee41ca4545745bfcc312fdbc0abf3e491), [6f1b4ab6f2f0](https://github.com/aurora-silicon/linux/commit/6f1b4ab6f2f036dedd104b83df774d6284ecab74) | Ryan Murray | arm64: dts: apple: t8140: Tidy the mailbox and pinctrl comments [#314](https://github.com/aurora-silicon/linux/pull/314) | Not applicable — PERIPH | V | +| [553e961d56c9](https://github.com/aurora-silicon/linux/commit/553e961d56c9ecbf6d58561c98468496955ad1d3), [38dd24cdc5a3](https://github.com/aurora-silicon/linux/commit/38dd24cdc5a3e13555afcac85f28d8ca3fc77a8e), [742366203cc8](https://github.com/aurora-silicon/linux/commit/742366203cc8a1f9356d82a43d52e2cb6fa97621) | Ryan Murray | pmdomain: apple: pmp-report: remove the raw DVFS debugfs dump [#317](https://github.com/aurora-silicon/linux/pull/317) | Not applicable — RAWDIAG; `drivers/pmdomain/apple/pmp-report.c` | O | +| [d04cbb892d26](https://github.com/aurora-silicon/linux/commit/d04cbb892d26dca250de4ff6f8c6cde5446682fb), [34754dd5bf3b](https://github.com/aurora-silicon/linux/commit/34754dd5bf3bd3d21b3409b6768af8b8fa38a045), [21c1d3f21ac4](https://github.com/aurora-silicon/linux/commit/21c1d3f21ac49f4223993ed1beefca1d6d6e89cd) | Ryan Murray | arm64: dts: apple: t8140: state why PMGR domains off at boot are always-on [#317](https://github.com/aurora-silicon/linux/pull/317) | Not applicable — PERIPH | V | +| [c3760b2f1cbd](https://github.com/aurora-silicon/linux/commit/c3760b2f1cbd4e17586ced9ac0d9ae7e8fe7d024), [732facd15036](https://github.com/aurora-silicon/linux/commit/732facd1503659e30fb096e0276e2efcdfa41ed7), [0d1799c7c179](https://github.com/aurora-silicon/linux/commit/0d1799c7c179d3b6e914eeaee59da7529e824565) | Ryan Murray | arm64: dts: apple: t8140-j700: trim measurement notes from comments [#317](https://github.com/aurora-silicon/linux/pull/317) | Not applicable — PERIPH | V | +| [ac9e0f78ef56](https://github.com/aurora-silicon/linux/commit/ac9e0f78ef560ef9d7a5c5655c7253e938b08d71), [f2928aa2ef66](https://github.com/aurora-silicon/linux/commit/f2928aa2ef662ff9045367ee9910a62a0f0b4e4b) | Ryan Murray | pmdomain: apple: pmc: Test partial enables and probe unwind | Not applicable — VERIFY; `drivers/pmdomain/apple/pmc-test.c` | V | +| [e8e0eae211f9](https://github.com/aurora-silicon/linux/commit/e8e0eae211f9d97ad777522ac8bf0c331ebf20f8), [a20499ed1321](https://github.com/aurora-silicon/linux/commit/a20499ed1321d5e5054d0f3dfd3f3a622e2938be) | Ryan Murray | soc: apple: mcc: Test partial allocation enable and cache handoff | Not applicable — VERIFY; `drivers/soc/apple/Kconfig` | V | +| [f2669637e7a6](https://github.com/aurora-silicon/linux/commit/f2669637e7a6c7e24f8eac823097156728ce64a8), [e3d59133d7a7](https://github.com/aurora-silicon/linux/commit/e3d59133d7a793ac15e3fa7328d11dafff8d8c92), [d6094473be4c](https://github.com/aurora-silicon/linux/commit/d6094473be4ceae69e8385ffb03891a22c60c3e4) | Ryan Murray | soc: apple: rtkit-helper: Terminate log messages [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — PERIPH; `drivers/soc/apple/rtkit-helper.c` | V | +| [8a83508a3870](https://github.com/aurora-silicon/linux/commit/8a83508a3870e8dd8d15ade42c759ed1be8fc9c1), [bee2492c2bac](https://github.com/aurora-silicon/linux/commit/bee2492c2bac7c4cf6022cffadd92b1e4fae2f54), [0afefd991028](https://github.com/aurora-silicon/linux/commit/0afefd9910288c1783a2756c4240dfc80ee8470f) | Ryan Murray | iommu: apple-dart: Return IOMEM_ERR_PTR() from the tunneled MMIO helper [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — TUNNEL; `drivers/iommu/apple-dart.c` | V | +| [d970f88dd819](https://github.com/aurora-silicon/linux/commit/d970f88dd819b24905808cac119647625e382303), [6b8a1244cb2c](https://github.com/aurora-silicon/linux/commit/6b8a1244cb2c8e0da6290fe794fb442b5cb9404e), [27da3a5d53ca](https://github.com/aurora-silicon/linux/commit/27da3a5d53ca11f2aeff4916d25f40308c378a07) | Eryk Wieliczko | arm64: dts: apple: t8140-j700: give the boot framebuffer its panel | Not applicable — PERIPH | V | +| [8a9127fdd1f5](https://github.com/aurora-silicon/linux/commit/8a9127fdd1f54bfe2913e7996073a994eb41ff04), [8feaf5d2fa71](https://github.com/aurora-silicon/linux/commit/8feaf5d2fa714a11984b1eeafd5dd2e076ec4af6), [748b2a25f2ca](https://github.com/aurora-silicon/linux/commit/748b2a25f2ca960017eef868e2323a27e07f0fe2) | Ryan Murray | drm/asahi: clean up the statistics Rust style | Not applicable — STAT; `drivers/gpu/drm/asahi/channel.rs` | T | +| [c987a1793e75](https://github.com/aurora-silicon/linux/commit/c987a1793e759da730ff84384a601d9d96d916d3), [37b3b1f7de85](https://github.com/aurora-silicon/linux/commit/37b3b1f7de8547eca29a4414465b5b4d705d413d) | Chris Kearney | drm/apple: wait for T6030 RTKit callbacks before a DCP is freed | Not applicable — G15; `drivers/gpu/drm/apple/dcp.c` | V | +| [9639e3803ffb](https://github.com/aurora-silicon/linux/commit/9639e3803ffb7f73c58dbbacad17a6e40b7596ac), [346fd6083ab2](https://github.com/aurora-silicon/linux/commit/346fd6083ab2d45a079fe512c6b3e68affd4cc6f) | Ryan Murray | drm/asahi: free completed commands on system_dfl_wq | Not applicable — LEGACY; `drivers/gpu/drm/asahi/workqueue.rs` | V | +| [3e6775c52948](https://github.com/aurora-silicon/linux/commit/3e6775c52948793951b618817b3715eec5f91924), [b9527e10b2b6](https://github.com/aurora-silicon/linux/commit/b9527e10b2b683dff7d4b718e9af6f93fb0a005f) | Ryan Murray | arm64: add an isolated T8140 graphics and power profile | Not applicable — PERIPH | V | +| [7f40cd79f058](https://github.com/aurora-silicon/linux/commit/7f40cd79f0582c4ca1abbad36f1e923d8163779f), [58db6bfedcec](https://github.com/aurora-silicon/linux/commit/58db6bfedcec7cb4a2be3843c5d3665b4680553e) | Chris Kearney | soc: apple: test every PMP integer replacement width | Not applicable — VERIFY; `drivers/soc/apple/pmp_bootargs.rs` | V | +| [0a1f130cd042](https://github.com/aurora-silicon/linux/commit/0a1f130cd0427037fc94a4ad39e54a3c66d5dbac), [f11518fc495d](https://github.com/aurora-silicon/linux/commit/f11518fc495d51dc3c5d8f67cc10d7cb02a4d739) | Eryk Wieliczko | pmdomain: apple: add the GFX TVM gate | Not applicable — G16; `drivers/pmdomain/apple/Makefile` | V | +| [4e7c8a74e4a5](https://github.com/aurora-silicon/linux/commit/4e7c8a74e4a5ef03a1d06b5cd73f01c68b75a63d), [fe7d063e4cee](https://github.com/aurora-silicon/linux/commit/fe7d063e4cee437b5196c438f9f19a6ad7e22afc) | Satya Benson | pmdomain: apple: gfx-tvm: accept the T8122 | Not applicable — G15; `drivers/pmdomain/apple/gfx-tvm.c` | V | +| [460c0a77ca86](https://github.com/aurora-silicon/linux/commit/460c0a77ca860aef48d7ed449d65a878cffab9ed), [c8aed729e9bf](https://github.com/aurora-silicon/linux/commit/c8aed729e9bf8d23f0adabff999aa16d34201d76) | Joshua Warren | gpu: let the firmware preempt compute work on G14X | Not applicable — LEGACY; `drivers/gpu/drm/asahi/asahi_core.rs` | V | +| [80797e4f1b2b](https://github.com/aurora-silicon/linux/commit/80797e4f1b2bbde71964ea2253553d2c2dbbe379), [9c7ab8b412a5](https://github.com/aurora-silicon/linux/commit/9c7ab8b412a50123bf7dbc642368aa7f930d2d41) | Ryan Murray | drm/asahi: gate compile-only M3 backends before resource setup | Not applicable — G15; `drivers/gpu/drm/asahi/asahi_core.rs` | V | +| [700bfca794e5](https://github.com/aurora-silicon/linux/commit/700bfca794e50093c6e4d2b4231aca6252451a41), [860d185ccc57](https://github.com/aurora-silicon/linux/commit/860d185ccc575a47df4962cb1cf32da8f27ff35f) | Chris Kearney | drm/asahi: add the M3 (G15S) GPU runtime (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_compute_layout.rs` | V | +| [01680f395e55](https://github.com/aurora-silicon/linux/commit/01680f395e557e93b331ad510fbf255e6622c2a0), [a846942e367d](https://github.com/aurora-silicon/linux/commit/a846942e367dade6002d2d456ae6249c50d04d39) | Eryk Wieliczko | drm/asahi: share M3 M4 host retirement accounting (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/agx_host_progress.rs` | V | +| [9593d5f2498f](https://github.com/aurora-silicon/linux/commit/9593d5f2498f3243100c828cc7bbf7ecdaddb861), [4bcb6f1acab4](https://github.com/aurora-silicon/linux/commit/4bcb6f1acab42a1772bf62aaae00c5fb1f6007cd) | Eryk Wieliczko | drm/asahi: account M3 GEM and coherent firmware backing lifetimes (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/agx_memory_stats.rs` | V | +| [bba3fa8f4562](https://github.com/aurora-silicon/linux/commit/bba3fa8f4562761864f8f57b1a461bcd50ec3d64), [3e963ea31aca](https://github.com/aurora-silicon/linux/commit/3e963ea31acaad3e4a78dc137f12339fa8ebb5ee) | Chris Kearney | drm/asahi: expose bounded native retirement timing snapshots (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/agx_timing_stats.rs` | V | +| [6d24d16ce24c](https://github.com/aurora-silicon/linux/commit/6d24d16ce24c8af1902191781e1255807ccd8aae), [1324a0258bb0](https://github.com/aurora-silicon/linux/commit/1324a0258bb0d0987993a3c34c2a74b0f2093cce) | Chris Kearney | drm/asahi: expose host-tracked pending GPU activity separately from completions (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/agx_host_progress.rs` | V | +| [a4bbcd4baf93](https://github.com/aurora-silicon/linux/commit/a4bbcd4baf933a15e5bc75b2c7c5603d8b1b6998), [8faa4de3948b](https://github.com/aurora-silicon/linux/commit/8faa4de3948b0795c068eeeb0456a6a7dfc45077) | Chris Kearney | drm/asahi: avoid repeated activity fences while GPU work is pending (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/agx_host_progress.rs` | V | +| [2397ac8a0861](https://github.com/aurora-silicon/linux/commit/2397ac8a086119bba90f6fc0d8d430e035d27f30), [b62d349e0fc6](https://github.com/aurora-silicon/linux/commit/b62d349e0fc6e160b9dabd167166a2d87ecfdbce) | Eryk Wieliczko | drm/asahi: remove M3 lifetime submission cap with wrapping wire stamps (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_timeline.rs` | V | +| [ce78a5b0c517](https://github.com/aurora-silicon/linux/commit/ce78a5b0c5173dbb9b5d7d0017488c43a07f222f), [578aa862e357](https://github.com/aurora-silicon/linux/commit/578aa862e357f0f61e18dc8e26181eeb5c0b9d7b) | Eryk Wieliczko | drm/asahi: preserve nonzero M3 stamps and dependencies through rollover (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_timeline.rs` | V | +| [646db65b2128](https://github.com/aurora-silicon/linux/commit/646db65b212826a29bd969332760722077926d61), [1d6a90b4117f](https://github.com/aurora-silicon/linux/commit/1d6a90b4117fe9cb84aa7863f491d18d37fcf0c5) | Chris Kearney | drm/asahi: remove unused backend helpers and narrow layout visibility (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_compute_storage.rs` | V | +| [a91573de916a](https://github.com/aurora-silicon/linux/commit/a91573de916a12ddc980c75d977567e57725e449), [f939d03f703f](https://github.com/aurora-silicon/linux/commit/f939d03f703fe760f413a87d435769b3aa970d21) | Chris Kearney | treewide: apple: reword M3 comments and messages as plain technical statements (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_compute_sequence.rs` | V | +| [997a7dba8158](https://github.com/aurora-silicon/linux/commit/997a7dba81582c94d1bdcc26aaae6031a486390c), [262009a6c7f9](https://github.com/aurora-silicon/linux/commit/262009a6c7f9dc7b6ca4abb158b669870a6180c5) | Chris Kearney | drm/asahi: select M3 runtime configuration per SoC (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_init_storage.rs` | V | +| [a8065eb937ce](https://github.com/aurora-silicon/linux/commit/a8065eb937cee67032a68207827419c515e52e11), [d16a8c32954b](https://github.com/aurora-silicon/linux/commit/d16a8c32954b412bb3bf9f61a7c48a68dc9303b1) | Chris Kearney | drm/asahi: m3: derive runtime IO maps from IO mappings (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_init_storage.rs` | V | +| [c3d978ed0793](https://github.com/aurora-silicon/linux/commit/c3d978ed0793058248d9994e868f5c1043cc18f7), [7f04f81b5a19](https://github.com/aurora-silicon/linux/commit/7f04f81b5a191b8ff9b1a3d454656af3993b47be) | Satya Benson | drm/asahi: m3: size render TPC and preemption storage by cluster count (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_pass_layout.rs` | V | +| [4287d2b5166b](https://github.com/aurora-silicon/linux/commit/4287d2b5166bb92f791ae652dba8361cfc732aeb), [d94dcc88b26c](https://github.com/aurora-silicon/linux/commit/d94dcc88b26ca0e964b5298ee1298ed068cbf6eb) | Satya Benson | drm/asahi: m3: publish queued packets from the completion worker (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_batch_policy.rs` | V | +| [6046b500e687](https://github.com/aurora-silicon/linux/commit/6046b500e6872cfe56d53cb65e9b0619cb1f066c), [6787ae6d5717](https://github.com/aurora-silicon/linux/commit/6787ae6d57170ef75c454f6f744d4c4d557cb9a7) | Satya Benson | drm/asahi: m3: bound overlapping render batches to free pass slots (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_batch_policy.rs` | V | +| [4ea0ea7df543](https://github.com/aurora-silicon/linux/commit/4ea0ea7df5437fad2829ec8eb68be6e9f3544591), [ce9df27fd4b6](https://github.com/aurora-silicon/linux/commit/ce9df27fd4b67bd47194655b57efecdec2321081) | Satya Benson | drm/asahi: m3: budget render coalescing by measured per-VM GPU time (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_batch_policy.rs` | V | +| [b877dfb7e3e8](https://github.com/aurora-silicon/linux/commit/b877dfb7e3e89e31fef97057c68581660b6f256e), [d57591e332b4](https://github.com/aurora-silicon/linux/commit/d57591e332b49cafc12aaf8af77845525dda760b) | Chris Kearney | drm/asahi: m3: compare waiting urgency with the active render batch (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_batch_policy.rs` | V | +| [f9ede2dda5dd](https://github.com/aurora-silicon/linux/commit/f9ede2dda5dd7a0fa8ce0e1143724e7be01b31a0), [7c912ffa2635](https://github.com/aurora-silicon/linux/commit/7c912ffa2635dd08673a43c09442a143a39b624d) | Chris Kearney | drm/asahi: retain full firmware-log payloads and use ring entry sizes (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_init_layout.rs` | V | +| [02e89516c254](https://github.com/aurora-silicon/linux/commit/02e89516c254d55b4ae686ccae460af8ddd30901), [e2bbe50fc3fd](https://github.com/aurora-silicon/linux/commit/e2bbe50fc3fd42e5dfbd74e5aff9181af86bb096) | Chris Kearney | drm/asahi: keep firmware-log payload clear of render counter pages (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_init_storage.rs` | V | +| [eb1dec967643](https://github.com/aurora-silicon/linux/commit/eb1dec967643c6b4fbbd528812befd07434951d8), [11896702843e](https://github.com/aurora-silicon/linux/commit/11896702843eb979e00beb871090800d9c53a4e0) | Chris Kearney | drm/asahi: coalesce eligible T6030 render completion stamp flushes (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_batch_policy.rs` | V | +| [407c4ba9d5c8](https://github.com/aurora-silicon/linux/commit/407c4ba9d5c8f14292a7ba75afdc929475a0e40c), [7feec149fdc7](https://github.com/aurora-silicon/linux/commit/7feec149fdc7795ff82698683c86dc4b3a2a486b) | Chris Kearney | drm/asahi: order opt-in T6030 mixed batches with firmware barriers (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_batch_policy.rs` | V | +| [d8c059ddae69](https://github.com/aurora-silicon/linux/commit/d8c059ddae697f618e0b70b4b2ddee103ba9a1eb), [0377e6c2e9ad](https://github.com/aurora-silicon/linux/commit/0377e6c2e9ad549130015a52de84c0c24e47a343) | Chris Kearney | drm/asahi: make T6030 internal render measurements optional (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_batch_policy.rs` | V | +| [ad54cf1de60e](https://github.com/aurora-silicon/linux/commit/ad54cf1de60ea638b0ab96e80d9bea7d7fc726ea), [cd13ef870e00](https://github.com/aurora-silicon/linux/commit/cd13ef870e00dd86aed1b582b393c6a697bec326) | Chris Kearney | drm/asahi: use stage-specific render sequence length fields (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_command_layout.rs` | V | +| [16fef343da41](https://github.com/aurora-silicon/linux/commit/16fef343da414828fe4098a566951945838bb370), [689764765782](https://github.com/aurora-silicon/linux/commit/68976476578273e77d0d5f05f77f437f610dbee8) | Chris Kearney | drm/asahi: add bounded T6030 active power-state sampling (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_active_power.rs` | V | +| [019434491afe](https://github.com/aurora-silicon/linux/commit/019434491afe2cd57bc001515778d9412c9b0747), [cd09499b2a78](https://github.com/aurora-silicon/linux/commit/cd09499b2a78ce5e0ca1b628af94403c56467256) | Chris Kearney | drm/asahi: add opt-in T6030 consistent performance requests (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_active_power.rs` | V | +| [b09e6d9fca33](https://github.com/aurora-silicon/linux/commit/b09e6d9fca33c554a597774f1a6cd4c4ca54622b), [263fd8538d83](https://github.com/aurora-silicon/linux/commit/263fd8538d83cca3809589da9b78cb1d982c0a21) | Chris Kearney | drm/asahi: refresh an opt-in T6030 busy performance request (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_consistent_perf.rs` | V | +| [9bc784357fe8](https://github.com/aurora-silicon/linux/commit/9bc784357fe8affe0875fb911be88ac953ca30db), [db732ce1f244](https://github.com/aurora-silicon/linux/commit/db732ce1f24488887872abc3b61b45de95285252) | Chris Kearney | drm/asahi: add an opt-in T6030 Globals debug-word clear (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_globals_debug.rs` | V | +| [02bde1b70c31](https://github.com/aurora-silicon/linux/commit/02bde1b70c312f6d4d964c7a2b1737be0de5a909), [651fbc49524d](https://github.com/aurora-silicon/linux/commit/651fbc49524d677ff86766dd2d85bb1bbf6ab3eb) | Chris Kearney | drm/asahi: m3: groundwork for more than two clusters and T6031 tables (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_pass_layout.rs` | V | +| [0f47fbf31934](https://github.com/aurora-silicon/linux/commit/0f47fbf31934d0b1684b5412e235315304687d4a), [783f9478d373](https://github.com/aurora-silicon/linux/commit/783f9478d3737cfae945aad01e02e9dea30c2249) | Chris Kearney | drm/asahi: reject unsupported command fields on the M1/M2 queue path | Not applicable — LEGACY; `drivers/gpu/drm/asahi/queue/mod.rs` | V | +| [1de2dbd89acb](https://github.com/aurora-silicon/linux/commit/1de2dbd89acb80f37aec08fa897374dfebe56b10), [294e9f9ebfb4](https://github.com/aurora-silicon/linux/commit/294e9f9ebfb42dbc77a85f635b5bfabaa9315003) | Chris Kearney | drm/asahi: excise unbound G16 and G17 runtimes (M3-only split: TA/3D command encoders) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/agx_render.rs` | V | +| [fd1a2a3d4e60](https://github.com/aurora-silicon/linux/commit/fd1a2a3d4e60ce3f3965d2a47d1904ce3a6ed158), [e91bc024eef8](https://github.com/aurora-silicon/linux/commit/e91bc024eef8a6fde320f7fa062a034a2fe0bccb) | Eryk Wieliczko | drm/asahi: carry validated M3 depth compression bindings | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_fragment_command.rs` | V | +| [bc0a841680f3](https://github.com/aurora-silicon/linux/commit/bc0a841680f3b36193e83e18c339d269d142de03), [2c301fab2412](https://github.com/aurora-silicon/linux/commit/2c301fab241280c36f20c57d7d240ff0f48ad88f) | garymeyerca | drm/asahi: gate T8122 G15G identity before startup | Not applicable — G15; `drivers/gpu/drm/asahi/m3/mod.rs` | V | +| [e10c013299bf](https://github.com/aurora-silicon/linux/commit/e10c013299bf639ca0c8f08574150970e381f203), [be90c110d9df](https://github.com/aurora-silicon/linux/commit/be90c110d9dfcae3918efad4ba9a3d5015bb5bf4) | Chris Kearney | drm/asahi: select M3 runtime configuration per SoC | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_soc.rs` | V | +| [ed90e7bfde51](https://github.com/aurora-silicon/linux/commit/ed90e7bfde5160c20b1e4165f577e874e60fc102), [a6b472702f24](https://github.com/aurora-silicon/linux/commit/a6b472702f24666ee52febc1c2eb93bf21c8d44a) | Satya Benson | drm/asahi: m3: T8122: the G15G compute register list | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_compute_layout.rs` | V | +| [278d0abfc612](https://github.com/aurora-silicon/linux/commit/278d0abfc6128a83aa00d9181bdf2c716b608c89), [7949c0d6139c](https://github.com/aurora-silicon/linux/commit/7949c0d6139c53f8e016cd773744386407a8c9cf) | Satya Benson | drm/asahi: m3: T8122: the G15G TA and 3D register lists | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_fragment_command.rs` | V | +| [7231c0beb83e](https://github.com/aurora-silicon/linux/commit/7231c0beb83ec1976716d28456e09df3e159f2e5), [bfd3e0bf0bbc](https://github.com/aurora-silicon/linux/commit/bfd3e0bf0bbc64ee99213b4b5b3824b588411bba) | Satya Benson | drm/asahi: m3: T8122: the runtime HwDataB words | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_soc.rs` | V | +| [6ad35705d81d](https://github.com/aurora-silicon/linux/commit/6ad35705d81dd1e65f8ef9e1f4136a0080ead760), [f6e58511c69a](https://github.com/aurora-silicon/linux/commit/f6e58511c69ae5cdef73b3ba0d87de8de9a74e01) | Chris Kearney | drm/asahi: m3: T8122: the remaining runtime InitData words | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_soc.rs` | V | +| [2a8cdd0ce1d4](https://github.com/aurora-silicon/linux/commit/2a8cdd0ce1d4a8ceef673922b9517e4780996a0c), [d109a5c9e597](https://github.com/aurora-silicon/linux/commit/d109a5c9e59768669f2d1750ab1abd4560a42e28) | Chris Kearney | drm/asahi: coalesce eligible T6030 render completion stamp flushes | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_fragment_command.rs` | V | +| [bb1f75a18b32](https://github.com/aurora-silicon/linux/commit/bb1f75a18b3265ab5e4a06e424df43ed0371c002), [d4dba3df31cf](https://github.com/aurora-silicon/linux/commit/d4dba3df31cff8f96ddeafad6a4b2709fe942d62) | Chris Kearney | drm/asahi: use stage-specific render sequence length fields | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_fragment_command.rs` | V | +| [1e8f813be7c6](https://github.com/aurora-silicon/linux/commit/1e8f813be7c681b558ffcd501ea278d6848099f4), [3d7945cbbd0f](https://github.com/aurora-silicon/linux/commit/3d7945cbbd0f9dd9d7806c402e70a53815c30738) | Satya Benson | drm/asahi: m3: T8122: write the HwDataA +0x418c block on 14.8.3 | Not applicable — G15; `drivers/gpu/drm/asahi/m3/mod.rs` | V | +| [8730d3dce2b7](https://github.com/aurora-silicon/linux/commit/8730d3dce2b7e8d9f05ad437c4cf89169e0c5b78), [453d66a437b0](https://github.com/aurora-silicon/linux/commit/453d66a437b02027ad0b6fb194242dd29bb0d31d) | Chris Kearney | pmdomain: apple: pmp-report: add the T6031 report | Not applicable — G15; `drivers/pmdomain/apple/pmp-report-test.c` | V | +| [a66386d03830](https://github.com/aurora-silicon/linux/commit/a66386d03830555c976850001c12644921b0a028), [a8e5d3a57a0e](https://github.com/aurora-silicon/linux/commit/a8e5d3a57a0e19360cffa4dc04ae500ba832e0ea) | Marius Gehrmann | pmdomain: apple: pmp-report: Request T8122 devices by soc-device record | Not applicable — G15; `drivers/pmdomain/apple/pmp-report-test.c` | V | +| [3b9bb4563bb8](https://github.com/aurora-silicon/linux/commit/3b9bb4563bb8b9f16761fe5842ff3bb6a09d8661), [c7bbd7faf778](https://github.com/aurora-silicon/linux/commit/c7bbd7faf778be8ab87515e81caf06571e527ad8) | Chris Kearney | pmdomain: apple: pmp-report: start a handed-over T8122 or T6031 PMP | Not applicable — G15; `drivers/pmdomain/apple/pmp-report.c` | V | +| [3157c614a661](https://github.com/aurora-silicon/linux/commit/3157c614a66119b47100a6a8ac1ad9232bacbf81), [2b115cc04222](https://github.com/aurora-silicon/linux/commit/2b115cc04222553f4bcde86a70f5a2f9c8f5d086) | Ryan Murray | pmdomain: apple: pmp-report: document a failed PMP start and its recovery | Not applicable — VERIFY; `drivers/pmdomain/apple/Kconfig` | V | +| [c332c6fbd56a](https://github.com/aurora-silicon/linux/commit/c332c6fbd56adddb1f6d5635b4010a940c780e7e), [54b90aaf82e9](https://github.com/aurora-silicon/linux/commit/54b90aaf82e9aa7241747d747a5e79c46841a2dd) | Ryan Murray | pmdomain: apple: pmp-report: add KUnit fault injection for the PMP start | Not applicable — VERIFY; `drivers/pmdomain/apple/.kunitconfig.apple-pmp-start` | V | +| [7d101f700326](https://github.com/aurora-silicon/linux/commit/7d101f7003268e2e459ed86b07c9bc3ec5a000f2), [ac234af7c851](https://github.com/aurora-silicon/linux/commit/ac234af7c851e08e6da06a4dffeb912786611ed8) | Chris Kearney | drm/asahi: uapi: add the M3 (G15S) runtime numbers | Not applicable — G15 | V | +| [70f4bebd69b0](https://github.com/aurora-silicon/linux/commit/70f4bebd69b070d37022bca55228879bb7ec4e32), [577249de3221](https://github.com/aurora-silicon/linux/commit/577249de322139ceb84aea9daffcf54d437da52b) | Eryk Wieliczko | drm/asahi: uapi: add the J613 25G83 runtime numbers | Not applicable — G15 | V | +| [f3de3ac043ac](https://github.com/aurora-silicon/linux/commit/f3de3ac043acd22bf4ded712f2999b5693d824ae), [744d0129b6c0](https://github.com/aurora-silicon/linux/commit/744d0129b6c008708c2d9d019ca0a95ee1c04874) | Ryan Murray | drm/asahi: reject timestamp copies on M1 and M2 | Not applicable — LEGACY; `drivers/gpu/drm/asahi/queue/mod.rs` | V | +| [d2cdf2ff6b35](https://github.com/aurora-silicon/linux/commit/d2cdf2ff6b353a72f5f33239a4a7f2b7d87e8cf4), [1e52a05edada](https://github.com/aurora-silicon/linux/commit/1e52a05edadad288cfc17120f37c721f506abcd5) | Chris Kearney | gpu: preserve compute overrides and limit automatic preemption to M2 Max | Not applicable — LEGACY; `drivers/gpu/drm/asahi/asahi_core.rs` | V | +| [a22a6bfc2fa0](https://github.com/aurora-silicon/linux/commit/a22a6bfc2fa049f249effd677a512d682870b171) | Ryan Murray | rust: drm: document Aurora ownership and simplify pointer conversions | Not applicable — VERIFY; `rust/kernel/drm/gpuvm/mod.rs` | V | +| [e8f94ff7af1c](https://github.com/aurora-silicon/linux/commit/e8f94ff7af1cd4104091ac851109fdae946aa585) | Ryan Murray | arm64: dts: apple: j700: Use the raw xHCI SuperSpeed port number | Not applicable — PERIPH | V | +| [58d75c388d55](https://github.com/aurora-silicon/linux/commit/58d75c388d55030f432e93f42ff8871ffe9390f3) | Chris Kearney | soc: apple: opt native T6030 GPUs into the admitted PMP supplier [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/pmdomain/apple/pmp-report.c` | V | +| [19538d922267](https://github.com/aurora-silicon/linux/commit/19538d9222679bc5ff3f17da845a10f60591e82c) | Chris Kearney | pmdomain: apple: read readiness from the passive T6030 report [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/pmdomain/apple/pmp-report.c` | V | +| [1633fb96515e](https://github.com/aurora-silicon/linux/commit/1633fb96515e3b733a0e3514e17f841ef344ab4c) | Eryk Wieliczko | drm/asahi: g15: Add the UAPI glue and register the G15 generation [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/adt.rs` | V | +| [4d6d40767ae2](https://github.com/aurora-silicon/linux/commit/4d6d40767ae2a1cdbb697d3471dbb2c4a7ef9f9d) | Naeem Malik | drm/asahi: g15: Add the T6031 (M3 Max, G15C) configuration row [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/adt.rs` | V | +| [99a4031d5828](https://github.com/aurora-silicon/linux/commit/99a4031d58282e9d2d0f9211b3ba50547d4fc643) | Naeem Malik | drm/asahi: g15: Pin the J516C GPU firmware build [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/hw/t6031.rs` | V | +| [4abf7048277d](https://github.com/aurora-silicon/linux/commit/4abf7048277dd6ec8364811002adeafe326046d6) | Naeem Malik | drm/asahi: g15: Build the J516C initdata from its board images [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/board.rs` | V | +| [5fc412af6085](https://github.com/aurora-silicon/linux/commit/5fc412af608586b6beb6a567544f3440f42fe7eb) | Naeem Malik | drm/asahi: g15: Encode compute commands with the G15C register values [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/hw/t6031.rs` | V | +| [377ce9d4f32a](https://github.com/aurora-silicon/linux/commit/377ce9d4f32a42a3b87e0885f48777140c4a2488) | Naeem Malik | drm/asahi: g15: Admit and map UAT regions that are reserved System RAM [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/adt.rs` | V | +| [3a5c855a7219](https://github.com/aurora-silicon/linux/commit/3a5c855a7219aaf45d18a3449bfd4a263ed781c2) | Naeem Malik | drm/asahi: g15: Accept the J516C 14.8 GPU firmware build [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/hw/t6031.rs` | V | +| [f0d7c15b4769](https://github.com/aurora-silicon/linux/commit/f0d7c15b47696e3bbf168f1c8bce9c4817ea28da) | DjDeveloperr | drm/asahi: g15: Reject a scissor base that overflows the address space [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/command.rs` | V | +| [2510cc337f15](https://github.com/aurora-silicon/linux/commit/2510cc337f1551b53e9de38b5c69d6d365e373f3) | DjDeveloperr | drm/asahi: g15: Do not fail a published prefix on a group allocation failure [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/engine.rs` | V | +| [5a592f22b23a](https://github.com/aurora-silicon/linux/commit/5a592f22b23a83ec106b50d5f9ec88f9d885e980) | DjDeveloperr | drm/asahi: g15: Release a closed VM's state when its handle goes [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/mod.rs` | V | +| [e2b962c42e40](https://github.com/aurora-silicon/linux/commit/e2b962c42e40c5ee92b81495436c11b5832d056c) | DjDeveloperr | drm/asahi: g15: Release the power hold and the PMP vote when a start fails early [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/boot.rs` | V | +| [c4d01d363c1d](https://github.com/aurora-silicon/linux/commit/c4d01d363c1d25c33fee168114f369288e24e55d) | DjDeveloperr | drm/asahi: g15: Only mark used pass slots dirty on release [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/slots.rs` | V | +| [35bbcb1bca7a](https://github.com/aurora-silicon/linux/commit/35bbcb1bca7a9dac71f9785aed900d4cf6665d9f) | DjDeveloperr | tools/asahi: Drop the removed T6031 knobs from the M3 runtime tests [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/glue.c` | V | +| [f6da2c8fd38a](https://github.com/aurora-silicon/linux/commit/f6da2c8fd38a75723a7569ff6046d08572900559) | Chris Kearney | drm/asahi-m3: adapt the native G15 driver to the 7.2 driver core and DRM device API [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/adt.rs` | V | +| [42f91c2f9eae](https://github.com/aurora-silicon/linux/commit/42f91c2f9eae615bc12335e37f2016ec09398f63) | Naeem Malik | drm/asahi: g15: Say what to report when the GPU firmware build is unknown [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/identity.rs` | V | +| [f3b867b04c1a](https://github.com/aurora-silicon/linux/commit/f3b867b04c1a50f46d034844754e39f8031ffae3) | Naeem Malik | drm/asahi: g15: Move the board template fill into a core-only submodule [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/board.rs` | V | +| [7375309d764f](https://github.com/aurora-silicon/linux/commit/7375309d764f899d8eb4328e30447ec2959efa02) | Naeem Malik | drm/asahi: g15: t6031: Build Globals and Power from the device tree [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/board.rs` | V | +| [b840b9944ad9](https://github.com/aurora-silicon/linux/commit/b840b9944ad9e171cf5b13b4e0e0fe8d018778c9) | Naeem Malik | drm/asahi: g15: board: Review fixes for the device-tree Globals and Power [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/board/words.rs` | V | +| [3f02f6c358ab](https://github.com/aurora-silicon/linux/commit/3f02f6c358abd1630f1342cbd4dbca755c78023c) | Naeem Malik | drm/asahi: g15: t6031: Build the hardware record from the device tree [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/adt.rs` | V | +| [f1f5a81b756f](https://github.com/aurora-silicon/linux/commit/f1f5a81b756f99b30baefcd967a924167b2a0c12) | Naeem Malik | drm/asahi: t6031: take the idle standby time from the GPU node [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/hw.rs` | V | +| [de646267a725](https://github.com/aurora-silicon/linux/commit/de646267a725f94ea67864c2695eb1082f60dc33) | Chris Kearney | drm/asahi: g15: t6031: refuse board-data values that do not fit in u32 [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/adt.rs` | V | +| [a9a0e4be0546](https://github.com/aurora-silicon/linux/commit/a9a0e4be0546ee7876924cee8e3478727f89cb41) | Chris Kearney | selftests: asahi-g15: run the T6031 board-data and firmware-lookup tests [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/board/record.rs` | V | +| [0b06992d812c](https://github.com/aurora-silicon/linux/commit/0b06992d812ce75667740514a5a472fc8d0e091b) | Chris Kearney | drm/asahi: g15: t6031: test that no GPU node value panics the record builder [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/board/record.rs` | V | +| [68b780fc7ced](https://github.com/aurora-silicon/linux/commit/68b780fc7ced9e39a5d4573993564ebda26f3649) | Naeem Malik | drm/asahi: g15: Read back the GPU's performance state in debugfs [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/board.rs` | V | +| [cd970562fa97](https://github.com/aurora-silicon/linux/commit/cd970562fa979059c6dea0e7f0a7f9df63c3b1c7) | Naeem Malik | drm/asahi: g15: t6031: Leave the 42 record words the firmware does not need zero [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/board/record.rs` | V | +| [2a0319927108](https://github.com/aurora-silicon/linux/commit/2a0319927108ee823a82bb1884d2cc6512b515bb) | Chris Kearney | drm/asahi: g15: m3: complete the stats record loads before consuming them [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/channel.rs` | V | +| [73047c7ee59e](https://github.com/aurora-silicon/linux/commit/73047c7ee59e45b8d16e8ec308fb15683caefc6e) | Naeem Malik | drm/asahi: g15: Seed the compute UMA freelist with every pool [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/fw/graph.rs` | V | +| [345f1d8467a4](https://github.com/aurora-silicon/linux/commit/345f1d8467a46f679eb2f2fc2852adb844911033) | Naeem Malik | drm/asahi: g15: Leave PB growth requests unanswered after two refusals at the maximum [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/params.rs` | V | +| [bd0201fad2e0](https://github.com/aurora-silicon/linux/commit/bd0201fad2e02429de6e265c51a223e4209920fe) | Naeem Malik | drm/asahi: g15: Keep PB blocks above the firmware's growth maximum for partial renders [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/params.rs` | V | +| [ff0d1cd692b8](https://github.com/aurora-silicon/linux/commit/ff0d1cd692b838934972a646fa7d1411c54b4864) | Naeem Malik | drm/asahi: g15: Map a 512 MiB parameter buffer heap [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/fw/buffer.rs` | V | +| [e1cdc12eeb97](https://github.com/aurora-silicon/linux/commit/e1cdc12eeb97fc9f4da88343054e6a964e8d2e40) | Chris Kearney | drm/asahi: g15: m3: keep every PB ledger below the block list's K [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/pools.rs` | V | +| [b1adafb49e1c](https://github.com/aurora-silicon/linux/commit/b1adafb49e1c49b71221f7c069dde6045ccce32c) | Chris Kearney | drm/asahi: g15: m3: say what a PB refusal does and size the heap right [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/fw/buffer.rs` | V | +| [3ea5cb6af82b](https://github.com/aurora-silicon/linux/commit/3ea5cb6af82bda7fca665b0902b22275ae02d996) | Chris Kearney | drm/asahi: g15: m3: check firmware-object backing geometry with checked arithmetic [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/object.rs` | V | +| [1eced02cd6c3](https://github.com/aurora-silicon/linux/commit/1eced02cd6c3efe9c43cfc9cf0993ee427e849b9) | Chris Kearney | drm/asahi: g15: m3: map the PB backing into each VM when it first runs [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/engine.rs` | V | +| [73b9ade12e01](https://github.com/aurora-silicon/linux/commit/73b9ade12e0123ef2168abd7eb4969d6865622a3) | Chris Kearney | drm/asahi: g15: m3: back the PB on demand, before an answer posts it [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/pools.rs` | V | +| [716d2fbe9254](https://github.com/aurora-silicon/linux/commit/716d2fbe9254f5ba13a8bd6a6ac687f5904a2f8a) | Chris Kearney | drm/asahi: adapt retained-G15 hooks to the maintained shared APIs [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/file.rs` | V | +| [a4000925920a](https://github.com/aurora-silicon/linux/commit/a4000925920a0e338e0bfa2b726f3b4de90b8cd7) | Chris Kearney | drm/asahi: port the native G15 adapter onto maintained DRM registration [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/Kconfig` | V | +| [23ae3eaf8377](https://github.com/aurora-silicon/linux/commit/23ae3eaf8377dbbd5c51541d77fb4a542fdb384b) | Chris Kearney | drm/asahi: g15: allocate driver data before touching firmware [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/mod.rs` | V | +| [a8289266db94](https://github.com/aurora-silicon/linux/commit/a8289266db94cf7069ab26420bd9d6b60a92dce6) | Chris Kearney | drm/asahi: g15: defer firmware start until module registration completes [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/asahi_core.rs` | V | +| [bc962412f16e](https://github.com/aurora-silicon/linux/commit/bc962412f16e1ca6d93ababf971c68011294c5e3) | Chris Kearney | drm/asahi: g15: make maintained loader contracts independent of SF profiles [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/adt.rs` | V | +| [6c6f0e62156c](https://github.com/aurora-silicon/linux/commit/6c6f0e62156cd9e35f5db47af226dd9821a26b0f) | Chris Kearney | drm/asahi: use the native private wire in G15 command consumers [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/boot.rs` | V | +| [627c501db65e](https://github.com/aurora-silicon/linux/commit/627c501db65ed9bbe22e853f37f247dc579db4af) | Chris Kearney | drm/asahi: isolate the native G15 command ABI behind a private facade [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/wire.rs` | V | +| [9d5c0ee71512](https://github.com/aurora-silicon/linux/commit/9d5c0ee715129fda8cbb8a1420315cf6a7d25cc4) | Chris Kearney | drm/asahi: g15: bound device-wide live logical queue capacity [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/mod.rs` | V | +| [4f6aba6a3598](https://github.com/aurora-silicon/linux/commit/4f6aba6a35980bf152ce5803d7bb5940a2cb11a2) | Chris Kearney | pmdomain: apple: pmgr-pwrstate: Test apple,no-auto-pm on power on and off [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — VERIFY; `drivers/pmdomain/apple/Kconfig` | V | +| [b9d3cf70f1e8](https://github.com/aurora-silicon/linux/commit/b9d3cf70f1e8b488a9ad032dadad7cbe432e164c) | Chris Kearney | drm/asahi: export a T6030 module alias for the native G15 driver [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/mod.rs` | V | +| [af2c5c09ba9f](https://github.com/aurora-silicon/linux/commit/af2c5c09ba9fae7fbf9f165fc6aad73e9aadce0b) | Chris K | pmdomain: apple: restore gated T6030 managed PMP startup [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/pmdomain/apple/pmp-report.c` | V | +| [2a182741b7f7](https://github.com/aurora-silicon/linux/commit/2a182741b7f7d80a7a743eafbf57bd9dbca3c6b4) | Chris Kearney | test: exercise the actual T6030 halted-PMP consumer graph [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/pmdomain/apple/Kconfig` | V | +| [70f41a7944ee](https://github.com/aurora-silicon/linux/commit/70f41a7944eeaf9afdd63f9246ebc5bc41df23f1) | Chris Kearney | drm/apple: admit T6030 G15 only after native display registration [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/apple/apple_drv.c` | V | +| [9632ccd283f9](https://github.com/aurora-silicon/linux/commit/9632ccd283f96ad98321238b6da4718b62f7671a) | Chris Kearney | drm/asahi: require a callable native display provider for G15 [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/Kconfig` | V | +| [e9d92ce6a5c2](https://github.com/aurora-silicon/linux/commit/e9d92ce6a5c27b36266de99d252db029476cb3f1) | Chris Kearney | drm/asahi: retain live G15 storage after allocation failures [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/boot.rs` | V | +| [fc13d05b1db9](https://github.com/aurora-silicon/linux/commit/fc13d05b1db9515fa9481d1362ff530fbeb01ca4) | DjDeveloperr | drm/asahi: g15: adapt the native G15 backend to the Linux 7.3 Rust APIs [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/boot.rs` | V | +| [0ffd22188c3d](https://github.com/aurora-silicon/linux/commit/0ffd22188c3dbb2e8bb2df216ee8e03d71e14bfb) | Ryan Murray | drm/asahi: Keep refusing HIGH queue priority on G13 and G14 [#399](https://github.com/aurora-silicon/linux/pull/399) | Not applicable — LEGACY; `drivers/gpu/drm/asahi/file.rs` | V | +| [1fac0605f061](https://github.com/aurora-silicon/linux/commit/1fac0605f061ce757917781940a5e0aec1225019) | Eryk Wieliczko | soc: apple: rtkit-helper: preserve the inherited J713 input session | Not applicable — G16; `drivers/soc/apple/rtkit-helper.c` | V | +| [751b9e08ca3d](https://github.com/aurora-silicon/linux/commit/751b9e08ca3d8fa673927984d45b22697cc26edf) | Eryk Wieliczko | iommu: apple-dart: retain J713 input and radio ownership [#400](https://github.com/aurora-silicon/linux/pull/400) | Not applicable — G16; `drivers/iommu/apple-dart.c` | V | +| [939a993d46dd](https://github.com/aurora-silicon/linux/commit/939a993d46dd36392155cc05f692472d2d1c881e) | Eryk Wieliczko | pmdomain: apple: pmc: support T8132 floor requests [#400](https://github.com/aurora-silicon/linux/pull/400) | Not applicable — G16; `drivers/pmdomain/apple/pmc-test.c` | V | +| [c26dfe75ac9b](https://github.com/aurora-silicon/linux/commit/c26dfe75ac9be0fb75dfe937a15246b4e12bc84a) | Eryk Wieliczko | soc: apple: clpc: retain qualified T8132 memory floors | Not applicable — G16; `drivers/soc/apple/clpc.c` | V | +| [15e0eb00b83b](https://github.com/aurora-silicon/linux/commit/15e0eb00b83b783d7fca9720530942020722cd6a) | Eryk Wieliczko | soc: apple: mcc: support the T8132 memory controller | Not applicable — G16; `drivers/soc/apple/mcc.c` | V | +| [df3e4970e686](https://github.com/aurora-silicon/linux/commit/df3e4970e68677d56eca3e9e9608c338abe52cdd) | Eryk Wieliczko | soc: apple: pmp: publish owner-managed T8132 telemetry [#400](https://github.com/aurora-silicon/linux/pull/400) | Not applicable — G16; `drivers/pmdomain/apple/pmp-report.c` | V | +| [f1b75932e61d](https://github.com/aurora-silicon/linux/commit/f1b75932e61d7d193801a7f22ea628cb94d9dd56) | Eryk Wieliczko | cpufreq: apple: track T8132 thermal ownership per policy | Not applicable — G16; `drivers/cpufreq/apple-soc-cpufreq.c` | V | +| [1e23ae3aee82](https://github.com/aurora-silicon/linux/commit/1e23ae3aee824c65cbb6d3c94780e44a2e3fc6c2) | Eryk Wieliczko | thermal: apple: add the J713 PMP CPU policy [#400](https://github.com/aurora-silicon/linux/pull/400) | Not applicable — G16; `drivers/thermal/Kconfig` | V | +| [332eea1e9005](https://github.com/aurora-silicon/linux/commit/332eea1e9005b0cf731a16695c9c87c34e8f13aa) | Eryk Wieliczko | arm64: apple: activate J713 with its PMGR ownership policy [#400](https://github.com/aurora-silicon/linux/pull/400) | Not applicable — G16; `drivers/pmdomain/apple/pmgr-pwrstate.c` | V | +| [3949e3640d94](https://github.com/aurora-silicon/linux/commit/3949e3640d94d2b5434c84e9e1f6d7b81f650b70) | Eryk Wieliczko | arm64: apple: enable qualified J713 SuperSpeed host support [#400](https://github.com/aurora-silicon/linux/pull/400) | Not applicable — G16; `drivers/phy/apple/atc.c` | V | +| [0877cd766126](https://github.com/aurora-silicon/linux/commit/0877cd766126503ad02cc11565b85be37517a62c) | Eryk Wieliczko | arm64: apple: enable J713 Bluetooth with scoped radio DMA [#400](https://github.com/aurora-silicon/linux/pull/400) | Not applicable — G16; `drivers/bluetooth/hci_bcm4377.c` | V | +| [1ae4d49f3592](https://github.com/aurora-silicon/linux/commit/1ae4d49f35921f356d831da974c43dd5fb67f5d7) | Eryk Wieliczko | soc: apple: expose retained PMP display and GPU ownership [#400](https://github.com/aurora-silicon/linux/pull/400) | Not applicable — G16; `drivers/pmdomain/apple/pmp-report.c` | V | +| [a5f2ee806da2](https://github.com/aurora-silicon/linux/commit/a5f2ee806da2931b10d69044e4a40609d4924201) | Eryk Wieliczko | thermal: apple: integrate J713 measured-rail GPU budgets [#400](https://github.com/aurora-silicon/linux/pull/400) | Not applicable — G16; `drivers/thermal/Kconfig` | V | +| [52d846f6b8d3](https://github.com/aurora-silicon/linux/commit/52d846f6b8d3664ea6135a660c0b3ece39937209) | Eryk Wieliczko | iommu/apple-dart: adopt J713 display streams with retained ownership [#400](https://github.com/aurora-silicon/linux/pull/400) | Not applicable — G16; `drivers/iommu/Kconfig` | V | +| [24f67337c9d5](https://github.com/aurora-silicon/linux/commit/24f67337c9d586100ca4a4f438e83310644c1903) | Eryk Wieliczko | drm/asahi: add the T8132 G16 firmware backend [#400](https://github.com/aurora-silicon/linux/pull/400) | Not applicable — G16; `drivers/gpu/drm/asahi/Kconfig` | V | +| [fb96882f6cb4](https://github.com/aurora-silicon/linux/commit/fb96882f6cb4ce464fcf3f8b3f27f0c3eba891ed) | Ryan Murray | soc: apple: rtkit-helper: Keep serving buffer requests on T8140 adopted sessions | Not applicable — PERIPH; `drivers/soc/apple/rtkit-helper.c` | V | +| [4d88194af46e](https://github.com/aurora-silicon/linux/commit/4d88194af46e4eec12e57c3e7ff8bbcf7ac5b856) | Ryan Murray | pmdomain: apple: pmgr-pwrstate: Honour apple,inherited-on only on T8132 [#400](https://github.com/aurora-silicon/linux/pull/400) | Not applicable — G16; `drivers/pmdomain/apple/pmgr-pwrstate.c` | V | +| [d14b566cc309](https://github.com/aurora-silicon/linux/commit/d14b566cc309cf6840199c6394dc8f5f75f82f54) | Ryan Murray | drm/asahi: g17: describe the 25G83 barrier and parameter block plainly [#420](https://github.com/aurora-silicon/linux/pull/420) | Not applicable — VERIFY; `drivers/gpu/drm/asahi/g17/buffer/target.rs` | V | +| [f0f00dca4b09](https://github.com/aurora-silicon/linux/commit/f0f00dca4b09c9caf6f379928827e0d10451c66b) | Ryan Murray | drm/asahi: mem: document that page-by-page invalidation is global [#420](https://github.com/aurora-silicon/linux/pull/420) | Not applicable — VERIFY; `drivers/gpu/drm/asahi/mem.rs` | V | +| [8eacef90ff89](https://github.com/aurora-silicon/linux/commit/8eacef90ff8952b6fc75a85cb20abce488bcd4ed) | Erik Bourget | drm/asahi: g16: accept the 8-core J713 loaded 25G83 image [#442](https://github.com/aurora-silicon/linux/pull/442) | Not applicable — G16; `drivers/gpu/drm/asahi/g16/hw.rs` | V | +| [da42c7cf4fc8](https://github.com/aurora-silicon/linux/commit/da42c7cf4fc83e68261f1c238b2a3b870f94a128) | Ryan Murray | drm/asahi: test shared firmware identity normalization [#428](https://github.com/aurora-silicon/linux/pull/428) | Not applicable — VERIFY; `drivers/gpu/drm/asahi/fwid.rs` | V | +| [b11a5e9e8cd8](https://github.com/aurora-silicon/linux/commit/b11a5e9e8cd83d4ac3ac40bc620de254b095c75d) | DjDeveloperr | cpufreq: apple: verify T8140 transitions on the next request [#443](https://github.com/aurora-silicon/linux/pull/443) | Not applicable — LAZY; `drivers/cpufreq/apple-soc-cpufreq.c` | C,I | +| [e2a2d8ecbb24](https://github.com/aurora-silicon/linux/commit/e2a2d8ecbb24146ff61e1649ff503f234e846b30) | DjDeveloperr | cpufreq: apple: verify T8140 fast-switch requests from a timer [#443](https://github.com/aurora-silicon/linux/pull/443) | Not applicable — LAZY; `drivers/cpufreq/apple-soc-cpufreq.c` | C,I | +| [b7ea3ca9de07](https://github.com/aurora-silicon/linux/commit/b7ea3ca9de073d66f0e031013b7853c3e5702479) | DjDeveloperr | cpufreq: apple: settle T8140 lazy requests before suspend [#443](https://github.com/aurora-silicon/linux/pull/443) | Not applicable — LAZY; `drivers/cpufreq/apple-soc-cpufreq.c` | C,I | +| [6f5d208b4ce0](https://github.com/aurora-silicon/linux/commit/6f5d208b4ce01ff8cfea04fa21f14ac9a7cb610c) | DjDeveloperr | cpufreq: apple: apply a lowered T8140 fast die limit at once [#443](https://github.com/aurora-silicon/linux/pull/443) | Not applicable — LAZY; `drivers/cpufreq/apple-soc-cpufreq.c` | P | +| [b884471f5b13](https://github.com/aurora-silicon/linux/commit/b884471f5b13e62de9105bb0f06362fe01663a75) | Ryan Murray | thermal: apple-smc-cpu-thermal: walk the OPP table by die temperature | Not applicable — OLDTHERMAL; `drivers/thermal/apple-smc-cpu-thermal.c` | P | +| [ca1dab3dde89](https://github.com/aurora-silicon/linux/commit/ca1dab3dde89c57cc0b111fd096478241e6565e5) | Ryan Murray | cpufreq: apple-soc: Drop the SMC CPU thermal policy hooks | Not applicable — OLDTHERMAL; `drivers/cpufreq/apple-soc-cpufreq.c` | P | +| [52006c8e0bc5](https://github.com/aurora-silicon/linux/commit/52006c8e0bc5750331643c2f36ebad06ef300079) | Ryan Murray | thermal: Remove the Apple SMC CPU thermal policy [#43](https://github.com/aurora-silicon/linux/pull/43) [#380](https://github.com/aurora-silicon/linux/pull/380) [#470](https://github.com/aurora-silicon/linux/pull/470) | Not applicable — OLDTHERMAL; `drivers/thermal/Kconfig` | P | +| [1ce75ee9e797](https://github.com/aurora-silicon/linux/commit/1ce75ee9e797c296f742abf955764fa00b77e03d) | Ryan Murray | treewide: apple: t8140: Describe observed behaviour, not other implementations | Not applicable — VERIFY; `drivers/media/platform/apple/isp/isp-cam.c` | V | +| [3b0af1da96c2](https://github.com/aurora-silicon/linux/commit/3b0af1da96c28777073ebf99d65f1498a5f9fe94) | Ace | PCI: apple: enable bounded J700 endpoint bootstrap and enumeration | Not applicable — PERIPH; `drivers/pci/controller/Kconfig` | V | +| [e179042ff067](https://github.com/aurora-silicon/linux/commit/e179042ff0673c4b5adc749cb0fc3fcb0f909106) | Acelogic | HID: dockchannel: support firmware-managed M5 trackpad power | Not applicable — OTHERCHIP; `drivers/hid/dockchannel-hid/dockchannel-hid.c` | V | +| [f19bcf1a1e37](https://github.com/aurora-silicon/linux/commit/f19bcf1a1e373fb5bae2c9a5a0153d7820b13645) | Joshua Warren | drm/asahi: stats: Add StatsSnapshot and firmware-stats decoder [#197](https://github.com/aurora-silicon/linux/pull/197) [#380](https://github.com/aurora-silicon/linux/pull/380) [#470](https://github.com/aurora-silicon/linux/pull/470) | Not applicable — STAT; `drivers/gpu/drm/asahi/stats.rs` | T | +| [72d21d654534](https://github.com/aurora-silicon/linux/commit/72d21d654534decaecc58af0f8fc813064e6f500) | Joshua Warren | drm/asahi: Wire StatsSnapshot into StatsChannel and GpuManager [#197](https://github.com/aurora-silicon/linux/pull/197) [#380](https://github.com/aurora-silicon/linux/pull/380) [#470](https://github.com/aurora-silicon/linux/pull/470) | Not applicable — STAT; `drivers/gpu/drm/asahi/channel.rs` | T | +| [e8c789f9f77b](https://github.com/aurora-silicon/linux/commit/e8c789f9f77baf73f21857ca29a527930ed12aab) | Joshua Warren | drm/asahi: Add sysfs shim for agx_stats [#197](https://github.com/aurora-silicon/linux/pull/197) [#380](https://github.com/aurora-silicon/linux/pull/380) [#470](https://github.com/aurora-silicon/linux/pull/470) | Not applicable — STAT; `drivers/gpu/drm/asahi/Makefile` | T | +| [1b02b0b40af3](https://github.com/aurora-silicon/linux/commit/1b02b0b40af3822e16b5842e21964be40a42025f) | Joshua Warren | drm/asahi: Register agx_stats sysfs file on probe and unregister on drop [#197](https://github.com/aurora-silicon/linux/pull/197) [#380](https://github.com/aurora-silicon/linux/pull/380) [#470](https://github.com/aurora-silicon/linux/pull/470) | Not applicable — STAT; `drivers/gpu/drm/asahi/driver.rs` | T | +| [742f54c4855d](https://github.com/aurora-silicon/linux/commit/742f54c4855d0e3b4db54da4663d421f63d1165a) | Joshua Warren | drm/asahi: count completed submissions in agx_stats jobs [#197](https://github.com/aurora-silicon/linux/pull/197) [#380](https://github.com/aurora-silicon/linux/pull/380) [#470](https://github.com/aurora-silicon/linux/pull/470) | Not applicable — STAT; `drivers/gpu/drm/asahi/gpu.rs` | T | +| [bbfb96f309c8](https://github.com/aurora-silicon/linux/commit/bbfb96f309c8dd264540281469bba6c4a48ee789) | Joshua Warren | drm/asahi: link the sysfs shim into the driver module correctly [#197](https://github.com/aurora-silicon/linux/pull/197) [#380](https://github.com/aurora-silicon/linux/pull/380) [#470](https://github.com/aurora-silicon/linux/pull/470) | Not applicable — STAT; `drivers/gpu/drm/asahi/Makefile` | T | +| [01d19b8a4f55](https://github.com/aurora-silicon/linux/commit/01d19b8a4f55bb202b5c9d85a36996a90537602c) | Joshua Warren | drm/asahi: propagate the stats snapshot allocation result [#197](https://github.com/aurora-silicon/linux/pull/197) [#380](https://github.com/aurora-silicon/linux/pull/380) [#470](https://github.com/aurora-silicon/linux/pull/470) | Not applicable — STAT; `drivers/gpu/drm/asahi/gpu.rs` | T | +| [91c24dc11b09](https://github.com/aurora-silicon/linux/commit/91c24dc11b093be4868234ac2e00eed670d1e741) | Joshua Warren | drm/asahi: decode the stats messages where StatsMsg is nameable [#197](https://github.com/aurora-silicon/linux/pull/197) [#380](https://github.com/aurora-silicon/linux/pull/380) [#470](https://github.com/aurora-silicon/linux/pull/470) | Not applicable — STAT; `drivers/gpu/drm/asahi/channel.rs` | T | +| [dbc8b48aeb06](https://github.com/aurora-silicon/linux/commit/dbc8b48aeb06f85c9e7f9b63be9db7afef074580) | Joshua Warren | drm/asahi: qualify the versioned StatsMsg variant patterns [#197](https://github.com/aurora-silicon/linux/pull/197) [#380](https://github.com/aurora-silicon/linux/pull/380) [#470](https://github.com/aurora-silicon/linux/pull/470) | Not applicable — STAT; `drivers/gpu/drm/asahi/channel.rs` | T | +| [4189501b643f](https://github.com/aurora-silicon/linux/commit/4189501b643f404faa0ea37fc3bb379226300aa9) | Joshua Warren | drm/asahi: fix the agx_stats readout layout and the off-arm string [#197](https://github.com/aurora-silicon/linux/pull/197) [#380](https://github.com/aurora-silicon/linux/pull/380) [#470](https://github.com/aurora-silicon/linux/pull/470) | Not applicable — STAT; `drivers/gpu/drm/asahi/driver.rs` | T | +| [0b923fb29c84](https://github.com/aurora-silicon/linux/commit/0b923fb29c840b9bb675ba8ad9e81c513903b516) | Joshua Warren | drm/asahi: integrate utilization-weighted time into agx_stats busy_ns [#197](https://github.com/aurora-silicon/linux/pull/197) [#380](https://github.com/aurora-silicon/linux/pull/380) [#470](https://github.com/aurora-silicon/linux/pull/470) | Not applicable — STAT; `drivers/gpu/drm/asahi/channel.rs` | T | +| [9250872b1f33](https://github.com/aurora-silicon/linux/commit/9250872b1f33f357c01ccd3bf79be7835fc5c982) | Joshua Warren | drm/asahi: convert stats timestamps from base-clock ticks to ns [#197](https://github.com/aurora-silicon/linux/pull/197) [#380](https://github.com/aurora-silicon/linux/pull/380) [#470](https://github.com/aurora-silicon/linux/pull/470) | Not applicable — STAT; `drivers/gpu/drm/asahi/channel.rs` | T | +| [993288b37ec4](https://github.com/aurora-silicon/linux/commit/993288b37ec4368bbf2debbbd73124d8a2652410) | Joshua Warren | drm/asahi: split the tick window to avoid 128-bit division [#197](https://github.com/aurora-silicon/linux/pull/197) [#380](https://github.com/aurora-silicon/linux/pull/380) [#470](https://github.com/aurora-silicon/linux/pull/470) | Not applicable — STAT; `drivers/gpu/drm/asahi/channel.rs` | T | +| [9aebc2952fb5](https://github.com/aurora-silicon/linux/commit/9aebc2952fb51f6b0b9c03222109fe7f0ac107c2) | Ryan Murray | arm64: dts: apple: retain the T8140 P-cluster domain cells [#179](https://github.com/aurora-silicon/linux/pull/179) | Not applicable — BOARD | B | +| [e3163ca622d6](https://github.com/aurora-silicon/linux/commit/e3163ca622d6c55979d963929edbdf47f2c4c38c) | Ryan Murray | soc: apple: expose the T8140 PMP DVFS report for diagnosis [#182](https://github.com/aurora-silicon/linux/pull/182) [#380](https://github.com/aurora-silicon/linux/pull/380) [#470](https://github.com/aurora-silicon/linux/pull/470) | Not applicable — RAWDIAG; `drivers/pmdomain/apple/pmp-report.c` | O | +| [14e101ee2b8b](https://github.com/aurora-silicon/linux/commit/14e101ee2b8b599cf627015368e16ff8b3d2b95f) | Ryan Murray | cpufreq: apple: retain the unmanaged T8152 P-state ceiling | Not applicable — OTHERCHIP; `drivers/cpufreq/apple-soc-cpufreq.c` | V | +| [19d6664ea33a](https://github.com/aurora-silicon/linux/commit/19d6664ea33affcceef42d5deef488ce1ba7d50d) | Ryan Murray | arm64: dts: apple: retain a single J700 PMP alias | Not applicable — BOARD | B | +| [98038492dc48](https://github.com/aurora-silicon/linux/commit/98038492dc481d784c76fb2c52f3dd030dfad8f5) | Ryan Murray | drm: apple: recognize the T8140 external binding in the display master | Not applicable — BOARD; `drivers/gpu/drm/apple/apple_drv.c` | B | +| [78fc11047efa](https://github.com/aurora-silicon/linux/commit/78fc11047efa814a238113c46b4bb07aa7d641fb) | Ryan Murray | arm64: dts: apple: give the external J700 trial its firmware DMA window | Not applicable — PERIPH | V | +| [bc3b8aaa560f](https://github.com/aurora-silicon/linux/commit/bc3b8aaa560f23e069a7ea2d774645ea5fec39cc) | Ryan Murray | arm64: dts: apple: select the second external DCP source on J700 | Not applicable — PERIPH | V | +| [d08fd52e1619](https://github.com/aurora-silicon/linux/commit/d08fd52e161953a481a464716dd68aae85338044) | DjDeveloperr | drm/apple: enable the validated J700 external display profile by default | Not applicable — BOARD; `drivers/gpu/drm/apple/dcp-external-bw.h` | B | +| [01c8fe5e98f4](https://github.com/aurora-silicon/linux/commit/01c8fe5e98f4ab8805957ca3206d6d46a65b62e7) | Ryan Murray | soc: apple: aop: Do not overrun unread entries in the transmit ring [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — PERIPH; `drivers/soc/apple/aop.rs` | V | +| [02694d65655b](https://github.com/aurora-silicon/linux/commit/02694d65655baae0536d47beef4f0356145069f4) | Ryan Murray | spi: apple-qspi: Resume the controller even if its reset fails [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — PERIPH; `drivers/spi/spi-apple-qspi.c` | V | +| [033fa5e3c095](https://github.com/aurora-silicon/linux/commit/033fa5e3c0959f772e8464a25b8b93773f39b5b9) | Ryan Murray | soc: apple: aop: Accept a transmit entry at a write pointer of zero [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — PERIPH; `drivers/soc/apple/aop_ring.rs` | V | +| [0790f8a546fe](https://github.com/aurora-silicon/linux/commit/0790f8a546fea7ae46871d857643568c1b3c0c64) | Ryan Murray | hwmon: macsmc: drop the check for the undefined apple,cpu-thermal-policy [#317](https://github.com/aurora-silicon/linux/pull/317) | Not applicable — PERIPH; `drivers/hwmon/macsmc-hwmon.c` | V | +| [0943ae25f9bd](https://github.com/aurora-silicon/linux/commit/0943ae25f9bd82917a7a4813962d624d22056fd3) | Ryan Murray | HID: dockchannel-hid: Reject unaligned packet lengths before checksumming [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — PERIPH; `drivers/hid/dockchannel-hid/dockchannel-hid-parser-test.c` | V | +| [0c55425a8157](https://github.com/aurora-silicon/linux/commit/0c55425a8157326aa0bafd20a1c30a0f34af8650) | Ryan Murray | dt-bindings: gpu: apple,agx: Require three cells in apple,firmware-version [#420](https://github.com/aurora-silicon/linux/pull/420) | Not applicable — VERIFY | V | +| [152cc6b4d737](https://github.com/aurora-silicon/linux/commit/152cc6b4d737c3523b56d1c4b0b4cd3928e514b5) | Ryan Murray | spi: apple-qspi: Do not print a banner on probe [#314](https://github.com/aurora-silicon/linux/pull/314) | Not applicable — PERIPH; `drivers/spi/spi-apple-qspi.c` | V | +| [1baee970e0a9](https://github.com/aurora-silicon/linux/commit/1baee970e0a9d5bf0343f72eed61badfc5918159) | Jonathan Goyvaerts | dt-bindings: mailbox: apple,ascwrap-v6: Add the T6040 [#420](https://github.com/aurora-silicon/linux/pull/420) | Not applicable — VERIFY | V | +| [1df5bd529669](https://github.com/aurora-silicon/linux/commit/1df5bd529669987e8804b6ab38dbd1d5175f508d) | Ryan Murray | HID: dockchannel-hid: Test MTP message bounds with KUnit [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — VERIFY; `drivers/hid/dockchannel-hid/Kconfig` | V | +| [1f7383d5a281](https://github.com/aurora-silicon/linux/commit/1f7383d5a2811b99a9f2950581ec392837bfc469) | DjDeveloperr | rust: check generated layouts and provide corrected private bindgen [#171](https://github.com/aurora-silicon/linux/pull/171) | Not applicable — VERIFY | V | +| [232faf36664c](https://github.com/aurora-silicon/linux/commit/232faf36664c7e23aa9950bc3019dbf6ba534ee8) | Ryan Murray | HID: dockchannel-hid: Keep parser tests in the subsystem menu [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — PERIPH; `drivers/hid/dockchannel-hid/Kconfig` | V | +| [2d5c7b6bc705](https://github.com/aurora-silicon/linux/commit/2d5c7b6bc705f0ba8eef8f3d1ea23a53d4248eba) | Ryan Murray | ASoC: apple: t8140-aop-audio: Refuse card names that do not fit [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — PERIPH | V | +| [3006ca1a3835](https://github.com/aurora-silicon/linux/commit/3006ca1a383525a092ad1cbe9c268786292a32de) | Ryan Murray | mfd: macsmc: Test key and fixed-width readers with KUnit [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — VERIFY; `drivers/mfd/Kconfig` | V | +| [3024185f2efa](https://github.com/aurora-silicon/linux/commit/3024185f2efa34c4ea8ac9eebebf06b921795f0b), [aaea0b3a4fcf](https://github.com/aurora-silicon/linux/commit/aaea0b3a4fcf5cb371b735c6b595a2919a6a4d07) | DjDeveloperr | dt-bindings: power: add Apple PMC voter interface [#212](https://github.com/aurora-silicon/linux/pull/212) [#219](https://github.com/aurora-silicon/linux/pull/219) | Not applicable — VERIFY | V | +| [3132675b256e](https://github.com/aurora-silicon/linux/commit/3132675b256e07ad782280c42c853c63f6d18ef1) | Ryan Murray | selftests: apple-dcp: Run the queue recovery control through the RX drain [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — VERIFY | V | +| [315f6dc9944d](https://github.com/aurora-silicon/linux/commit/315f6dc9944de65990476dae34079d3e1313d605) | Chris Kearney | soc: apple: mailbox: match the T8122 GPU mailbox without the v4 fallback | Not applicable — G15; `drivers/soc/apple/mailbox.c` | V | +| [3407efe83df6](https://github.com/aurora-silicon/linux/commit/3407efe83df680534e10c0e66ffa36b75cae69ba) | Jonathan Goyvaerts | dt-bindings: soc: apple,t8140-mcc: Add the T6040 MCC [#385](https://github.com/aurora-silicon/linux/pull/385) | Not applicable — VERIFY | V | +| [35d6b26e5c9d](https://github.com/aurora-silicon/linux/commit/35d6b26e5c9d8bf22ce6284bf0594084ffd2dcdf) | Ryan Murray | soc: apple: aop: Test that a wrapped entry is refused below the read pointer [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — VERIFY; `drivers/soc/apple/aop_ring.rs` | V | +| [4b8d29e13da6](https://github.com/aurora-silicon/linux/commit/4b8d29e13da638bf7ba7f532844c5e368505bf20) | Jonathan Goyvaerts | dt-bindings: gpu: apple,agx: allow a fw-text region on the T6040 [#428](https://github.com/aurora-silicon/linux/pull/428) | Not applicable — VERIFY | V | +| [4c5aad430f62](https://github.com/aurora-silicon/linux/commit/4c5aad430f6283d1d6b3b961a87d1629b397f22f) | DjDeveloperr | dt-bindings: mailbox: apple,mailbox: Add the T8140 GPU mailboxes [#171](https://github.com/aurora-silicon/linux/pull/171) | Not applicable — VERIFY | V | +| [543ac70dce46](https://github.com/aurora-silicon/linux/commit/543ac70dce46b749f1a446cb80d4e63cf08949cd) | Ryan Murray | drm: apple: parser: add KUnit tests for malformed dictionaries [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — VERIFY; `drivers/gpu/drm/apple/Makefile` | V | +| [5b62d0c81387](https://github.com/aurora-silicon/linux/commit/5b62d0c81387e8d0d68f2a88244eeb7a618f9ec1) | Ryan Murray | mfd: macsmc: Preserve the key output on lookup failure [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — PERIPH; `drivers/mfd/macsmc.c` | V | +| [5e03e15dae17](https://github.com/aurora-silicon/linux/commit/5e03e15dae1740326b41df4777a5ee4b7502c02e), [94f215eaa642](https://github.com/aurora-silicon/linux/commit/94f215eaa6424e8f077f1447e78e35b3ebeb6542) | DjDeveloperr | dt-bindings: power: apple,t8140-pmp-report: describe report entries [#212](https://github.com/aurora-silicon/linux/pull/212) [#219](https://github.com/aurora-silicon/linux/pull/219) | Not applicable — VERIFY | V | +| [66db357bd614](https://github.com/aurora-silicon/linux/commit/66db357bd614c7f8e25dab080574c31dab6c0ed2) | Ryan Murray | tools/asahi: measure CPU cost of small render submissions [#201](https://github.com/aurora-silicon/linux/pull/201) | Not applicable — VERIFY | V | +| [6e2835ae7bd6](https://github.com/aurora-silicon/linux/commit/6e2835ae7bd6d16570464106bdfb740b6fed27c4) | Naeem Malik | soc: apple: mailbox: match the T6031 GPU mailbox without the v4 fallback | Not applicable — G15; `drivers/soc/apple/mailbox.c` | V | +| [712530b9753f](https://github.com/aurora-silicon/linux/commit/712530b9753fd8692b31382b3e9c6aaaa720ffe0) | Ryan Murray | gpio: macsmc: tidy the J700 pcIO helpers [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — PERIPH; `drivers/gpio/gpio-macsmc.c` | V | +| [7a6e327251cc](https://github.com/aurora-silicon/linux/commit/7a6e327251cc51d0a483483f0b81dda99a811791) | Ryan Murray | soc: apple: aop: Validate the receive ring before taking entries [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — PERIPH; `drivers/soc/apple/aop.rs` | V | +| [807cb8976697](https://github.com/aurora-silicon/linux/commit/807cb8976697ff272433278b272444ced7e91d57) | Ryan Murray | hwmon: macsmc: account for temperature channels in one helper [#317](https://github.com/aurora-silicon/linux/pull/317) | Not applicable — PERIPH; `drivers/hwmon/macsmc-hwmon.c` | V | +| [83039e319faf](https://github.com/aurora-silicon/linux/commit/83039e319faf3e49ceabc424c7c69180ac997dfd) | Ryan Murray | dt-bindings: apple: use the canonical SPDX form in PMP, PMC and report bindings [#317](https://github.com/aurora-silicon/linux/pull/317) | Not applicable — VERIFY | V | +| [87314d48cc08](https://github.com/aurora-silicon/linux/commit/87314d48cc080bcf535291dc636f1ec6164a06f9) | Ryan Murray | drm/apple: Test firmware message bounds with KUnit [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — VERIFY; `drivers/gpu/drm/apple/Kconfig` | V | +| [910327707f85](https://github.com/aurora-silicon/linux/commit/910327707f852c060b5ada81e64a2a4563cb64b4) | Ryan Murray | ASoC: apple: Test T8140 AOP producer reports with KUnit [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — VERIFY | V | +| [95b26591a1c4](https://github.com/aurora-silicon/linux/commit/95b26591a1c4b446656c3bc8a257a17792917a78) | Ryan Murray | spi: apple: Reword the hardware CS delay comment [#314](https://github.com/aurora-silicon/linux/pull/314) | Not applicable — VERIFY; `drivers/spi/spi-apple.c` | V | +| [966e777fd568](https://github.com/aurora-silicon/linux/commit/966e777fd568565b88c5f8ba4acb36463ae31e47) | Ryan Murray | MAINTAINERS: keep T8140 PMP coverage under Aurora [#179](https://github.com/aurora-silicon/linux/pull/179) | Not applicable — VERIFY | V | +| [a1f9aabbe6a9](https://github.com/aurora-silicon/linux/commit/a1f9aabbe6a9771a4dbf3259bc808713f0daac46) | Ryan Murray | dt-bindings: apple: describe PMP fast-die reporting on T8140 [#179](https://github.com/aurora-silicon/linux/pull/179) | Not applicable — VERIFY | V | +| [a239464d233d](https://github.com/aurora-silicon/linux/commit/a239464d233d2e94b89dea159f298b70e015b6a6) | Ryan Murray | kunit: Add an Apple firmware parser test configuration [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — VERIFY | V | +| [a27007e6ace3](https://github.com/aurora-silicon/linux/commit/a27007e6ace3815d11c340c25a3ee90cfb2981b0) | Ryan Murray | HID: dockchannel: Log routine interface set-up at debug level [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — PERIPH; `drivers/hid/dockchannel-hid/dockchannel-hid.c` | V | +| [a494d618f5a9](https://github.com/aurora-silicon/linux/commit/a494d618f5a9af40ae466166176ae36476b4638a) | Ryan Murray | mfd: macsmc: Hold the mutex while decoding key metadata [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — PERIPH; `drivers/mfd/macsmc.c` | V | +| [b8f892b79561](https://github.com/aurora-silicon/linux/commit/b8f892b79561ea085c0ab2e44368f873f15b9183) | Ryan Murray | soc: apple: aop: Decode ring entry headers without pointer casts [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — PERIPH; `drivers/soc/apple/aop.rs` | V | +| [ba6eb20e2ed9](https://github.com/aurora-silicon/linux/commit/ba6eb20e2ed9065a2925d0f16ae030acfaeceefe) | Ryan Murray | soc: apple: mailbox: Describe the ASCWrap v6 CPU start and stop in hardware terms | Not applicable — VERIFY; `drivers/soc/apple/mailbox.c` | V | +| [bc7394673cbe](https://github.com/aurora-silicon/linux/commit/bc7394673cbed4ef22971a49926be6070a7312be) | DjDeveloperr | tools: preserve bindgen recipe validation across Python modes [#171](https://github.com/aurora-silicon/linux/pull/171) | Not applicable — VERIFY | V | +| [bd7739d6232e](https://github.com/aurora-silicon/linux/commit/bd7739d6232e818ef7cf30acbcc50d24723e061c) | Ryan Murray | mfd: macsmc: Reject short fixed-width replies [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — PERIPH; `drivers/mfd/macsmc.c` | V | +| [c49ab96deb3c](https://github.com/aurora-silicon/linux/commit/c49ab96deb3c378015d1db27bf2122c837fd3bc0) | DjDeveloperr | dt-bindings: gpu: apple,agx: Add apple,agx-t8140 [#171](https://github.com/aurora-silicon/linux/pull/171) | Not applicable — VERIFY | V | +| [c9c779d6a2ba](https://github.com/aurora-silicon/linux/commit/c9c779d6a2bae406a9d48b7f733b16fd253730c7) | Jonathan Goyvaerts | dt-bindings: soc: apple,pmp: Add the T6040 PMP [#374](https://github.com/aurora-silicon/linux/pull/374) | Not applicable — VERIFY | V | +| [cbd157ef2a12](https://github.com/aurora-silicon/linux/commit/cbd157ef2a12a46e3caf5367de912a525d8d4874) | Ryan Murray | hwmon: macsmc: keep the channel of a temperature sensor that fails to set up [#317](https://github.com/aurora-silicon/linux/pull/317) | Not applicable — PERIPH; `drivers/hwmon/Kconfig` | V | +| [cf4ce5f41f4b](https://github.com/aurora-silicon/linux/commit/cf4ce5f41f4b86c4084b49ec6585377e16fd53b7) | Chris Kearney | thunderbolt, phy, soc: apple: T6030 USB4/Thunderbolt, Type-C and DP tunnel support | Not applicable — G15; `drivers/iommu/of_iommu.c` | V | +| [d36196335065](https://github.com/aurora-silicon/linux/commit/d36196335065346ef57a323e483002151670320c) | Ryan Murray | arm64: dts: apple: t6040-j616s: Keep the GPU disabled [#420](https://github.com/aurora-silicon/linux/pull/420) | Not applicable — SUPERSEDED | B | +| [d717a601430a](https://github.com/aurora-silicon/linux/commit/d717a601430aea6711ffc7486f5edb16ca7e7f66) | Jonathan Goyvaerts | dt-bindings: gpu: apple,agx: Add apple,agx-t6040 [#420](https://github.com/aurora-silicon/linux/pull/420) | Not applicable — VERIFY | V | +| [d90daf04de84](https://github.com/aurora-silicon/linux/commit/d90daf04de8431f70eed8b590a532f38a6249095) | Ryan Murray | media: apple: isp: Do not trust the batch count when a batch fails [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — PERIPH; `drivers/media/platform/apple/isp/isp-v4l2.c` | V | +| [d9aface7ea85](https://github.com/aurora-silicon/linux/commit/d9aface7ea8533965e33476631c8dbc44dfeb439) | Ryan Murray | HID: dockchannel: Build the protocol KUnit test only with the driver [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — PERIPH; `drivers/hid/dockchannel-hid/Kconfig` | V | +| [e65283c42678](https://github.com/aurora-silicon/linux/commit/e65283c426783b12298f72896ef6f0dc33c4222c) | Ryan Murray | ASoC: apple: t8140-aop-audio: Let power request sequences wrap [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — PERIPH | V | +| [e666e50ccfe8](https://github.com/aurora-silicon/linux/commit/e666e50ccfe8c58e8fa72bdf7ef6bedffec6551a) | Ryan Murray | rtc: macsmc: correct the comment on the whole-second offset [#317](https://github.com/aurora-silicon/linux/pull/317) | Not applicable — PERIPH; `drivers/rtc/rtc-macsmc.c` | V | +| [e99a6adbeba7](https://github.com/aurora-silicon/linux/commit/e99a6adbeba77b7ed032ef61f864afb3da8fe3a9) | Ryan Murray | dt-bindings: hwmon: apple,smc-hwmon: drop apple,cpu-thermal-policy [#317](https://github.com/aurora-silicon/linux/pull/317) | Not applicable — VERIFY | V | +| [ec34fb1f2835](https://github.com/aurora-silicon/linux/commit/ec34fb1f2835b291f978bd091ed73347cba7ceae) | Ryan Murray | mfd: macsmc: Reject out-of-range key indices [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — PERIPH; `drivers/mfd/macsmc.c` | V | +| [ed0925cdf091](https://github.com/aurora-silicon/linux/commit/ed0925cdf091083ba1efee97bbb288e9c331f812) | Ryan Murray | spi: apple-qspi: Restore the controller configuration after system sleep [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — PERIPH; `drivers/spi/spi-apple-qspi.c` | V | +| [ee6f48ce60bd](https://github.com/aurora-silicon/linux/commit/ee6f48ce60bdf68d596c4bedd31e35f55cd6bb03) | Ryan Murray | MAINTAINERS: Cover Apple firmware parser tests [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — VERIFY | V | +| [f3324e0de0e2](https://github.com/aurora-silicon/linux/commit/f3324e0de0e26bd43df402001c2e3f0f16821520) | Jonathan Goyvaerts | dt-bindings: power: apple,pmc: Add the T6040 PMC and CLPC [#385](https://github.com/aurora-silicon/linux/pull/385) | Not applicable — VERIFY | V | +| [f52e01d6925d](https://github.com/aurora-silicon/linux/commit/f52e01d6925de5022176c5038ff13cc9ad17559b) | Ryan Murray | dt-bindings: gpu: apple,agx: Match the T8140 register layout [#314](https://github.com/aurora-silicon/linux/pull/314) | Not applicable — VERIFY | V | +| [fdd138bbc722](https://github.com/aurora-silicon/linux/commit/fdd138bbc7228b8b1a232268e2a02cc126b24d95) | Ryan Murray | hwmon: macsmc: move the module help text back to its option [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — PERIPH; `drivers/hwmon/Kconfig` | V | +| [fec5e40ebade](https://github.com/aurora-silicon/linux/commit/fec5e40ebade9f7cf3684acfbb9d603d5b82127a) | Jonathan Goyvaerts | dt-bindings: power: apple,t8140-pmp-report: Add the T6040 aperture [#374](https://github.com/aurora-silicon/linux/pull/374) | Not applicable — VERIFY | V | +| [ff3ebdbad58c](https://github.com/aurora-silicon/linux/commit/ff3ebdbad58c1c50dadbf9cecd7f67aada516dc1) | Ryan Murray | nvme-apple: Reword two post-M4 handoff comments [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — VERIFY; `drivers/nvme/host/apple.c` | V | + +### Merge/provenance records + +Parent mechanisms are listed above; no isolated speedup is assigned here. + +| Commits / encountered aliases | Author | Improvement / record | T6040 after ports — evidence key and changed source | Measurement | +| --- | --- | --- | --- | --- | +| [00463d1d4031](https://github.com/aurora-silicon/linux/commit/00463d1d40317155246a13573aeab60ff773b51b), [f566e6098c67](https://github.com/aurora-silicon/linux/commit/f566e6098c676566b02a191eb58e5783fa7bf0b6) | Janne Grunau | Merge branch 'bits/010-soc' into asahi-wip | Not applicable — MERGE | V | +| [80390ef034fc](https://github.com/aurora-silicon/linux/commit/80390ef034fcfaf212674bc53b0a91e596409bcf), [5e35a79dac7f](https://github.com/aurora-silicon/linux/commit/5e35a79dac7f0958af6bd3ae9ea4517d22769cb2) | Janne Grunau | Merge branch 'bits/020-dart' into asahi-wip | Not applicable — MERGE | V | +| [4eea6c6d47ba](https://github.com/aurora-silicon/linux/commit/4eea6c6d47bab3a334b9c1633279c4392e2a4de7), [57710743dc8c](https://github.com/aurora-silicon/linux/commit/57710743dc8c0e9286115f38ad19a298cd36cc50) | Janne Grunau | Merge branch 'bits/030-misc' into asahi-wip | Not applicable — MERGE | V | +| [59f7e8be4e3b](https://github.com/aurora-silicon/linux/commit/59f7e8be4e3b693a96a1dd3b2355aa65dd784b0e), [466efd223bbf](https://github.com/aurora-silicon/linux/commit/466efd223bbf43f9e0f4f7065c67d6d4e97fc04c) | Janne Grunau | Merge branch 'bits/090-spi-hid' into asahi-wip | Not applicable — MERGE | V | +| [2871ed01752e](https://github.com/aurora-silicon/linux/commit/2871ed01752ef74924608f23b5edd7927ae4b71c), [f55090a2d12a](https://github.com/aurora-silicon/linux/commit/f55090a2d12a6fd3dad485a7621392b8f508f8a8) | Janne Grunau | Merge branch 'bits/190-rust' into asahi-wip | Not applicable — MERGE | V | +| [cd36abb1e1d2](https://github.com/aurora-silicon/linux/commit/cd36abb1e1d2bcead322aedaed3cab7cbbab5d4b), [3f3dfd6ef171](https://github.com/aurora-silicon/linux/commit/3f3dfd6ef171f4c3801e1bd8b7c880774e17f1ad) | Janne Grunau | Merge branch 'bits/210-gpu' into asahi-wip | Not applicable — MERGE | V | +| [d6c7409e354f](https://github.com/aurora-silicon/linux/commit/d6c7409e354fee96c74a0aa68c5cbd0cdc0b8d92), [39b401626267](https://github.com/aurora-silicon/linux/commit/39b4016262672c0f87d7e5af835f42b9632dcc6c) | Janne Grunau | Merge branch 'bits/250-aop' into asahi-wip | Not applicable — MERGE | V | +| [072195f87df8](https://github.com/aurora-silicon/linux/commit/072195f87df8ca94580cf5ca0d9a801e5bd217e4), [11ee430b3cf2](https://github.com/aurora-silicon/linux/commit/11ee430b3cf20a6611bea0462335f0ae975a40d7) | Janne Grunau | Merge branch 'bits/500-backports' into asahi-wip | Not applicable — MERGE | V | +| [fe8324184dd8](https://github.com/aurora-silicon/linux/commit/fe8324184dd8c6d3b09a532a9cb6a533c561a324) | Ryan Murray | Merge tag 'v7.3' into unified-7.3/base | Not applicable — MERGE | V | +| [cb8e5e495b88](https://github.com/aurora-silicon/linux/commit/cb8e5e495b88f638c22782136a8721a4f26f4346) | Ryan Murray | Merge branch 'unified/bits/010-soc' into rebase-wip/7.2.10-unified | Not applicable — MERGE | V | +| [6de20e534e47](https://github.com/aurora-silicon/linux/commit/6de20e534e473dd9efd9e14c727883df6036d60b) | Ryan Murray | Merge branch 'unified/bits/020-dart' into rebase-wip/7.2.10-unified | Not applicable — MERGE | V | +| [15fe1cbe72df](https://github.com/aurora-silicon/linux/commit/15fe1cbe72df8d8eb79b2dec500e58bbc5f57647) | Ryan Murray | Merge branch 'unified/bits/060-power-thermal' into rebase-wip/7.2.10-unified | Not applicable — MERGE | V | +| [939cccf33380](https://github.com/aurora-silicon/linux/commit/939cccf333802c32a319f0ddb4b0d01c85aab990) | Ryan Murray | Merge branch 'unified/bits/070-audio' into rebase-wip/7.2.10-unified | Not applicable — MERGE | V | +| [2409e9da1efb](https://github.com/aurora-silicon/linux/commit/2409e9da1efb9494fc9437ce9f3bd909c0f7dd36) | Ryan Murray | Merge branch 'unified/bits/100-sep' into rebase-wip/7.2.10-unified | Not applicable — MERGE | V | +| [2bc8aa67f178](https://github.com/aurora-silicon/linux/commit/2bc8aa67f17817c22c6bacaecd7ecf4df8e8c63d) | Ryan Murray | Merge branch 'unified/bits/200-dcp' into rebase-wip/7.2.10-unified | Not applicable — MERGE | V | +| [c00586c09e2b](https://github.com/aurora-silicon/linux/commit/c00586c09e2bab26f92ddd5b943cbcc66c047ff5) | Ryan Murray | Merge branch 'unified/bits/210-gpu' into rebase-wip/7.2.10-unified | Not applicable — MERGE | V | +| [14ca49f98175](https://github.com/aurora-silicon/linux/commit/14ca49f98175b54c37fa16af766e11f163d3a80f) | Ryan Murray | Merge branch 'unified-7.3/bits/010-soc' into unified-7.3/canonical | Not applicable — MERGE | V | +| [665a5ca84508](https://github.com/aurora-silicon/linux/commit/665a5ca845081049335f69d0bf27c66c90898b7b) | Ryan Murray | Merge branch 'unified-7.3/bits/020-dart' into unified-7.3/canonical | Not applicable — MERGE | V | +| [c217f4f47bf0](https://github.com/aurora-silicon/linux/commit/c217f4f47bf003c8fb39400d1af8179b552c497e) | Ryan Murray | Merge branch 'unified-7.3/bits/060-power-thermal' into unified-7.3/canonical | Not applicable — MERGE | V | +| [5bdd6c5b2c80](https://github.com/aurora-silicon/linux/commit/5bdd6c5b2c80a6f16c8835a57a5d56d26af67839) | Ryan Murray | Merge branch 'unified-7.3/bits/070-audio' into unified-7.3/canonical | Not applicable — MERGE | V | +| [f3c8cf25dd5d](https://github.com/aurora-silicon/linux/commit/f3c8cf25dd5d01f3871267c1b77df343dbef045d) | Ryan Murray | Merge branch 'unified-7.3/bits/100-sep' into unified-7.3/canonical | Not applicable — MERGE | V | +| [c15790e20e01](https://github.com/aurora-silicon/linux/commit/c15790e20e01e8562e0b54a00bee8a8ef9ded84b) | Ryan Murray | Merge branch 'unified-7.3/bits/200-dcp' into unified-7.3/canonical | Not applicable — MERGE | V | +| [ea36d4240e40](https://github.com/aurora-silicon/linux/commit/ea36d4240e40fc7607d568a8fa56f1fa5cf01c03) | Ryan Murray | Merge branch 'unified-7.3/bits/210-gpu' into unified-7.3/canonical | Not applicable — MERGE | V | +| [94141d397eae](https://github.com/aurora-silicon/linux/commit/94141d397eaede2ba95eab7279f4bc18f75bb9d1) | Ryan | Merge pull request #374 from jgoyvaerts/m4pro/pmp-t6041 | Not applicable — MERGE | V | +| [4e5eea54ce7e](https://github.com/aurora-silicon/linux/commit/4e5eea54ce7e1c9d21867d81c5e5672e1cbd1c34) | Ryan | Merge pull request #368 from jgoyvaerts/m4pro/small-fixes | Not applicable — MERGE | V | +| [815438428a9e](https://github.com/aurora-silicon/linux/commit/815438428a9e45f0facbb10c4f9bd77047ef7282) | Ryan | Merge pull request #385 from jgoyvaerts/m4pro/pmc-clpc-mcc-t6040 | Not applicable — MERGE | V | +| [49273fd6921c](https://github.com/aurora-silicon/linux/commit/49273fd6921c9b39101cd1ee8c8d2f2ca0153440) | Ryan | Merge pull request #386 from jgoyvaerts/m4pro/apple-dart-t6040 | Not applicable — MERGE | V | +| [50a912fe81fc](https://github.com/aurora-silicon/linux/commit/50a912fe81fcdbd770d231db8eda856a1678c194) | Ryan | Merge pull request #391 from jgoyvaerts/m4pro/g17-generic | Not applicable — MERGE | V | +| [311471f73e99](https://github.com/aurora-silicon/linux/commit/311471f73e9919764bfb24acfe7eeb7c5230795a) | Ryan | Merge pull request #402 from aurora-silicon/j616s/pmp-ep7-20261024 | Not applicable — MERGE | V | +| [1aa2857509d5](https://github.com/aurora-silicon/linux/commit/1aa2857509d50f7f787541280aba7b274721919c) | Ryan | Merge pull request #403 from aurora-silicon/j616s/usb-20261023 | Not applicable — MERGE | V | +| [04231230fd76](https://github.com/aurora-silicon/linux/commit/04231230fd76fadd12a8b44707159f37f0e498b1), [e9a9b647d0f3](https://github.com/aurora-silicon/linux/commit/e9a9b647d0f36e222f16de732f78f2d77ea2d661), [b9a73d410ff4](https://github.com/aurora-silicon/linux/commit/b9a73d410ff48c65624c9283c634b4d06f4dfc6a), [4433181e4eb6](https://github.com/aurora-silicon/linux/commit/4433181e4eb649033f07751001fbb72621752647) | Jonathan Goyvaerts, Ryan Murray | Merge integration/aurora into the T6040 GPU series [#420](https://github.com/aurora-silicon/linux/pull/420) | Not applicable — MERGE | V | +| [2a868cf22d90](https://github.com/aurora-silicon/linux/commit/2a868cf22d90f0de72b2ad3c339b93460fdb9927) | Ryan | Merge pull request #413 from jgoyvaerts/m4pro/g17-robustness | Not applicable — MERGE | V | +| [2e9e457c922d](https://github.com/aurora-silicon/linux/commit/2e9e457c922d799dcc27975ff6f5d114d2719f3f) | Ryan | Merge pull request #418 from aurora-silicon/ryan/g17-innocent-clients-413 | Not applicable — MERGE | V | +| [127df1be31af](https://github.com/aurora-silicon/linux/commit/127df1be31af90613079dcedaeaf0a2aa9ae7d10) | Ryan Murray | Merge integration/aurora into aurora-wip-sync-177 | Not applicable — MERGE | V | +| [e5dad6df06f6](https://github.com/aurora-silicon/linux/commit/e5dad6df06f66b944037770e81e6a481a0b8302f), [d1b62dc403c6](https://github.com/aurora-silicon/linux/commit/d1b62dc403c6f9ed534850f204f069befca149f1) | Ryan Murray | Merge integration/aurora into m4pro/g17-t6040-text-identity [#428](https://github.com/aurora-silicon/linux/pull/428) | Not applicable — MERGE | V | +| [a1b269f79e35](https://github.com/aurora-silicon/linux/commit/a1b269f79e35844660892b9779adb2f68abc38b7) | Ryan | Merge pull request #395: sync omacom #177 (G15, T6030 dock, platform fixes) onto 7.3 | Not applicable — MERGE | V | +| [8bc8f12e95ed](https://github.com/aurora-silicon/linux/commit/8bc8f12e95edbbd5b7d5431973eb1f08c872e87c) | Ryan | Merge pull request #427 from jgoyvaerts/m4pro/asahi-pgtable-checked-borrow | Not applicable — MERGE | V | +| [18012befd69c](https://github.com/aurora-silicon/linux/commit/18012befd69ceed9892c28291132ebd4c05a6076) | Ryan | Merge pull request #428 from jgoyvaerts/m4pro/g17-t6040-text-identity | Not applicable — MERGE | V | +| [41ef596ab373](https://github.com/aurora-silicon/linux/commit/41ef596ab3731ef695fcce2fdfb666bc47da21b7) | Dj | Merge pull request #443 from aurora-silicon/fix/t8140-cpufreq-lazy-verify-20261025 | Not applicable — MERGE | V | +| [af80fbe9094b](https://github.com/aurora-silicon/linux/commit/af80fbe9094b631c5d734883bd259ad0925e88ba) | Dj | Merge pull request #447 from aurora-silicon/fix/aic2-effective-affinity-20261025 | Not applicable — MERGE | V | +| [67fdc27bdadb](https://github.com/aurora-silicon/linux/commit/67fdc27bdadb58520839376a2b85d1265c3fa78f), [b4e5e4cff710](https://github.com/aurora-silicon/linux/commit/b4e5e4cff710c9961a05d4ba6c16f0f8c47c3776) | Chris Kearney | Merge current aurora-wip into the combined Thunderbolt series | Not applicable — MERGE | V | +| [99a554596d0a](https://github.com/aurora-silicon/linux/commit/99a554596d0a4960120b78f9a09ab30ffd4d0fc1) | Chris Kearney | Merge aurora-wip 3bb0a6104a11 into tb-dp-tunnel-t8103 | Not applicable — MERGE | V | +| [5b55206ca89b](https://github.com/aurora-silicon/linux/commit/5b55206ca89bbeb8bb54a208a9b9a36b80d6360b) | Acelogic | Merge M5 CPU support while retaining per-chip frequency safeguards | Not applicable — MERGE | V | +| [95f70d759093](https://github.com/aurora-silicon/linux/commit/95f70d7590935fdfb63c889d73598996dbf6c695) | Ryan Murray | Merge pull request #172 from aurora-silicon/j700-dcp-foundation | Not applicable — MERGE | V | +| [b37df4bdd552](https://github.com/aurora-silicon/linux/commit/b37df4bdd552dca6fa180e7826d0ec22b5e264f4) | Ryan Murray | Merge pull request #8 from iconidentify/tb-dp-tunnel-t8103 | Not applicable — MERGE | V | +| [c9a55efdff8f](https://github.com/aurora-silicon/linux/commit/c9a55efdff8f9505114fab2c89e934ce363e9b3b) | Ryan Murray | Merge pull request #42 from aurora-silicon/feat/sep-neo-rebase | Not applicable — MERGE | V | +| [026b4131cf05](https://github.com/aurora-silicon/linux/commit/026b4131cf0506b0974e6a0ad0b13cfa15f80cc6) | Ryan Murray | Merge pull request #43 from aurora-silicon/feat/j700-thermal-framework-v2 | Not applicable — MERGE | V | +| [e9c631c62252](https://github.com/aurora-silicon/linux/commit/e9c631c6225261125aae251b6a0b743e0663ed37) | Ryan Murray | Merge pull request #46 from aurora-silicon/fix/j414s-dual-dock-dp | Not applicable — MERGE | V | +| [b8fef4b5b0b5](https://github.com/aurora-silicon/linux/commit/b8fef4b5b0b5d1ed5401db1249fc230fc9ce3f60) | Ryan Murray | Merge pull request #156 from aurora-silicon/codex/neo-ave-bringup | Not applicable — MERGE | V | +| [527179cf0856](https://github.com/aurora-silicon/linux/commit/527179cf085663164d17e4d8e8abf2c3e33b5070) | Ryan Murray | Merge pull request #168 from joshuaswarren/agent/jw16-agx-stats4 | Not applicable — MERGE | V | +| [918c0ff6270c](https://github.com/aurora-silicon/linux/commit/918c0ff6270c673e67067453a2e727b5f3e26331) | Ryan Murray | Merge pull request #55 from aurora-silicon/t8140-cpu-capacity | Not applicable — MERGE | V | +| [db4752825c93](https://github.com/aurora-silicon/linux/commit/db4752825c935257fb77f6b8aacf3b4ee003e9fa) | Ryan Murray | Merge pull request #174 from aurora-silicon/j700/t8140-dt-complete | Not applicable — MERGE | V | +| [d8ad51f6b8c8](https://github.com/aurora-silicon/linux/commit/d8ad51f6b8c809444fe4e3f1ef0a1ebb3ba6af5e) | Ryan Murray | Merge pull request #179 from aurora-silicon/j700/pmp-mcc-thermal | Not applicable — MERGE | V | +| [5de43d36cd52](https://github.com/aurora-silicon/linux/commit/5de43d36cd520eaf5df6fc16d047ee413ac54968) | Ryan Murray | Merge pull request #181 from aurora-silicon/j700/t8140-ane-topology | Not applicable — MERGE | V | +| [564976d8e4a6](https://github.com/aurora-silicon/linux/commit/564976d8e4a60dd64a4dc77b2b82f2a0f25f3f45) | Ryan Murray | Merge pull request #182 from aurora-silicon/j700/mcc-telemetry | Not applicable — MERGE | V | +| [4f3a64e7baa9](https://github.com/aurora-silicon/linux/commit/4f3a64e7baa91c6616ae955695e8a5c847dff968) | Ryan | Merge pull request #215 from aurora-silicon/j700/t8140-slc-allocation | Not applicable — MERGE | V | +| [ccedac0c6a30](https://github.com/aurora-silicon/linux/commit/ccedac0c6a30b11b9b1926522a98d4079c9acbf1) | Ryan | Merge pull request #221 from aurora-silicon/j700/display-handoff-race | Not applicable — MERGE | V | +| [123c0d909a8a](https://github.com/aurora-silicon/linux/commit/123c0d909a8af07363e3e885691f0e9feb10c9bc) | Ryan | Merge pull request #223 from aurora-silicon/apple/sashiko-pmp-entry-unbind | Not applicable — MERGE | V | +| [1702229de78f](https://github.com/aurora-silicon/linux/commit/1702229de78f1acd92808b228034839fb7e6c0bc) | Ryan | Merge pull request #232 from aurora-silicon/j700/aop-dart-invalidate | Not applicable — MERGE | V | +| [5c06b1998b8e](https://github.com/aurora-silicon/linux/commit/5c06b1998b8eefdf785b5320a6f8cec31f8da29a) | Ryan | Merge pull request #272 from aurora-silicon/j700/pmgr-failed-power-on | Not applicable — MERGE | V | +| [92ad2a772af8](https://github.com/aurora-silicon/linux/commit/92ad2a772af82a81fc3c73c915a48b9b611db55b) | Ryan Murray | Merge public J700 DP Alt qualification stack into current aurora-wip | Not applicable — MERGE | V | +| [f5afac94ca29](https://github.com/aurora-silicon/linux/commit/f5afac94ca2999c858fb61ebab0bf4b4c168fc6d) | Ryan Murray | drm/apple: compose lean J700 display and automatic recovery candidate | Not applicable — MERGE | V | +| [08e0d0a90be3](https://github.com/aurora-silicon/linux/commit/08e0d0a90be3bb6d777cc1a285ba22bfce12a4d9) | Ryan Murray | drm/apple: integrate reviewed fixed-host lifecycle and scoped boot fixes | Not applicable — MERGE | V | +| [47c99edf4878](https://github.com/aurora-silicon/linux/commit/47c99edf48782ec6078624f579108e243f8cbb4c) | Ryan | Merge pull request #342 from aurora-silicon/cleanup/rust-clippy-20261017 | Not applicable — MERGE | V | +| [3abf4a56b59e](https://github.com/aurora-silicon/linux/commit/3abf4a56b59e323026f9a6e2fe9a0d4307c251d5) | Ryan | Merge pull request #345 from aurora-silicon/fix/harden-all-20261017 | Not applicable — MERGE | V | +| [c20b9d96ba77](https://github.com/aurora-silicon/linux/commit/c20b9d96ba77da9a32c751ab0e63f283d10eea4a) | Ryan Murray | Merge branch 'integration/aurora' into aurora-wip | Not applicable — MERGE | V | +| [8080772b8377](https://github.com/aurora-silicon/linux/commit/8080772b83773a6ca224718de3fdb8bddc1b8511) | Ryan Murray | Merge branch 'integration/aurora' at a1b269f7 into aurora-wip | Not applicable — MERGE | V | +| [42c59170fe98](https://github.com/aurora-silicon/linux/commit/42c59170fe98eb0266c4ca5ea5e1abd0cbac97aa) | Ryan Murray | Merge integration/aurora at 3b77f0f9 into aurora-wip | Not applicable — MERGE | V | +| [1a3c5ca41f8f](https://github.com/aurora-silicon/linux/commit/1a3c5ca41f8f0215a355625ad7c4d456c78f2802) | Ryan Murray | Merge branch 'unified-7.3/bits/170-usb' into unified-7.3/canonical | Not applicable — MERGE | V | +| [2e1656f93c27](https://github.com/aurora-silicon/linux/commit/2e1656f93c27f76171fa265dadcb5ba019bce421) | Ryan Murray | Merge pull request #65 from joshuaswarren/ane-dt-polltx-aurora | Not applicable — MERGE | V | +| [9dbd46baf1b2](https://github.com/aurora-silicon/linux/commit/9dbd46baf1b289a30b5f9be6c4b5f6341135d7ce) | Ryan | Merge pull request #198 from aurora-silicon/fix/pr171-review [#171](https://github.com/aurora-silicon/linux/pull/171) | Not applicable — MERGE | V | +| [f1f334c2ec72](https://github.com/aurora-silicon/linux/commit/f1f334c2ec724b8c96f534a561df9480aa4310ee) | Ryan Murray | Merge branch 'backlog/kunit-parsers-fixes-20261017' into backlog/harden-all-20261017 [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — MERGE | V | + +### Public measured follow-ups outside the audited branch heads + +[#464](https://github.com/aurora-silicon/linux/pull/464), +[#468](https://github.com/aurora-silicon/linux/pull/468) and +[#494](https://github.com/aurora-silicon/linux/pull/494) were also read. Their +heads are not in either pinned branch snapshot; they are not silently counted +as inherited or qualified ports. They remain separate proposals for review: + +- #464 limits the T8140 GPU floor to streams and yields to PMP demand. Its + reported video power is 5.151 to 4.975 W, with light-submit gains retained. + Check the same video/browsing/heavy-load failure modes on g2, including actual + T6040 PMP voter behavior, before accepting its unconditional powered floor. +- #468 reports 7409 to 4625 doorbells in a traced hop, slow hops 19.6% to 13.9%, + and C-d64 13.89 to 13.11 us. Its shared compute-doorbell mechanism would need + no T6040 feature gate; it is an additional unmerged source dependency. +- #494 reports event passes 8.2 to 6.2 us and submit.empty d2 1.78 to 1.69 us. + Its shared event housekeeping change is another unmerged source dependency; + it is not evidence that the audited branches already provide those gains. + +No register, voltage, slot-key address, power curve, module parameter or new +platform option is invented to close an evidence gap. Applicability of dormant +25G83-specific feature/identity paths must come from validated facts; successful +compilation is not that qualification. From fcd39d542f865e7a0ae744da44748897dfdee75d Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Tue, 27 Oct 2026 14:45:56 +1100 Subject: [PATCH 092/184] drm/asahi: require a compute stamp for record fallback across recovery An exact completion record that has aged for 50 ms permits compute retirement without a stamp in the ordinary generation. The recovery path can consume queue items without completing command writes; those cursors and an old record alone must not release command mappings. Restrict the record-only fallback to the publication's generation. After a restart, retain the command until a covering stamp and consumed items provide the release witness, or the existing quarantine/stop path takes ownership. Keep interrupted fence classification independent. Exercise aged records with missing, stale, exact and later stamps across a restart, consumed and busy rings, and the ordinary fallback control. This closes a retention hazard; it does not establish the cause of the T6040 CTS page faults or qualify recovery on that firmware. Assisted-by: LLM Signed-off-by: Ryan Murray --- .../drm/asahi/g17/queue/compute/retirement.rs | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/asahi/g17/queue/compute/retirement.rs b/drivers/gpu/drm/asahi/g17/queue/compute/retirement.rs index c2b49e7b436d7..3f0246047e845 100644 --- a/drivers/gpu/drm/asahi/g17/queue/compute/retirement.rs +++ b/drivers/gpu/drm/asahi/g17/queue/compute/retirement.rs @@ -106,7 +106,9 @@ pub(crate) fn note_stampless(qid: u8, kick: u64) { /// A zero-duration record fails before visibility polling. It proves execution did not occur; /// ownership still requires a separate retirement witness before mappings can be released. /// The command is complete when its stamp covers it, or when its exact completion record has -/// been present for [`STAMP_FALLBACK_NS`] without one (`record_aged`); in both cases its items +/// been present for [`STAMP_FALLBACK_NS`] without one (`record_aged`) in the same recovery +/// generation. Recovery may consume items without completing their writes, so an old record +/// and consumed items cannot replace the stamp across a restart. In both cases its items /// must be consumed. #[expect( clippy::too_many_arguments, @@ -125,7 +127,9 @@ pub(crate) fn poll( if observed.is_some_and(|record| record.timestamps[0] == record.timestamps[1]) { return Err(ENODATA); } - let completed = stamp_covers(stamp, ticket.kick) || (observed.is_some() && record_aged); + let same_generation = !matches!(recordless, Recordless::CrossedRecovery); + let completed = stamp_covers(stamp, ticket.kick) + || (same_generation && observed.is_some() && record_aged); if !completed || !cursors.consumed(ticket.item_producer, ticket.item_count)? { return Ok(None); } @@ -527,6 +531,30 @@ mod tests { assert_eq!(result, Ok(None)); } + #[test] + fn recovery_cannot_turn_an_aged_record_into_a_release_witness() { + let record = Some(Observation { timestamps: [10, 20] }); + // Recovery can advance the consumer without completing cache maintenance. + // Even a record observed before the restart and aged past the fallback + // must retain its command's mappings until a real stamp covers it. + for stamp in [0, own(KICK - 1)] { + assert_eq!(poll(&ticket(), record, consumed(), stamp, + Recordless::CrossedRecovery, 0, None, true), Ok(None)); + } + // A real stamp restores the release witness; completion's interrupted + // classification independently prevents false successful fences. + for stamp in [own(KICK), own(KICK + 1)] { + assert_eq!(poll(&ticket(), record, consumed(), stamp, + Recordless::CrossedRecovery, 0, None, true), Ok(Some([10, 20]))); + } + let busy = Cursors { producer: 4, consumer: 2, count: 64 }; + assert_eq!(poll(&ticket(), record, busy, own(KICK), + Recordless::CrossedRecovery, 0, None, true), Ok(None)); + // The existing same-generation fallback remains available. + assert_eq!(poll(&ticket(), record, consumed(), own(KICK - 1), + Recordless::Allowed, 0, None, true), Ok(Some([10, 20]))); + } + #[test] fn a_zero_duration_record_fails() { let record = Some(Observation { timestamps: [5, 5] }); From 90150b09b82066a3104df8bb7ec3a1dfeffc06c3 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Tue, 27 Oct 2026 14:46:45 +1100 Subject: [PATCH 093/184] drm/asahi: retain recovery progress when slot keys are unknown The 25G83 path has no validated slot-key address. It consequently lost progress observations too, even though they come from a separately validated firmware-owned view. Read those observations with an unknown key represented by zero; preserve firmware blame when ownership is unknown or a read fails. Report the bounded halted-firmware snapshot with its generation, reason, slot mask, known-key count and per-slot progress. Identify stampless retirement counters explicitly as lifetime totals so they cannot be mistaken for commands retired by a single recovery. Read no new MMIO addresses and change no firmware timeout, feature or voltage policy. Test unknown ownership, incomplete progress and page-fault fallback. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/gpu/drm/asahi/g17/initdata.rs | 5 +- .../drm/asahi/g17/queue/compute/retirement.rs | 6 +- drivers/gpu/drm/asahi/g17/recovery.rs | 1 + .../gpu/drm/asahi/g17/recovery/attribution.rs | 58 +++++++++++++++++++ 4 files changed, 68 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/asahi/g17/initdata.rs b/drivers/gpu/drm/asahi/g17/initdata.rs index 3b224e330cdcb..c8763f40871d1 100644 --- a/drivers/gpu/drm/asahi/g17/initdata.rs +++ b/drivers/gpu/drm/asahi/g17/initdata.rs @@ -1563,7 +1563,10 @@ impl super::recovery::Memory for InitData { .dword(self.status_b + abi::PrimaryStatusB::RECOVERY_SLOT_MASK)? .load(Ordering::Relaxed); let mut sources = super::recovery::Sources::sample(mask as u32, |slot| { - let key = super::recovery::slot_key(slot, self.recovery_slot_keys)?; + // Progress is independently available even when this firmware's key + // address is unknown. Zero keeps ownership unknown and cannot override + // firmware blame, but preserves the progress evidence for diagnostics. + let key = super::recovery::slot_key(slot, self.recovery_slot_keys).unwrap_or(0); let monitor = self.recovery_view_words::<6>(abi::recovery::PROGRESS_VIEW, abi::recovery::PROGRESS_START + slot * abi::recovery::PROGRESS_STRIDE) .ok().map(|words| words[0])?; diff --git a/drivers/gpu/drm/asahi/g17/queue/compute/retirement.rs b/drivers/gpu/drm/asahi/g17/queue/compute/retirement.rs index 3f0246047e845..c132b87a9860a 100644 --- a/drivers/gpu/drm/asahi/g17/queue/compute/retirement.rs +++ b/drivers/gpu/drm/asahi/g17/queue/compute/retirement.rs @@ -88,13 +88,17 @@ pub(crate) fn record_aged(observed_at: Option, now: u64) -> bool { static STAMPLESS: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0); +pub(crate) fn stampless_total() -> u64 { + STAMPLESS.load(core::sync::atomic::Ordering::Relaxed) +} + /// Counts a command that retired on its record because its completion stamp never arrived; /// logs the first and every 64th. pub(crate) fn note_stampless(qid: u8, kick: u64) { let count = STAMPLESS.fetch_add(1, core::sync::atomic::Ordering::Relaxed) + 1; if count == 1 || count % 64 == 0 { pr_info!( - "G17: {} compute commands retired on their completion record after {} ms without a completion stamp (latest: queue {} kick {:#x})\n", + "G17: lifetime total {} compute commands retired on their completion record after {} ms without a completion stamp (latest: queue {} kick {:#x})\n", count, STAMP_FALLBACK_NS / 1_000_000, qid, diff --git a/drivers/gpu/drm/asahi/g17/recovery.rs b/drivers/gpu/drm/asahi/g17/recovery.rs index 3bb9d20190b57..8bffec476c9ac 100644 --- a/drivers/gpu/drm/asahi/g17/recovery.rs +++ b/drivers/gpu/drm/asahi/g17/recovery.rs @@ -244,6 +244,7 @@ pub(super) fn service( host.clear_timestamps()?; } let mut sources = host.fault_sources()?; + sources.report(request.generation, super::queue::compute::retirement::stampless_total()); let target_error = host.publish_recovery_targets(); host.transition(1, 2)?; let mut recovered = false; diff --git a/drivers/gpu/drm/asahi/g17/recovery/attribution.rs b/drivers/gpu/drm/asahi/g17/recovery/attribution.rs index 1755484219187..be1c7ab9eb662 100644 --- a/drivers/gpu/drm/asahi/g17/recovery/attribution.rs +++ b/drivers/gpu/drm/asahi/g17/recovery/attribution.rs @@ -30,6 +30,23 @@ pub(super) enum Attribution { } impl Sources { + /// Bounded evidence from the existing halted-firmware snapshot. No guessed + /// MMIO/key addresses or additional firmware reads are used for diagnostics. + pub(super) fn report(&self, generation: u64, stampless_total: u64) { + kernel::pr_warn!( + "G17: recovery snapshot gen {} reason {:?} slot_mask {:#x} sampled {} known_keys {}; stampless lifetime total {} before restart\n", + generation, self.reason, self.mask, self.count, + self.records[..self.count].iter().filter(|source| source.qid.is_some()).count(), + stampless_total + ); + for source in &self.records[..self.count] { + kernel::pr_warn!( + "G17: recovery slot {} queue {:?} kick {:#x} monitor {:#x}\n", + source.slot, source.qid, source.stamp, source.monitor + ); + } + } + /// Firmware is halted for every selected key and progress-record read. /// Failed reads stay unknown; incomplete evidence cannot override firmware blame. pub(crate) fn sample(mask: u32, mut read: impl FnMut(usize) -> Option<(u64, u32)>) -> Self { @@ -103,3 +120,44 @@ impl Sources { } } } + +#[kernel::macros::kunit_tests(asahi_g17_recovery_sources)] +mod tests { + use super::*; + + #[test] + fn progress_without_keys_preserves_firmware_blame() { + let mut reads = 0; + let mut sources = Sources::sample(0b101, |slot| { + reads += 1; + Some((0, (slot + 1) as u32)) + }); + sources.reason = Some(2); + assert_eq!(reads, 2); + assert_eq!(sources.count, 2); + assert_eq!(sources.records[1].slot, 2); + assert_eq!(sources.records[1].monitor, 3); + assert!(sources.records[..sources.count].iter().all(|source| source.qid.is_none())); + assert_eq!(sources.attribute(Some(38), |_, _| panic!("unknown key")), + Attribution::Firmware); + } + + #[test] + fn incomplete_progress_cannot_clear_firmware_blame() { + let mut sources = Sources::sample(0b11, |slot| { + (slot == 0).then_some(((38 << 40) | 1, 0)) + }); + sources.reason = Some(2); + assert_eq!(sources.attribute(Some(38), |_, _| Some(false)), Attribution::Firmware); + } + + #[test] + fn page_fault_keeps_firmware_blame_with_complete_keys() { + let mut sources = Sources::sample(0b11, |slot| { + Some(((((38 + slot) as u64) << 40) | 1, 0)) + }); + sources.reason = Some(PAGE_FAULT); + assert_eq!(sources.attribute(Some(38), |qid, _| Some(qid == 39)), + Attribution::Firmware); + } +} From 0c857881725fd02b155c704e190e524183da5483 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Tue, 27 Oct 2026 14:48:00 +1100 Subject: [PATCH 094/184] docs: enumerate the remaining T6040 firmware qualification facts Specify the exact firmware facts needed for each gated 25G83 path, including achieved-clock telemetry, recovery identity/table semantics, visibility witnesses, native features and MMIO registration. Distinguish advertised OPPs from sustained residency and retention safety from CTS root-cause qualification. Keep all unsupported paths gated by evidence. Assisted-by: LLM Signed-off-by: Ryan Murray --- GAPS.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/GAPS.md b/GAPS.md index cfa2140a890b4..dd5781b12df4d 100644 --- a/GAPS.md +++ b/GAPS.md @@ -47,6 +47,36 @@ parity. There are no J616s results for this series yet. | M5 Pro comparison | No M5 Pro hardware configuration exists in this public baseline | No T605x/G17S KMD path to compare | No claim of M5 parity | Pinned public tree contains T6040 and T8140; a licensed public M5 Pro reference is required | | CTS and Linux/macOS performance ratio | Hardware results belong to their exact kernel/Mesa pair | No result for this candidate | Pending hardware agent | Run gpubench with matching suite, configuration and conditions; review CTS failures and kernel faults before claiming parity | +## Remaining 25G83 facts and recovery qualification + +No firmware capability is inferred from another GPU's successful CTS result. +The following are the exact T6040/25G83 facts needed to open the remaining +paths. A reference capture of achieved frequency establishes a target, not +an offset, voltage, descriptor ABI, or Linux residency measurement. + +| Gated path | Exact facts needed for T6040/25G83 | Current evidence and disposition | +| --- | --- | --- | +| Sustained top clock | DT OPP count, both voltage lanes, hardware A/B selector maps, selected logical-state limit; independently decoded achieved frequency or residency, counter units/timebase/wrap, request versus achieved-state semantics and limiting reason under sustained load | Per-chip DT handoff and maps are already mandatory; both maps select the final raw OPP. `max_frequency_khz` is an advertised maximum, not achieved telemetry. G17 `stats_snapshot()` returns None; ring drain alone proves no residency. Keep voltages/maps unchanged and require a measured Linux load trace. | +| Raw versus logical state count | Meaning of the ADT count properties for this firmware, both SGX map banks and number of entries actually consumed by the 25G83 power tables | Public `HwData25G83`, calibration validation and loader handoff select eleven logical states from sixteen raw OPPs. Do not resize the firmware structure from the ADT property name or discard its unsorted OPPs. Compare actual top selection and achieved clock. | +| DVFS ramp / idle / fast-die | 25G83 meaning and units of ramp/effort controls, idle/off delays, temperature limits and hysteresis; PMP/AP/CLPC request ownership, achieved floors and release under idle and load | Existing shared submission/idle policy, DT suppliers and T6040 power-state graph are enabled. Power-state presence is not proof of load tracking. Require live requests, actual floor release and thermal traces; opaque power words are not tunable parameters. | +| Recovery slot attribution | Exact 25G83 slot-key storage range, lifetime, cache policy and slot stride; key QID/stamp bit fields; slot-mask meanings, progress monitor semantics and association with an actual started render | `recovery_slot_keys=None`; never copy T8140's physical address. Preserve firmware blame. Progress-view reads no longer depend on knowing the key address; bounded diagnostics report unknown ownership explicitly. | +| Recovery table / reason | 25G83 recovery-info offset/extent, valid/guilty/cancelled bits, stamp sequence encoding; reason-code semantics; ordering and reset of records relative to states 1/2/3/0; interrupted-command drain and visibility guarantees | Existing checked 256-entry table and handshake remain unchanged. Reason 3 is treated as page fault by existing attribution. New generation/slot diagnostics aid qualification; missing key evidence cannot clear blame. An aged compute record may no longer substitute for a stamp across recovery. | +| Compute completion / visibility | Exact compute record identity and timestamp fields, stamp write/cache-maintenance order, ring consumption during restart, late/missing-stamp behavior and a release witness for interrupted work | Same-generation 50 ms fallback retained. Crossing recovery now requires a covering stamp for ordinary retirement; quarantine, kill/drain and processor-stop ownership remain. A completion record plus elapsed time after restart is not independent visibility evidence. | +| MMIO pair registration | 25G83 MMIO port addresses and access-power witness; pair registration write order, ACK and coexistence with lead commands/bootstrap QID reservation | Keep disabled. The 26A428 sequence, POWER_ON_DONE message and recovery-target pointer are distinct ABI facts. | +| MCache | 25G83 table-entry format, sizes/alignment, descriptor pointers and enable bits, ownership/coherency rules, partial-render/restart behavior | Keep disabled until a licensed public implementation or independently supplied facts establish the whole contract and tests validate it. | +| Few-primitives | T6040 enable-bit semantics, required auxiliary buffer sizes/registers and partial-store/load/resume handling for all tiling/fragment paths | Keep disabled; previously observed hangs are not grounds to enable it for performance. | +| Ray traversal / preemption | T6040 register program, scratch sizing/alignment by enabled cores/clusters, saved operand-state layout, resume/checkpoint semantics, kill/restart contract, scheduler timer mask and priority profiles | Existing two-by-ten operand layout and priorities retained; periodic timer mask remains zero on both variants. Native traversal remains disabled pending bounded execution/fault/recovery qualification. | +| TLB range operations | T6040 opcode/operand encoding, VA/ASID granules, ordering, completion and negative-translation invalidation across every mapping alias | Existing page TLBI and PoC table cleaning retained. Copying Neo range encoding cannot close a performance gap safely. | +| Large vertex/barrier and tess/geometry faults | Faulting engine/access/VA/VM, exact bound intervals/backings and mapping lifetime; render/compute descriptor identity, TVB/GTP capacities, pass-start/stamp state and recovery reason before acknowledgment | Isolate exact public CTS names with one process and compare kernels with the same Mesa. Buffer size alone does not establish VM-bind, timeout or TVB causality. Retention fix is independently justified, not a claim to have cured a CTS fault. | + +The compute stamp-wait KUnit suite now checks that an aged exact record and +consumed items cannot retire a pre-recovery kick without its stamp, while an +exact/later stamp, busy-ring control and same-generation fallback retain their +expected behavior. Recovery-source tests cover unknown keys, incomplete reads +and page-fault fallback. Diagnostic counters explicitly identify lifetime +stampless totals; they are not a count of commands retired by one restart. + + ## Source and validation boundaries The implementation references are the licensed public Aurora/Asahi kernel and From 34665a1107eb74adb99f8724ec5a94370e2fe918 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Tue, 27 Oct 2026 14:49:05 +1100 Subject: [PATCH 095/184] drm/asahi: preserve firmware blame when any selected slot lacks identity Readable progress is not complete ownership evidence. If even one selected slot lacks a valid key, do not use another slot's unstarted render to clear firmware blame. Require every sampled selected key to name a queue before attempting host attribution. Cover a known blamed queue with an unknown peer key, in addition to wholly unknown keys and failed progress reads. This also keeps a failed key read conservative on firmware builds with a known key address. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/gpu/drm/asahi/g17/recovery/attribution.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/asahi/g17/recovery/attribution.rs b/drivers/gpu/drm/asahi/g17/recovery/attribution.rs index be1c7ab9eb662..38ad8612a0b99 100644 --- a/drivers/gpu/drm/asahi/g17/recovery/attribution.rs +++ b/drivers/gpu/drm/asahi/g17/recovery/attribution.rs @@ -81,7 +81,10 @@ impl Sources { blamed: Option, mut started: impl FnMut(u8, u64) -> Option, ) -> Attribution { - if self.reason.is_none() || self.count as u32 != self.mask.count_ones() { + if self.reason.is_none() + || self.count as u32 != self.mask.count_ones() + || self.records[..self.count].iter().any(|source| source.qid.is_none()) + { return Attribution::Firmware; } for source in &mut self.records[..self.count] { @@ -151,6 +154,16 @@ mod tests { assert_eq!(sources.attribute(Some(38), |_, _| Some(false)), Attribution::Firmware); } + #[test] + fn a_missing_peer_key_cannot_clear_a_known_queues_blame() { + let mut sources = Sources::sample(0b11, |slot| { + Some((if slot == 0 { (38 << 40) | 1 } else { 0 }, 0)) + }); + sources.reason = Some(2); + assert_eq!(sources.count, 2); + assert_eq!(sources.attribute(Some(38), |_, _| Some(false)), Attribution::Firmware); + } + #[test] fn page_fault_keeps_firmware_blame_with_complete_keys() { let mut sources = Sources::sample(0b11, |slot| { From 6ca4244633eb9e7105685f99469332f8203a4adf Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Tue, 27 Oct 2026 15:03:51 +1100 Subject: [PATCH 096/184] drm/asahi: initialize the G17 idle hold from the existing DT policy The G17 backend exported an idle-delay attribute but always started at the native two-millisecond policy, ignoring apple,idle-off-delay-ms. Use the existing DT property at probe and validate it against the same 2..1000 ms bounds as the existing attribute. Document that property in the binding. Missing policy preserves the current default; other GPUs need no platform flag or new module parameter. Exercise absent, valid and invalid policy values and depth-one gaps within a bounded sixteen-millisecond hold, followed by real idle release. This supplies a board policy through the existing power-control path; it changes neither firmware register layouts nor voltage/clock tables. Assisted-by: LLM Signed-off-by: Ryan Murray --- .../devicetree/bindings/gpu/apple,agx.yaml | 10 +++++ drivers/gpu/drm/asahi/g17/mod.rs | 6 +++ drivers/gpu/drm/asahi/g17/power.rs | 44 ++++++++++++++++++- 3 files changed, 58 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/gpu/apple,agx.yaml b/Documentation/devicetree/bindings/gpu/apple,agx.yaml index 0cd4dc1d6fd78..97c7b4d8cb68f 100644 --- a/Documentation/devicetree/bindings/gpu/apple,agx.yaml +++ b/Documentation/devicetree/bindings/gpu/apple,agx.yaml @@ -72,6 +72,16 @@ properties: - enum: [hw-cal-b, fw-text, l2-pagetables] - enum: [globals, fw-data, fw-text] + apple,idle-off-delay-ms: + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 2 + maximum: 1000 + default: 2 + description: + Continuous idle time before GPU power-off, in milliseconds. The G17 + backend extends the firmware's native delay with its host idle-hold + control; an absent property preserves the native policy. + apple,pmp: $ref: /schemas/types.yaml#/definitions/phandle description: PMP firmware provider owning the GPU power vote and telemetry diff --git a/drivers/gpu/drm/asahi/g17/mod.rs b/drivers/gpu/drm/asahi/g17/mod.rs index e527e6fa83528..78a924782a1a2 100644 --- a/drivers/gpu/drm/asahi/g17/mod.rs +++ b/drivers/gpu/drm/asahi/g17/mod.rs @@ -1141,6 +1141,12 @@ impl Gpu { oas: cfg.uat_oas, }; let max_frequency_khz = Self::max_frequency_khz(dev)?; + let node = dev.as_ref().as_ref().of_node().ok_or(EIO)?; + let idle_delay = power::idle_delay( + node.get_opt_property(c_str!("apple,idle-off-delay-ms"))?, + )?; + power::IDLE_HOLD_MS.store(idle_delay, Ordering::Relaxed); + dev_info!(dev.as_ref(), "GPU idle power-off delay: {} ms\n", idle_delay); let channels = availability::Availability::new()?; let progress = availability::Availability::new()?; let queues = availability::Availability::new()?; diff --git a/drivers/gpu/drm/asahi/g17/power.rs b/drivers/gpu/drm/asahi/g17/power.rs index b0e21e70bbe18..bbd10b204edd7 100644 --- a/drivers/gpu/drm/asahi/g17/power.rs +++ b/drivers/gpu/drm/asahi/g17/power.rs @@ -12,10 +12,22 @@ pub(super) const IDLE_HOLD_NS: u64 = 16_000_000; /// the last job. Not measured on other firmware; at or below it the hold stays off. pub(crate) const FIRMWARE_IDLE_OFF_MS: u32 = 2; -/// The `idle_off_delay_ms` sysfs knob. Above [`FIRMWARE_IDLE_OFF_MS`] the driver keeps the GPU -/// cores powered until the GPU has been idle this long ([`Hold`]). +/// Idle policy initialized from the existing DT `apple,idle-off-delay-ms` property. +/// The existing `idle_off_delay_ms` sysfs attribute may override it. Above +/// [`FIRMWARE_IDLE_OFF_MS`] the driver keeps the GPU cores powered until it has been idle +/// this long ([`Hold`]). pub(crate) static IDLE_HOLD_MS: AtomicU32 = AtomicU32::new(FIRMWARE_IDLE_OFF_MS); +/// Use the same bounds as the existing idle-delay attribute. Missing DT policy +/// preserves the native delay; malformed or out-of-range values fail probe. +pub(super) fn idle_delay(requested: Option) -> kernel::error::Result { + let delay = requested.unwrap_or(FIRMWARE_IDLE_OFF_MS); + if !(FIRMWARE_IDLE_OFF_MS..=1000).contains(&delay) { + return Err(kernel::error::code::EINVAL); + } + Ok(delay) +} + #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub(super) enum IdleAction { None, @@ -237,6 +249,34 @@ pub(super) fn release(control: &mut C) -> kernel::error::Result { mod tests { use super::*; + #[test] + fn dt_idle_delay_is_bounded_and_absence_preserves_native_policy() { + assert_eq!(idle_delay(None), Ok(FIRMWARE_IDLE_OFF_MS)); + for delay in [2, 16, 1000] { + assert_eq!(idle_delay(Some(delay)), Ok(delay)); + } + for delay in [0, 1, 1001, u32::MAX] { + assert!(idle_delay(Some(delay)).is_err()); + } + } + + #[test] + fn frame_hold_survives_depth_one_gaps_but_releases_at_idle() { + let mut hold = Hold::new(); + assert_eq!(hold.step(0, true, 1, 16), HoldAction::Assert); + hold.asserted(); + for submission in 1..=8 { + // A completed depth-one dispatch followed by another short gap. + let now = submission * 3_000_000; + assert_eq!(hold.step(now, false, submission, 16), HoldAction::Recheck(16_000_000)); + assert!(hold.active()); + } + assert_eq!(hold.step(39_999_999, false, 8, 16), HoldAction::Recheck(1)); + assert_eq!(hold.step(40_000_000, false, 8, 16), HoldAction::Release); + hold.released(); + assert!(!hold.active()); + } + #[test] fn holds_until_idle_for_the_delay_then_releases() { let mut hold = Hold::new(); From 5440369988c80c1a415b9242f1e2c6f40f9a235c Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Tue, 27 Oct 2026 15:03:52 +1100 Subject: [PATCH 097/184] arm64: dts: apple: retain J616s GPU power across short submission gaps Request sixteen milliseconds of continuous idle before releasing the existing host GPU power hold. The native two-millisecond off policy can cycle the T6040 cores between interactive submissions and each wake has a substantial fixed cost. Use the existing idle-delay DT policy, with a bounded hold matching the existing performance-effort idle interval. Long idle still sends the existing power-off control. Qualify depth-one round trips, short/long idle gaps and idle power alongside the T6040 CLPC/PMP memory-floor ports; no measured speedup is asserted here. Assisted-by: LLM Signed-off-by: Ryan Murray --- arch/arm64/boot/dts/apple/t6040-j616s.dts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t6040-j616s.dts b/arch/arm64/boot/dts/apple/t6040-j616s.dts index 7abbd491489d0..f5ba130e5c183 100644 --- a/arch/arm64/boot/dts/apple/t6040-j616s.dts +++ b/arch/arm64/boot/dts/apple/t6040-j616s.dts @@ -853,6 +853,8 @@ operating-points-v2 = <&gpu_opp>; apple,cs-opp = <&gpu_cs_opp>; apple,afr-opp = <&gpu_afr_opp>; + /* Avoid a core power cycle between interactive submissions, then release. */ + apple,idle-off-delay-ms = <16>; status = "okay"; }; From e6bf61f5fb42978bd2f3696b0184b0ccb9b1e078 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Tue, 27 Oct 2026 15:04:46 +1100 Subject: [PATCH 098/184] docs: distinguish GFX power reporting from GPU memory performance votes Record the actual T6040 power-report, PMP voter and CLPC demand paths. Explain the bounded J616s DT idle policy and the separate latency/power acceptance gates. Update the recovery audit for the release-witness and unknown-slot ownership guards. Software reachability is not evidence of achieved clocks, an honoured memory floor or a benchmark gain. Assisted-by: LLM Signed-off-by: Ryan Murray --- GAPS.md | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/GAPS.md b/GAPS.md index dd5781b12df4d..b958e0225f815 100644 --- a/GAPS.md +++ b/GAPS.md @@ -7,8 +7,9 @@ two r6 display patches. The reconstructed r6 branch is `wip/j616s-display-r6` at `af0a664046fb`. This series repairs regression coverage and a shared KUNIT=m WERROR build defect, -and ports GPU-driven memory/fabric demand and PMP voter cooperation using -T6040 data. GPU voltages, firmware features and the submission algorithm remain +ports GPU-driven memory/fabric demand and PMP voter cooperation using +T6040 data, honors the existing DT idle-delay policy, and closes a compute +release-witness gap across recovery. GPU voltages, firmware features and the submission algorithm remain unchanged from the reconstructed base. The major T6040 calibration and image-admission changes requested by this audit are already in the baseline. Software parity does not establish benchmark or CTS @@ -24,7 +25,7 @@ parity. There are no J616s results for this series yet. | DVFS ramp / effort | Full effort on publication, cleared after 16 ms of continuous idle | Same `Firmware::note_submission`, direct-port accounting and race-checked idle clear | Same runtime behavior | `g17/mod.rs`, `g17/port.rs`, `g17/power.rs`; opaque firmware power words are not a documented ramp control | | GPU-driven fabric/memory floor | #440 lifts DCS 4/fabric 3 for GPU demand | T6040 CPU-only demand, gpu_demand=false and no power-state phandle | Port 0d63308820ab enables the shared mechanism with existing T6040 DCS 5/fabric 2 data | clpc.c and T6040 DT; g1/g2 P/C/R/H/I measurements required | | PMP PMC vote cooperation | PMP/CLPC/AP agents enabled | T6040 static voters omit PMP 0 from pre-working-PMP bring-up | Port 772a9d3104ff adds the existing PMP supplier dependency and voter 0 on T6040 DCS/SOC rails | pmc.c and T6040 DT; live votes, idle release and thermal qualification required | -| Idle / off policy | Shared host hold and firmware device-control path | Same host implementation; source documents a 25G83 effective 2 ms off delay and approximately 1 ms wake cost | Same runtime behavior; default hold unchanged | `g17/power.rs`; measure actual idle/wake latency before altering policy | +| Idle / off policy | Shared host hold and firmware device-control path | Same host implementation; source documents a 25G83 effective 2 ms off delay and approximately 1 ms wake cost | J616s requests a bounded 16 ms host hold through existing `apple,idle-off-delay-ms`; absent policy retains 2 ms | `g17/power.rs`, `g17/mod.rs`, J616s DT and binding; compare short/long gaps, depth-one latency and idle power | | Fast-die / PMP | Platform-specific firmware power graph | 25G83 AON table and separate power-state target/release (101/96 C) are already populated; DT enables the GFX suppliers | Same runtime behavior | `g17/fw/initdata/v25g83.rs::POWER_STATE_WORDS`, `g17/initdata/v25g83.rs`, J616s DT; verify clocks with PMP running | | Periodic preemption | Context-switch timer mask is zero | Same zero mask; two-cluster operand-state allocation differs by hardware layout | Same runtime behavior | `g17/fw/initdata.rs::CONTEXT_SWITCH_TIMER_MASK`, `g17/fw/compute.rs::OperandState`; no evidence supports changing the mask | | High-priority queues | HIGH / MEDIUM / LOW firmware scheduling profiles | Same scheduling-policy selector and priority work rings | Same runtime behavior | `g17/mod.rs::scheduling_policy`, `g17/fw/channels.rs`, `g17/qos.rs` | @@ -39,14 +40,40 @@ parity. There are no J616s results for this series yet. | G13/G14 idle wake regressions | Firmware-specific cost plus shared host overhead | G16S shares common ioctl/GEM/sync infrastructure, but has different firmware, power handling and queue backend | Same runtime behavior | `driver.rs`, `g17/power.rs`; G13/G14 benchmark ratios cannot be transferred to G16S | | QID capacity | Firmware floor 0; reserve protects dedicated render pairs | 25G83 reserves QIDs 0..31; public queue limit is reduced accordingly | KUnit checks floor 32 in dynamic growth and full logical queue budget tests; host resource runner follows current reserve helper | `g17/kick.rs`, `g17/runtime.rs`, `tools/asahi/g17-resource-tests.py` | | KUnit / WERROR config matrix | Shared test modules require KUNIT=y | Four G17 test helpers used the broader CONFIG_KUNIT guard and failed WERROR with the shipping KUNIT=m config | Helpers now use CONFIG_KUNIT = "y", matching the Rust macro; config and warning policy unchanged | `rust/macros/kunit.rs`, `g17/kick.rs`, `g17/preparation.rs`, `g17/queue/admission.rs`, `g17/runtime.rs`; verify both KUNIT=m release and KUNIT=y QEMU builds | -| Compute ownership / retained mappings | Recovery and retirement witnesses protect mapping lifetime | Same production queue ownership, source loans, growth rollback and deferred release | Restored host fixtures and negative controls; source loans explicitly prevent premature reuse/sharing/rearming | `tools/asahi/g17-capacity-fault-tests.py`, constructor/growth/pool/release-order fixtures | -| Restart and fault classification | Shared recovery handshake plus validated T8140 per-slot keys | Shared handshake uses 25G83 status offsets; `recovery_slot_keys=None` preserves firmware blame when keys are unknown | Same runtime behavior | `hw/t6040.rs`, `g17/initdata.rs`, `g17/recovery/attribution.rs`; successful 25G83 fault/restart qualification is still required | +| Compute ownership / retained mappings | Recovery and retirement witnesses protect mapping lifetime | Same production queue ownership, source loans, growth rollback and deferred release | Aged-record fallback now requires the original recovery generation; source loans and host negative controls remain | `tools/asahi/g17-capacity-fault-tests.py`, constructor/growth/pool/release-order fixtures | +| Restart and fault classification | Shared recovery handshake plus validated T8140 per-slot keys | Shared handshake uses 25G83 status offsets; `recovery_slot_keys=None` preserves firmware blame when keys are unknown | Bounded generation/slot/progress diagnostics retain unknown ownership; any missing selected key preserves firmware blame | `hw/t6040.rs`, `g17/initdata.rs`, `g17/recovery/attribution.rs`; successful 25G83 fault/restart qualification is still required | | DART / UAT | Shared mapping ownership and checked page-table borrowing | Shared MMU, with T6040 reserved tables, PoC cleaning, per-page TLBI and native GPU permission quirks | Same runtime behavior | `hw/t6040.rs`, `mmu.rs`, `pgtable.rs`; do not remove coherency or handoff requirements for a benchmark | | Memory accounting | Creator-attributed committed/resident GEM accounting and capacity admission | Same `memacct::Client`, `File::memory_stats` and shared capacity machinery | Same runtime behavior | `file.rs`, `gem.rs`, `memacct.rs`, `g17/capacity.rs`; verify multi-client high-water and post-close behavior on hardware | | MCache / few-primitives / ray traversal | Enabled by the 26A428 variant | Disabled by 25G83 variant; source records hangs for few-primitives and stalled ray traversal, while MCache is unqualified | Same runtime behavior | `g17/fw/variant.rs::V25G83`; enabling these requires independent ABI evidence and hardware tests | | M5 Pro comparison | No M5 Pro hardware configuration exists in this public baseline | No T605x/G17S KMD path to compare | No claim of M5 parity | Pinned public tree contains T6040 and T8140; a licensed public M5 Pro reference is required | | CTS and Linux/macOS performance ratio | Hardware results belong to their exact kernel/Mesa pair | No result for this candidate | Pending hardware agent | Run gpubench with matching suite, configuration and conditions; review CTS failures and kernel faults before claiming parity | +## GPU memory demand and interactive latency + +The GFX PMP report entry and PMC performance votes are separate mechanisms. +The existing T6040 `pmp_gfx` soc-device 13 entry follows the powered GPU domain; +`apple_pmp_report_set_state()` publishes a power-state request bit and waits +for its acknowledgement. It does not encode a DCS/fabric performance request. +The GPU firmware owns the PMP's performance demand. Before this series, PMC's +static voter list omitted PMP agent 0, and CLPC lacked a GPU power-state input. +The two existing ports enable PMP agent 0 on T6040's DCS/SOC rails and make +CLPC request the already qualified DCS 5 / fabric 2 floor while graphics is +powered. They preserve the existing 32 ms memory-floor release. Prove live +request, enabled-agent and actual-state agreement; supplier binding alone +is not proof of an honoured vote. + +For dispatch latency, G17 now reads the existing `apple,idle-off-delay-ms` DT +policy, validates the existing 2..1000 ms bounds, and starts the existing host +hold with that policy. J616s selects 16 ms, matching the existing effort-idle +interval; other boards without the property retain the native 2 ms behavior. +This avoids power-off across short interactive gaps and releases after real +idle, without changing firmware layout or adding a parameter/platform flag. +KUnit covers absent/valid/invalid policy and repeated depth-one gaps followed +by idle release. A shorter software delay is not itself a measured latency +win: compare actual power transitions, firmware execution, IRQ/retirement and +fence wakeup boundaries. Keep empty CPU fence transfers separate from GPU +round trips and require low-duty/long-idle power acceptance. + ## Remaining 25G83 facts and recovery qualification No firmware capability is inferred from another GPU's successful CTS result. From 09f4a9bb81190c3029d446356771008d8ad28c83 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Tue, 27 Oct 2026 15:09:18 +1100 Subject: [PATCH 099/184] dt-bindings: gpu: use the standard millisecond property type The DT meta-schema supplies the type for a standard -ms unit suffix. Drop the redundant uint32 reference on apple,idle-off-delay-ms so the binding passes validation while retaining its bounds and description. Assisted-by: LLM Signed-off-by: Ryan Murray --- Documentation/devicetree/bindings/gpu/apple,agx.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/Documentation/devicetree/bindings/gpu/apple,agx.yaml b/Documentation/devicetree/bindings/gpu/apple,agx.yaml index 97c7b4d8cb68f..145e277d6be4d 100644 --- a/Documentation/devicetree/bindings/gpu/apple,agx.yaml +++ b/Documentation/devicetree/bindings/gpu/apple,agx.yaml @@ -73,7 +73,6 @@ properties: - enum: [globals, fw-data, fw-text] apple,idle-off-delay-ms: - $ref: /schemas/types.yaml#/definitions/uint32 minimum: 2 maximum: 1000 default: 2 From 73fb974b8478c8b3b245256afa751e80b28f8db1 Mon Sep 17 00:00:00 2001 From: Ryan <81541646+RyanTheTide@users.noreply.github.com> Date: Tue, 27 Oct 2026 17:13:08 +1100 Subject: [PATCH 100/184] arm64: dts: apple: t6040-j616s: Describe every SMC sensor and add die thermal zones * arm64: dts: apple: t6040-j616s: Describe every SMC sensor and add die zones The J616s DT listed one SMC temperature (TCMz) plus the shared hwmon includes: five temperatures, two fans and a handful of rails, while its SMC has 3462 keys. A read-only walk of the key table on this machine found 466 more readable float sensors that update: 278 temperatures (P and E cluster, GPU, SoC, VR, NAND, battery, ports), 49 voltages, 67 currents and 72 powers, including the three battery cells and the battery current and power. Describe all of them, in the style of the J700, so the machine reports at least what the fanless J700 does. Keys that only ever read a fixed placeholder (Tz1*, Ta0*) or a fixed limit (TVMx, TVmS, TVms, PHPB, PHPM) stay out; rails that read 0 W at idle stay in. The GPU sensors (Tg*) report placeholder values from -4 to 4 C while the GPU is powered off and read live while it runs. Add a zone per die-maximum sensor: Tex0-3 for the E cluster, Tpx0-5 and Tpx8-D for the two P clusters, with the TCMz backstop trips (113 C passive, 118 C critical), and the seven hottest GPU sensors, which shed package power through the CPU clusters from 105 C, above the 103.8 C peak seen during conformance runs. The PMP and the SMC fans regulate in normal use; these zones catch a failure of that loop. The fans already list their minimum, maximum, target and mode keys, so the existing fan_control module parameter makes fanN_target writable; no new parameter is needed. Assisted-by: LLM Signed-off-by: Ryan Murray * arm64: dts: apple: t6040-j616s: Fix the SMC sensor comment "Tp*/Te*" in the sensor comment closed the comment early and broke the build of the J616s DT. Spell the groups out. Fixes: 8b5a2d73a21a ("arm64: dts: apple: t6040-j616s: Describe every SMC sensor and add die zones") Assisted-by: LLM Signed-off-by: Ryan Murray --------- Signed-off-by: Ryan Murray --- arch/arm64/boot/dts/apple/t6040-j616s.dts | 3492 ++++++++++++++++++++- 1 file changed, 3359 insertions(+), 133 deletions(-) diff --git a/arch/arm64/boot/dts/apple/t6040-j616s.dts b/arch/arm64/boot/dts/apple/t6040-j616s.dts index f5ba130e5c183..c47e71ed1e258 100644 --- a/arch/arm64/boot/dts/apple/t6040-j616s.dts +++ b/arch/arm64/boot/dts/apple/t6040-j616s.dts @@ -301,6 +301,21 @@ * 113 C, critical (orderly shutdown) at 118 C, polled once a second as * the SMC refreshes TCMz. The cpufreq cooling device sits on a * cluster's policy CPU, so each map lists every CPU of its cluster. + * + * Per-cluster and GPU zones follow, one per SMC die-maximum sensor + * (smc_hwmon channels 1-23, in the order of the temperature nodes + * below): Tex0-3 throttle the E cluster, Tpx0-5 the first P cluster + * and Tpx8-D the second, with the same 113/118 C backstop as TCMz. + * Unlike the fanless J700, this machine has two SMC-driven fans and + * the PMP regulates the dies itself, so these zones only catch a + * failure of that loop; they never act in normal use. + * + * The GPU has no cooling device of its own: its firmware sets the GPU + * performance state. The seven hottest GPU die sensors (Tg05, Tg0S, + * Tg0Y, Tg0k, Tg0z, Tg1V, Tg1l) shed package power through the CPU + * clusters from 105 C, above the 103.8 C peak measured during + * conformance runs, and shut down at 118 C. While the GPU is powered + * off its sensors report placeholder values from -4 to 4 C. */ thermal-zones { cpu-die-thermal { @@ -353,183 +368,3394 @@ }; }; }; + + cpu-e0-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 1>; /* Tex0 */ + + trips { + cpu_e0_passive: trip-passive { + temperature = <113000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-e { + trip = <&cpu_e0_passive>; + cooling-device = + <&cpu_e0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + cpu-e1-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 2>; /* Tex1 */ + + trips { + cpu_e1_passive: trip-passive { + temperature = <113000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-e { + trip = <&cpu_e1_passive>; + cooling-device = + <&cpu_e0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + cpu-e2-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 3>; /* Tex2 */ + + trips { + cpu_e2_passive: trip-passive { + temperature = <113000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-e { + trip = <&cpu_e2_passive>; + cooling-device = + <&cpu_e0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + cpu-e3-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 4>; /* Tex3 */ + + trips { + cpu_e3_passive: trip-passive { + temperature = <113000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-e { + trip = <&cpu_e3_passive>; + cooling-device = + <&cpu_e0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + cpu-p00-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 5>; /* Tpx0 */ + + trips { + cpu_p00_passive: trip-passive { + temperature = <113000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-p0 { + trip = <&cpu_p00_passive>; + cooling-device = + <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + cpu-p01-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 6>; /* Tpx1 */ + + trips { + cpu_p01_passive: trip-passive { + temperature = <113000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-p0 { + trip = <&cpu_p01_passive>; + cooling-device = + <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + cpu-p02-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 7>; /* Tpx2 */ + + trips { + cpu_p02_passive: trip-passive { + temperature = <113000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-p0 { + trip = <&cpu_p02_passive>; + cooling-device = + <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + cpu-p03-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 8>; /* Tpx3 */ + + trips { + cpu_p03_passive: trip-passive { + temperature = <113000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-p0 { + trip = <&cpu_p03_passive>; + cooling-device = + <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + cpu-p04-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 9>; /* Tpx4 */ + + trips { + cpu_p04_passive: trip-passive { + temperature = <113000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-p0 { + trip = <&cpu_p04_passive>; + cooling-device = + <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + cpu-p05-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 10>; /* Tpx5 */ + + trips { + cpu_p05_passive: trip-passive { + temperature = <113000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-p0 { + trip = <&cpu_p05_passive>; + cooling-device = + <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + cpu-p10-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 11>; /* Tpx8 */ + + trips { + cpu_p10_passive: trip-passive { + temperature = <113000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-p1 { + trip = <&cpu_p10_passive>; + cooling-device = + <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + cpu-p11-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 12>; /* Tpx9 */ + + trips { + cpu_p11_passive: trip-passive { + temperature = <113000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-p1 { + trip = <&cpu_p11_passive>; + cooling-device = + <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + cpu-p12-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 13>; /* TpxA */ + + trips { + cpu_p12_passive: trip-passive { + temperature = <113000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-p1 { + trip = <&cpu_p12_passive>; + cooling-device = + <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + cpu-p13-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 14>; /* TpxB */ + + trips { + cpu_p13_passive: trip-passive { + temperature = <113000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-p1 { + trip = <&cpu_p13_passive>; + cooling-device = + <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + cpu-p14-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 15>; /* TpxC */ + + trips { + cpu_p14_passive: trip-passive { + temperature = <113000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-p1 { + trip = <&cpu_p14_passive>; + cooling-device = + <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + cpu-p15-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 16>; /* TpxD */ + + trips { + cpu_p15_passive: trip-passive { + temperature = <113000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-p1 { + trip = <&cpu_p15_passive>; + cooling-device = + <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + gpu0-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 17>; /* Tg05 */ + + trips { + gpu0_passive: trip-passive { + temperature = <105000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-e { + trip = <&gpu0_passive>; + cooling-device = + <&cpu_e0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + + map-p0 { + trip = <&gpu0_passive>; + cooling-device = + <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + + map-p1 { + trip = <&gpu0_passive>; + cooling-device = + <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + gpu1-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 18>; /* Tg0S */ + + trips { + gpu1_passive: trip-passive { + temperature = <105000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-e { + trip = <&gpu1_passive>; + cooling-device = + <&cpu_e0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + + map-p0 { + trip = <&gpu1_passive>; + cooling-device = + <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + + map-p1 { + trip = <&gpu1_passive>; + cooling-device = + <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + gpu2-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 19>; /* Tg0Y */ + + trips { + gpu2_passive: trip-passive { + temperature = <105000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-e { + trip = <&gpu2_passive>; + cooling-device = + <&cpu_e0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + + map-p0 { + trip = <&gpu2_passive>; + cooling-device = + <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + + map-p1 { + trip = <&gpu2_passive>; + cooling-device = + <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + gpu3-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 20>; /* Tg0k */ + + trips { + gpu3_passive: trip-passive { + temperature = <105000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-e { + trip = <&gpu3_passive>; + cooling-device = + <&cpu_e0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + + map-p0 { + trip = <&gpu3_passive>; + cooling-device = + <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + + map-p1 { + trip = <&gpu3_passive>; + cooling-device = + <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + gpu4-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 21>; /* Tg0z */ + + trips { + gpu4_passive: trip-passive { + temperature = <105000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-e { + trip = <&gpu4_passive>; + cooling-device = + <&cpu_e0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + + map-p0 { + trip = <&gpu4_passive>; + cooling-device = + <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + + map-p1 { + trip = <&gpu4_passive>; + cooling-device = + <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + gpu5-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 22>; /* Tg1V */ + + trips { + gpu5_passive: trip-passive { + temperature = <105000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-e { + trip = <&gpu5_passive>; + cooling-device = + <&cpu_e0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + + map-p0 { + trip = <&gpu5_passive>; + cooling-device = + <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + + map-p1 { + trip = <&gpu5_passive>; + cooling-device = + <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + gpu6-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 23>; /* Tg1l */ + + trips { + gpu6_passive: trip-passive { + temperature = <105000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-e { + trip = <&gpu6_passive>; + cooling-device = + <&cpu_e0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + + map-p0 { + trip = <&gpu6_passive>; + cooling-device = + <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + + map-p1 { + trip = <&gpu6_passive>; + cooling-device = + <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + }; + + /* + * The speaker amps' shared shutdown line (SDZ, AP pin 54, low = + * shutdown). One fixed regulator, the SDZ supply of all six, not + * per-amp shutdown-gpios: with GPIO_SHARED six references become a + * vote, and one amp driving the line low would not shut the other + * five down. + */ + speaker_sdz: regulator-sn012776-sdz { + compatible = "regulator-fixed"; + regulator-name = "sn012776-sdz"; + gpios = <&pinctrl 54 GPIO_ACTIVE_HIGH>; + enable-active-high; + }; + + /* + * The AOP firmware runs the stream (MCA cluster 0, one serializer for + * all six amps); the J616 variant of macaudio links the AOP audio + * component's speaker back-end (DAI 3) to the amps. The codecs are + * listed in the ADT provider order, so each gets its native playback + * slot (LW1 0, RW1 1, LW2 2, RW2 3, LT 4, RT 5). The idle masks are the + * ADT amp-tx-zd-config rows 0 and 1: LW1 drives zeros in the right + * amps' sense slots, RW1 in the left amps'. + */ + sound: sound { + compatible = "apple,j616-macaudio", "apple,macaudio"; + model = "MacBook Pro J616"; + #address-cells = <1>; + #size-cells = <0>; + + dai-link@0 { + reg = <0>; + link-name = "Speakers"; + dai-tdm-idle-mode-0 = "zero"; + dai-tdm-idle-mode-1 = "zero"; + dai-tdm-slot-tx-idle-mask-0 = <0xf0f0f0>; + dai-tdm-slot-tx-idle-mask-1 = <0x0f0f0f>; + + cpu { + sound-dai = <&aop_audio 3>; + }; + + codec { + sound-dai = <&speaker_left_woof1>, <&speaker_right_woof1>, + <&speaker_left_woof2>, <&speaker_right_woof2>, + <&speaker_left_tweet>, <&speaker_right_tweet>; + }; + }; + }; + + /* + * The headphone jack: the AOP audio component's jack back-end (DAI 4) + * to the CS42L84. A card of its own, so a codec that does not answer + * costs the jack only, never the speakers. + */ + sound_jack: sound-jack { + compatible = "apple,j616-macaudio-jack", "apple,macaudio"; + model = "MacBook Pro J616 Jack"; + #address-cells = <1>; + #size-cells = <0>; + + dai-link@0 { + reg = <0>; + link-name = "Headphone Jack"; + + cpu { + sound-dai = <&aop_audio 4>; + }; + + codec { + sound-dai = <&jack_codec>; + }; + }; + }; +}; + +&cpuidle { + status = "okay"; +}; + +&smc { + status = "okay"; +}; + +&smc_mbox { + status = "okay"; +}; + +&nub_spmi0 { + status = "okay"; +}; + +&pinctrl { + status = "okay"; + + wifi_clkreq_pins: wifi-clkreq-pins { + pinmux = ; + }; +}; + +/* + * The AP takes none of the always-on bank's interrupts: the AOP firmware + * owns some of its pins and interrupt registers, and a chained handler here + * would see their pending bits without a handler of ours. + */ +&pinctrl_nub { + status = "okay"; + /delete-property/ interrupt-controller; + /delete-property/ #interrupt-cells; + /delete-property/ interrupts; +}; + +&fpwm0 { + status = "okay"; +}; + +/* The Type-C retimers (ADT atcrt at 0x18/0x19/0x1a) sit on this bus; no driver yet. */ +&i2c6 { + status = "okay"; +}; + +&mtp { + status = "okay"; +}; + +&mtp_mbox { + status = "okay"; +}; + +&mtp_dart { + status = "okay"; +}; + +&mtp_dockchannel { + status = "okay"; +}; + +&mtp_hid { + /* SMC key gp1c powers the trackpad analog front end. */ + apple,afe-smc = <&smc>; + + multi-touch { + firmware-name = "apple/tpmtfw-j616s.bin"; + }; + + keyboard: keyboard { + hid-country-code = <0>; + apple,keyboard-layout-id = <2>; + }; + + stm { + }; + + actuator { + }; + + tp-accel { + }; + + mtp { + }; +}; + +&smc_hwmon { + /* + * TCMz and the 23 zone sensors after it must stay the first + * temperature children: the thermal zones name a sensor by its index, + * so the shared hwmon includes below only append after them. + * + * Every key below is in this machine's SMC key table as a readable + * float and was read on the J616s (SMC firmware of macOS 26.6.2). + * Keys that only ever read a fixed placeholder (Tz1*, Ta0*) or a + * fixed limit (TVMx, TVmS, TVms, PHPB, PHPM) are left out; rails that + * read 0 W while idle are kept. GPU (Tg*), P-cluster (Tp*) and + * E-cluster (Te*) labels follow load tests: Tg* only read live while + * the GPU is powered and rise with GPU load, Tp* and Te* rise with CPU + * load. Other names follow the SMC key groups and are not otherwise + * verified. SBA1-3 are the three cells of the 3S battery. + */ + temperature-TCMz { + apple,key-id = "TCMz"; + label = "CPU Die Max"; + }; + + temperature-Tex0 { + apple,key-id = "Tex0"; + label = "E-cluster Tex0"; + }; + + temperature-Tex1 { + apple,key-id = "Tex1"; + label = "E-cluster Tex1"; + }; + + temperature-Tex2 { + apple,key-id = "Tex2"; + label = "E-cluster Tex2"; + }; + + temperature-Tex3 { + apple,key-id = "Tex3"; + label = "E-cluster Tex3"; + }; + + temperature-Tpx0 { + apple,key-id = "Tpx0"; + label = "P-cluster Tpx0"; + }; + + temperature-Tpx1 { + apple,key-id = "Tpx1"; + label = "P-cluster Tpx1"; + }; + + temperature-Tpx2 { + apple,key-id = "Tpx2"; + label = "P-cluster Tpx2"; + }; + + temperature-Tpx3 { + apple,key-id = "Tpx3"; + label = "P-cluster Tpx3"; + }; + + temperature-Tpx4 { + apple,key-id = "Tpx4"; + label = "P-cluster Tpx4"; + }; + + temperature-Tpx5 { + apple,key-id = "Tpx5"; + label = "P-cluster Tpx5"; + }; + + temperature-Tpx8 { + apple,key-id = "Tpx8"; + label = "P-cluster Tpx8"; + }; + + temperature-Tpx9 { + apple,key-id = "Tpx9"; + label = "P-cluster Tpx9"; + }; + + temperature-TpxA { + apple,key-id = "TpxA"; + label = "P-cluster TpxA"; + }; + + temperature-TpxB { + apple,key-id = "TpxB"; + label = "P-cluster TpxB"; + }; + + temperature-TpxC { + apple,key-id = "TpxC"; + label = "P-cluster TpxC"; + }; + + temperature-TpxD { + apple,key-id = "TpxD"; + label = "P-cluster TpxD"; + }; + + temperature-Tg05 { + apple,key-id = "Tg05"; + label = "GPU Tg05"; + }; + + temperature-Tg0S { + apple,key-id = "Tg0S"; + label = "GPU Tg0S"; + }; + + temperature-Tg0Y { + apple,key-id = "Tg0Y"; + label = "GPU Tg0Y"; + }; + + temperature-Tg0k { + apple,key-id = "Tg0k"; + label = "GPU Tg0k"; + }; + + temperature-Tg0z { + apple,key-id = "Tg0z"; + label = "GPU Tg0z"; + }; + + temperature-Tg1V { + apple,key-id = "Tg1V"; + label = "GPU Tg1V"; + }; + + temperature-Tg1l { + apple,key-id = "Tg1l"; + label = "GPU Tg1l"; + }; + + temperature-TAOL { + apple,key-id = "TAOL"; + label = "SMC TAOL"; + }; + + temperature-TB1T { + apple,key-id = "TB1T"; + label = "Battery 1"; + }; + + temperature-TB2T { + apple,key-id = "TB2T"; + label = "Battery 2"; + }; + + temperature-TCMb { + apple,key-id = "TCMb"; + label = "SMC TCMb"; + }; + + temperature-TD00 { + apple,key-id = "TD00"; + label = "SMC TD00"; + }; + + temperature-TD01 { + apple,key-id = "TD01"; + label = "SMC TD01"; + }; + + temperature-TD02 { + apple,key-id = "TD02"; + label = "SMC TD02"; + }; + + temperature-TD03 { + apple,key-id = "TD03"; + label = "SMC TD03"; + }; + + temperature-TD04 { + apple,key-id = "TD04"; + label = "SMC TD04"; + }; + + temperature-TD10 { + apple,key-id = "TD10"; + label = "SMC TD10"; + }; + + temperature-TD11 { + apple,key-id = "TD11"; + label = "SMC TD11"; + }; + + temperature-TD12 { + apple,key-id = "TD12"; + label = "SMC TD12"; + }; + + temperature-TD13 { + apple,key-id = "TD13"; + label = "SMC TD13"; + }; + + temperature-TD14 { + apple,key-id = "TD14"; + label = "SMC TD14"; + }; + + temperature-TD20 { + apple,key-id = "TD20"; + label = "SMC TD20"; + }; + + temperature-TD21 { + apple,key-id = "TD21"; + label = "SMC TD21"; + }; + + temperature-TD22 { + apple,key-id = "TD22"; + label = "SMC TD22"; + }; + + temperature-TD23 { + apple,key-id = "TD23"; + label = "SMC TD23"; + }; + + temperature-TD24 { + apple,key-id = "TD24"; + label = "SMC TD24"; + }; + + temperature-TDBP { + apple,key-id = "TDBP"; + label = "SMC TDBP"; + }; + + temperature-TDCR { + apple,key-id = "TDCR"; + label = "SMC TDCR"; + }; + + temperature-TDEC { + apple,key-id = "TDEC"; + label = "SMC TDEC"; + }; + + temperature-TDEL { + apple,key-id = "TDEL"; + label = "SMC TDEL"; + }; + + temperature-TDER { + apple,key-id = "TDER"; + label = "SMC TDER"; + }; + + temperature-TDTP { + apple,key-id = "TDTP"; + label = "SMC TDTP"; + }; + + temperature-TDVx { + apple,key-id = "TDVx"; + label = "SMC TDVx"; + }; + + temperature-TDeL { + apple,key-id = "TDeL"; + label = "SMC TDeL"; + }; + + temperature-TDeR { + apple,key-id = "TDeR"; + label = "SMC TDeR"; + }; + + temperature-TF2S { + apple,key-id = "TF2S"; + label = "SMC TF2S"; + }; + + temperature-TH0a { + apple,key-id = "TH0a"; + label = "SMC TH0a"; + }; + + temperature-TH0b { + apple,key-id = "TH0b"; + label = "SMC TH0b"; + }; + + temperature-TMVR { + apple,key-id = "TMVR"; + label = "SMC TMVR"; + }; + + temperature-TPD0 { + apple,key-id = "TPD0"; + label = "SMC TPD0"; + }; + + temperature-TPD1 { + apple,key-id = "TPD1"; + label = "SMC TPD1"; + }; + + temperature-TPD2 { + apple,key-id = "TPD2"; + label = "SMC TPD2"; + }; + + temperature-TPD3 { + apple,key-id = "TPD3"; + label = "SMC TPD3"; + }; + + temperature-TPD4 { + apple,key-id = "TPD4"; + label = "SMC TPD4"; + }; + + temperature-TPD5 { + apple,key-id = "TPD5"; + label = "SMC TPD5"; + }; + + temperature-TPD6 { + apple,key-id = "TPD6"; + label = "SMC TPD6"; + }; + + temperature-TPD7 { + apple,key-id = "TPD7"; + label = "SMC TPD7"; + }; + + temperature-TPD8 { + apple,key-id = "TPD8"; + label = "SMC TPD8"; + }; + + temperature-TPD9 { + apple,key-id = "TPD9"; + label = "SMC TPD9"; + }; + + temperature-TPDX { + apple,key-id = "TPDX"; + label = "SMC TPDX"; + }; + + temperature-TPDa { + apple,key-id = "TPDa"; + label = "SMC TPDa"; + }; + + temperature-TPDb { + apple,key-id = "TPDb"; + label = "SMC TPDb"; + }; + + temperature-TPDc { + apple,key-id = "TPDc"; + label = "SMC TPDc"; + }; + + temperature-TPDd { + apple,key-id = "TPDd"; + label = "SMC TPDd"; + }; + + temperature-TPDe { + apple,key-id = "TPDe"; + label = "SMC TPDe"; + }; + + temperature-TPDf { + apple,key-id = "TPDf"; + label = "SMC TPDf"; + }; + + temperature-TPH3 { + apple,key-id = "TPH3"; + label = "SMC TPH3"; + }; + + temperature-TPMP { + apple,key-id = "TPMP"; + label = "SMC TPMP"; + }; + + temperature-TPSP { + apple,key-id = "TPSP"; + label = "SMC TPSP"; + }; + + temperature-TRD0 { + apple,key-id = "TRD0"; + label = "SMC TRD0"; + }; + + temperature-TRD1 { + apple,key-id = "TRD1"; + label = "SMC TRD1"; + }; + + temperature-TRD2 { + apple,key-id = "TRD2"; + label = "SMC TRD2"; + }; + + temperature-TRD3 { + apple,key-id = "TRD3"; + label = "SMC TRD3"; + }; + + temperature-TRD4 { + apple,key-id = "TRD4"; + label = "SMC TRD4"; + }; + + temperature-TRD5 { + apple,key-id = "TRD5"; + label = "SMC TRD5"; + }; + + temperature-TRD6 { + apple,key-id = "TRD6"; + label = "SMC TRD6"; + }; + + temperature-TRD7 { + apple,key-id = "TRD7"; + label = "SMC TRD7"; + }; + + temperature-TRD8 { + apple,key-id = "TRD8"; + label = "SMC TRD8"; + }; + + temperature-TRD9 { + apple,key-id = "TRD9"; + label = "SMC TRD9"; + }; + + temperature-TRDX { + apple,key-id = "TRDX"; + label = "SMC TRDX"; + }; + + temperature-TRDa { + apple,key-id = "TRDa"; + label = "SMC TRDa"; + }; + + temperature-TRDb { + apple,key-id = "TRDb"; + label = "SMC TRDb"; + }; + + temperature-TRDc { + apple,key-id = "TRDc"; + label = "SMC TRDc"; + }; + + temperature-TRDd { + apple,key-id = "TRDd"; + label = "SMC TRDd"; + }; + + temperature-TRDe { + apple,key-id = "TRDe"; + label = "SMC TRDe"; + }; + + temperature-TRDf { + apple,key-id = "TRDf"; + label = "SMC TRDf"; + }; + + temperature-TS0P { + apple,key-id = "TS0P"; + label = "SMC TS0P"; + }; + + temperature-TSG1 { + apple,key-id = "TSG1"; + label = "SMC TSG1"; + }; + + temperature-TUD0 { + apple,key-id = "TUD0"; + label = "SMC TUD0"; + }; + + temperature-TUD1 { + apple,key-id = "TUD1"; + label = "SMC TUD1"; + }; + + temperature-TUD2 { + apple,key-id = "TUD2"; + label = "SMC TUD2"; + }; + + temperature-TUD3 { + apple,key-id = "TUD3"; + label = "SMC TUD3"; + }; + + temperature-TUD4 { + apple,key-id = "TUD4"; + label = "SMC TUD4"; + }; + + temperature-TUD5 { + apple,key-id = "TUD5"; + label = "SMC TUD5"; + }; + + temperature-TUD6 { + apple,key-id = "TUD6"; + label = "SMC TUD6"; + }; + + temperature-TUD7 { + apple,key-id = "TUD7"; + label = "SMC TUD7"; + }; + + temperature-TUD8 { + apple,key-id = "TUD8"; + label = "SMC TUD8"; + }; + + temperature-TUD9 { + apple,key-id = "TUD9"; + label = "SMC TUD9"; + }; + + temperature-TUDX { + apple,key-id = "TUDX"; + label = "SMC TUDX"; + }; + + temperature-TUDa { + apple,key-id = "TUDa"; + label = "SMC TUDa"; + }; + + temperature-TUDb { + apple,key-id = "TUDb"; + label = "SMC TUDb"; + }; + + temperature-TUDc { + apple,key-id = "TUDc"; + label = "SMC TUDc"; + }; + + temperature-TUDd { + apple,key-id = "TUDd"; + label = "SMC TUDd"; + }; + + temperature-TUDe { + apple,key-id = "TUDe"; + label = "SMC TUDe"; + }; + + temperature-TUDf { + apple,key-id = "TUDf"; + label = "SMC TUDf"; + }; + + temperature-TVA0 { + apple,key-id = "TVA0"; + label = "SMC TVA0"; + }; + + temperature-TVD0 { + apple,key-id = "TVD0"; + label = "SMC TVD0"; + }; + + temperature-TVMR { + apple,key-id = "TVMR"; + label = "SMC TVMR"; + }; + + temperature-TVMX { + apple,key-id = "TVMX"; + label = "SMC TVMX"; + }; + + temperature-TVMr { + apple,key-id = "TVMr"; + label = "SMC TVMr"; + }; + + temperature-TVS0 { + apple,key-id = "TVS0"; + label = "SMC TVS0"; + }; + + temperature-TVS1 { + apple,key-id = "TVS1"; + label = "SMC TVS1"; + }; + + temperature-TVS2 { + apple,key-id = "TVS2"; + label = "SMC TVS2"; + }; + + temperature-TVV0 { + apple,key-id = "TVV0"; + label = "SMC TVV0"; + }; + + temperature-TVXX { + apple,key-id = "TVXX"; + label = "SMC TVXX"; + }; + + temperature-TVXh { + apple,key-id = "TVXh"; + label = "SMC TVXh"; + }; + + temperature-TVXm { + apple,key-id = "TVXm"; + label = "SMC TVXm"; + }; + + temperature-TVh0 { + apple,key-id = "TVh0"; + label = "SMC TVh0"; + }; + + temperature-TVh1 { + apple,key-id = "TVh1"; + label = "SMC TVh1"; + }; + + temperature-TVm0 { + apple,key-id = "TVm0"; + label = "SMC TVm0"; + }; + + temperature-TVxx { + apple,key-id = "TVxx"; + label = "SMC TVxx"; + }; + + temperature-TaLP { + apple,key-id = "TaLP"; + label = "SMC TaLP"; + }; + + temperature-TaLT { + apple,key-id = "TaLT"; + label = "SMC TaLT"; + }; + + temperature-TaLW { + apple,key-id = "TaLW"; + label = "SMC TaLW"; + }; + + temperature-TaRF { + apple,key-id = "TaRF"; + label = "SMC TaRF"; + }; + + temperature-TaRT { + apple,key-id = "TaRT"; + label = "SMC TaRT"; + }; + + temperature-TaRW { + apple,key-id = "TaRW"; + label = "SMC TaRW"; + }; + + temperature-TaTP { + apple,key-id = "TaTP"; + label = "SMC TaTP"; + }; + + temperature-Te04 { + apple,key-id = "Te04"; + label = "E-cluster Te04"; + }; + + temperature-Te05 { + apple,key-id = "Te05"; + label = "E-cluster Te05"; + }; + + temperature-Te06 { + apple,key-id = "Te06"; + label = "E-cluster Te06"; + }; + + temperature-Te0R { + apple,key-id = "Te0R"; + label = "E-cluster Te0R"; + }; + + temperature-Te0S { + apple,key-id = "Te0S"; + label = "E-cluster Te0S"; + }; + + temperature-Te0T { + apple,key-id = "Te0T"; + label = "E-cluster Te0T"; + }; + + temperature-TfC0 { + apple,key-id = "TfC0"; + label = "SMC TfC0"; + }; + + temperature-TfC1 { + apple,key-id = "TfC1"; + label = "SMC TfC1"; + }; + + temperature-TfC2 { + apple,key-id = "TfC2"; + label = "SMC TfC2"; + }; + + temperature-TfC3 { + apple,key-id = "TfC3"; + label = "SMC TfC3"; + }; + + temperature-TfC4 { + apple,key-id = "TfC4"; + label = "SMC TfC4"; + }; + + temperature-Tg04 { + apple,key-id = "Tg04"; + label = "GPU Tg04"; + }; + + temperature-Tg0K { + apple,key-id = "Tg0K"; + label = "GPU Tg0K"; + }; + + temperature-Tg0L { + apple,key-id = "Tg0L"; + label = "GPU Tg0L"; + }; + + temperature-Tg0R { + apple,key-id = "Tg0R"; + label = "GPU Tg0R"; + }; + + temperature-Tg0X { + apple,key-id = "Tg0X"; + label = "GPU Tg0X"; + }; + + temperature-Tg0d { + apple,key-id = "Tg0d"; + label = "GPU Tg0d"; + }; + + temperature-Tg0e { + apple,key-id = "Tg0e"; + label = "GPU Tg0e"; + }; + + temperature-Tg0j { + apple,key-id = "Tg0j"; + label = "GPU Tg0j"; + }; + + temperature-Tg0y { + apple,key-id = "Tg0y"; + label = "GPU Tg0y"; + }; + + temperature-Tg1E { + apple,key-id = "Tg1E"; + label = "GPU Tg1E"; + }; + + temperature-Tg1F { + apple,key-id = "Tg1F"; + label = "GPU Tg1F"; + }; + + temperature-Tg1U { + apple,key-id = "Tg1U"; + label = "GPU Tg1U"; + }; + + temperature-Tg1c { + apple,key-id = "Tg1c"; + label = "GPU Tg1c"; + }; + + temperature-Tg1d { + apple,key-id = "Tg1d"; + label = "GPU Tg1d"; + }; + + temperature-Tg1k { + apple,key-id = "Tg1k"; + label = "GPU Tg1k"; + }; + + temperature-Tp00 { + apple,key-id = "Tp00"; + label = "P-cluster Tp00"; + }; + + temperature-Tp01 { + apple,key-id = "Tp01"; + label = "P-cluster Tp01"; + }; + + temperature-Tp02 { + apple,key-id = "Tp02"; + label = "P-cluster Tp02"; + }; + + temperature-Tp04 { + apple,key-id = "Tp04"; + label = "P-cluster Tp04"; + }; + + temperature-Tp05 { + apple,key-id = "Tp05"; + label = "P-cluster Tp05"; + }; + + temperature-Tp06 { + apple,key-id = "Tp06"; + label = "P-cluster Tp06"; + }; + + temperature-Tp08 { + apple,key-id = "Tp08"; + label = "P-cluster Tp08"; + }; + + temperature-Tp09 { + apple,key-id = "Tp09"; + label = "P-cluster Tp09"; + }; + + temperature-Tp0A { + apple,key-id = "Tp0A"; + label = "P-cluster Tp0A"; + }; + + temperature-Tp0C { + apple,key-id = "Tp0C"; + label = "P-cluster Tp0C"; + }; + + temperature-Tp0D { + apple,key-id = "Tp0D"; + label = "P-cluster Tp0D"; + }; + + temperature-Tp0E { + apple,key-id = "Tp0E"; + label = "P-cluster Tp0E"; + }; + + temperature-Tp0G { + apple,key-id = "Tp0G"; + label = "P-cluster Tp0G"; + }; + + temperature-Tp0H { + apple,key-id = "Tp0H"; + label = "P-cluster Tp0H"; + }; + + temperature-Tp0I { + apple,key-id = "Tp0I"; + label = "P-cluster Tp0I"; + }; + + temperature-Tp0K { + apple,key-id = "Tp0K"; + label = "P-cluster Tp0K"; + }; + + temperature-Tp0L { + apple,key-id = "Tp0L"; + label = "P-cluster Tp0L"; + }; + + temperature-Tp0M { + apple,key-id = "Tp0M"; + label = "P-cluster Tp0M"; + }; + + temperature-Tp0O { + apple,key-id = "Tp0O"; + label = "P-cluster Tp0O"; + }; + + temperature-Tp0P { + apple,key-id = "Tp0P"; + label = "P-cluster Tp0P"; + }; + + temperature-Tp0Q { + apple,key-id = "Tp0Q"; + label = "P-cluster Tp0Q"; + }; + + temperature-Tp0S { + apple,key-id = "Tp0S"; + label = "P-cluster Tp0S"; + }; + + temperature-Tp0T { + apple,key-id = "Tp0T"; + label = "P-cluster Tp0T"; + }; + + temperature-Tp0U { + apple,key-id = "Tp0U"; + label = "P-cluster Tp0U"; + }; + + temperature-Tp0W { + apple,key-id = "Tp0W"; + label = "P-cluster Tp0W"; + }; + + temperature-Tp0X { + apple,key-id = "Tp0X"; + label = "P-cluster Tp0X"; + }; + + temperature-Tp0Y { + apple,key-id = "Tp0Y"; + label = "P-cluster Tp0Y"; + }; + + temperature-Tp0a { + apple,key-id = "Tp0a"; + label = "P-cluster Tp0a"; + }; + + temperature-Tp0b { + apple,key-id = "Tp0b"; + label = "P-cluster Tp0b"; + }; + + temperature-Tp0c { + apple,key-id = "Tp0c"; + label = "P-cluster Tp0c"; + }; + + temperature-Tp0d { + apple,key-id = "Tp0d"; + label = "P-cluster Tp0d"; + }; + + temperature-Tp0e { + apple,key-id = "Tp0e"; + label = "P-cluster Tp0e"; + }; + + temperature-Tp0f { + apple,key-id = "Tp0f"; + label = "P-cluster Tp0f"; + }; + + temperature-Tp0g { + apple,key-id = "Tp0g"; + label = "P-cluster Tp0g"; + }; + + temperature-Tp0i { + apple,key-id = "Tp0i"; + label = "P-cluster Tp0i"; + }; + + temperature-Tp0k { + apple,key-id = "Tp0k"; + label = "P-cluster Tp0k"; + }; + + temperature-Tp0l { + apple,key-id = "Tp0l"; + label = "P-cluster Tp0l"; + }; + + temperature-Tp0m { + apple,key-id = "Tp0m"; + label = "P-cluster Tp0m"; + }; + + temperature-Tp0n { + apple,key-id = "Tp0n"; + label = "P-cluster Tp0n"; + }; + + temperature-Tp0o { + apple,key-id = "Tp0o"; + label = "P-cluster Tp0o"; + }; + + temperature-Tp0q { + apple,key-id = "Tp0q"; + label = "P-cluster Tp0q"; + }; + + temperature-Tp0t { + apple,key-id = "Tp0t"; + label = "P-cluster Tp0t"; + }; + + temperature-Tp0u { + apple,key-id = "Tp0u"; + label = "P-cluster Tp0u"; + }; + + temperature-Tp0v { + apple,key-id = "Tp0v"; + label = "P-cluster Tp0v"; + }; + + temperature-Tp0w { + apple,key-id = "Tp0w"; + label = "P-cluster Tp0w"; + }; + + temperature-Tp1i { + apple,key-id = "Tp1i"; + label = "P-cluster Tp1i"; + }; + + temperature-Tp1j { + apple,key-id = "Tp1j"; + label = "P-cluster Tp1j"; + }; + + temperature-Tp1k { + apple,key-id = "Tp1k"; + label = "P-cluster Tp1k"; + }; + + temperature-Tp1m { + apple,key-id = "Tp1m"; + label = "P-cluster Tp1m"; + }; + + temperature-Tp1n { + apple,key-id = "Tp1n"; + label = "P-cluster Tp1n"; + }; + + temperature-Tp1o { + apple,key-id = "Tp1o"; + label = "P-cluster Tp1o"; + }; + + temperature-Tp1q { + apple,key-id = "Tp1q"; + label = "P-cluster Tp1q"; + }; + + temperature-Tp1t { + apple,key-id = "Tp1t"; + label = "P-cluster Tp1t"; + }; + + temperature-Tp1u { + apple,key-id = "Tp1u"; + label = "P-cluster Tp1u"; + }; + + temperature-Tp1v { + apple,key-id = "Tp1v"; + label = "P-cluster Tp1v"; + }; + + temperature-Tp1w { + apple,key-id = "Tp1w"; + label = "P-cluster Tp1w"; + }; + + temperature-Tp1x { + apple,key-id = "Tp1x"; + label = "P-cluster Tp1x"; + }; + + temperature-Tp1y { + apple,key-id = "Tp1y"; + label = "P-cluster Tp1y"; + }; + + temperature-Tp1z { + apple,key-id = "Tp1z"; + label = "P-cluster Tp1z"; + }; + + temperature-Tp20 { + apple,key-id = "Tp20"; + label = "P-cluster Tp20"; + }; + + temperature-Tp21 { + apple,key-id = "Tp21"; + label = "P-cluster Tp21"; + }; + + temperature-Tp22 { + apple,key-id = "Tp22"; + label = "P-cluster Tp22"; + }; + + temperature-Tp23 { + apple,key-id = "Tp23"; + label = "P-cluster Tp23"; + }; + + temperature-Tp24 { + apple,key-id = "Tp24"; + label = "P-cluster Tp24"; + }; + + temperature-Tp25 { + apple,key-id = "Tp25"; + label = "P-cluster Tp25"; + }; + + temperature-Tp26 { + apple,key-id = "Tp26"; + label = "P-cluster Tp26"; + }; + + temperature-Tp27 { + apple,key-id = "Tp27"; + label = "P-cluster Tp27"; + }; + + temperature-Tp28 { + apple,key-id = "Tp28"; + label = "P-cluster Tp28"; + }; + + temperature-Tp29 { + apple,key-id = "Tp29"; + label = "P-cluster Tp29"; + }; + + temperature-Tp2A { + apple,key-id = "Tp2A"; + label = "P-cluster Tp2A"; + }; + + temperature-Tp2B { + apple,key-id = "Tp2B"; + label = "P-cluster Tp2B"; + }; + + temperature-Tp2C { + apple,key-id = "Tp2C"; + label = "P-cluster Tp2C"; + }; + + temperature-Tp2D { + apple,key-id = "Tp2D"; + label = "P-cluster Tp2D"; + }; + + temperature-Tp2E { + apple,key-id = "Tp2E"; + label = "P-cluster Tp2E"; + }; + + temperature-Tp2G { + apple,key-id = "Tp2G"; + label = "P-cluster Tp2G"; + }; + + temperature-Tp2I { + apple,key-id = "Tp2I"; + label = "P-cluster Tp2I"; + }; + + temperature-Tp2J { + apple,key-id = "Tp2J"; + label = "P-cluster Tp2J"; + }; + + temperature-Tp2K { + apple,key-id = "Tp2K"; + label = "P-cluster Tp2K"; + }; + + temperature-Tp2L { + apple,key-id = "Tp2L"; + label = "P-cluster Tp2L"; + }; + + temperature-Tp2M { + apple,key-id = "Tp2M"; + label = "P-cluster Tp2M"; + }; + + temperature-Tp2N { + apple,key-id = "Tp2N"; + label = "P-cluster Tp2N"; + }; + + temperature-Tp2O { + apple,key-id = "Tp2O"; + label = "P-cluster Tp2O"; + }; + + temperature-Tp2Q { + apple,key-id = "Tp2Q"; + label = "P-cluster Tp2Q"; + }; + + temperature-Tp2R { + apple,key-id = "Tp2R"; + label = "P-cluster Tp2R"; + }; + + temperature-Tp2S { + apple,key-id = "Tp2S"; + label = "P-cluster Tp2S"; + }; + + temperature-Tp2T { + apple,key-id = "Tp2T"; + label = "P-cluster Tp2T"; + }; + + temperature-Tp2U { + apple,key-id = "Tp2U"; + label = "P-cluster Tp2U"; + }; + + temperature-Tp2V { + apple,key-id = "Tp2V"; + label = "P-cluster Tp2V"; + }; + + temperature-Tp2W { + apple,key-id = "Tp2W"; + label = "P-cluster Tp2W"; + }; + + temperature-Tp2X { + apple,key-id = "Tp2X"; + label = "P-cluster Tp2X"; + }; + + temperature-Ts00 { + apple,key-id = "Ts00"; + label = "SMC Ts00"; + }; + + temperature-Ts01 { + apple,key-id = "Ts01"; + label = "SMC Ts01"; + }; + + temperature-Ts02 { + apple,key-id = "Ts02"; + label = "SMC Ts02"; + }; + + temperature-Ts04 { + apple,key-id = "Ts04"; + label = "SMC Ts04"; + }; + + temperature-Ts05 { + apple,key-id = "Ts05"; + label = "SMC Ts05"; + }; + + temperature-Ts06 { + apple,key-id = "Ts06"; + label = "SMC Ts06"; + }; + + temperature-Ts08 { + apple,key-id = "Ts08"; + label = "SMC Ts08"; + }; + + temperature-Ts09 { + apple,key-id = "Ts09"; + label = "SMC Ts09"; + }; + + temperature-Ts0A { + apple,key-id = "Ts0A"; + label = "SMC Ts0A"; + }; + + temperature-Ts0C { + apple,key-id = "Ts0C"; + label = "SMC Ts0C"; + }; + + temperature-Ts0D { + apple,key-id = "Ts0D"; + label = "SMC Ts0D"; + }; + + temperature-Ts0E { + apple,key-id = "Ts0E"; + label = "SMC Ts0E"; + }; + + temperature-Ts0G { + apple,key-id = "Ts0G"; + label = "SMC Ts0G"; + }; + + temperature-Ts0H { + apple,key-id = "Ts0H"; + label = "SMC Ts0H"; + }; + + temperature-Ts0I { + apple,key-id = "Ts0I"; + label = "SMC Ts0I"; + }; + + temperature-Ts0K { + apple,key-id = "Ts0K"; + label = "SMC Ts0K"; + }; + + temperature-Ts0L { + apple,key-id = "Ts0L"; + label = "SMC Ts0L"; + }; + + temperature-Ts0M { + apple,key-id = "Ts0M"; + label = "SMC Ts0M"; + }; + + temperature-Ts0O { + apple,key-id = "Ts0O"; + label = "SMC Ts0O"; + }; + + temperature-Ts0P { + apple,key-id = "Ts0P"; + label = "SMC Ts0P"; + }; + + temperature-Ts0Q { + apple,key-id = "Ts0Q"; + label = "SMC Ts0Q"; + }; + + temperature-Ts0R { + apple,key-id = "Ts0R"; + label = "SMC Ts0R"; + }; + + temperature-Ts0T { + apple,key-id = "Ts0T"; + label = "SMC Ts0T"; + }; + + temperature-Ts0U { + apple,key-id = "Ts0U"; + label = "SMC Ts0U"; + }; + + temperature-Ts0W { + apple,key-id = "Ts0W"; + label = "SMC Ts0W"; + }; + + temperature-Ts1P { + apple,key-id = "Ts1P"; + label = "SMC Ts1P"; + }; + + temperature-Tsx0 { + apple,key-id = "Tsx0"; + label = "SMC Tsx0"; + }; + + temperature-Tsx1 { + apple,key-id = "Tsx1"; + label = "SMC Tsx1"; + }; + + voltage-VDM2 { + apple,key-id = "VDM2"; + label = "SMC VDM2"; + }; + + voltage-VDMA { + apple,key-id = "VDMA"; + label = "SMC VDMA"; + }; + + voltage-VDMM { + apple,key-id = "VDMM"; + label = "SMC VDMM"; + }; + + voltage-VP0R { + apple,key-id = "VP0R"; + label = "SMC VP0R"; + }; + + voltage-VP0b { + apple,key-id = "VP0b"; + label = "SMC VP0b"; + }; + + voltage-VP1b { + apple,key-id = "VP1b"; + label = "SMC VP1b"; + }; + + voltage-VP1l { + apple,key-id = "VP1l"; + label = "SMC VP1l"; + }; + + voltage-VP2b { + apple,key-id = "VP2b"; + label = "SMC VP2b"; + }; + + voltage-VP3b { + apple,key-id = "VP3b"; + label = "SMC VP3b"; + }; + + voltage-VP4b { + apple,key-id = "VP4b"; + label = "SMC VP4b"; + }; + + voltage-VP5l { + apple,key-id = "VP5l"; + label = "SMC VP5l"; + }; + + voltage-VP7b { + apple,key-id = "VP7b"; + label = "SMC VP7b"; + }; + + voltage-VP7l { + apple,key-id = "VP7l"; + label = "SMC VP7l"; + }; + + voltage-VP9b { + apple,key-id = "VP9b"; + label = "SMC VP9b"; + }; + + voltage-VP9l { + apple,key-id = "VP9l"; + label = "SMC VP9l"; + }; + + voltage-VPal { + apple,key-id = "VPal"; + label = "SMC VPal"; + }; + + voltage-VPbb { + apple,key-id = "VPbb"; + label = "SMC VPbb"; + }; + + voltage-VPdb { + apple,key-id = "VPdb"; + label = "SMC VPdb"; + }; + + voltage-VPeb { + apple,key-id = "VPeb"; + label = "SMC VPeb"; + }; + + voltage-VR0b { + apple,key-id = "VR0b"; + label = "SMC VR0b"; + }; + + voltage-VR1b { + apple,key-id = "VR1b"; + label = "SMC VR1b"; + }; + + voltage-VR2b { + apple,key-id = "VR2b"; + label = "SMC VR2b"; + }; + + voltage-VR3b { + apple,key-id = "VR3b"; + label = "SMC VR3b"; + }; + + voltage-VR3l { + apple,key-id = "VR3l"; + label = "SMC VR3l"; + }; + + voltage-VR4b { + apple,key-id = "VR4b"; + label = "SMC VR4b"; + }; + + voltage-VR7b { + apple,key-id = "VR7b"; + label = "SMC VR7b"; + }; + + voltage-VR8l { + apple,key-id = "VR8l"; + label = "SMC VR8l"; + }; + + voltage-VR9b { + apple,key-id = "VR9b"; + label = "SMC VR9b"; + }; + + voltage-VR9l { + apple,key-id = "VR9l"; + label = "SMC VR9l"; + }; + + voltage-VRal { + apple,key-id = "VRal"; + label = "SMC VRal"; + }; + + voltage-VRbb { + apple,key-id = "VRbb"; + label = "SMC VRbb"; + }; + + voltage-VRdb { + apple,key-id = "VRdb"; + label = "SMC VRdb"; + }; + + voltage-VReb { + apple,key-id = "VReb"; + label = "SMC VReb"; + }; + + voltage-VS0b { + apple,key-id = "VS0b"; + label = "SMC VS0b"; + }; + + voltage-VS1b { + apple,key-id = "VS1b"; + label = "SMC VS1b"; + }; + + voltage-VS2b { + apple,key-id = "VS2b"; + label = "SMC VS2b"; + }; + + voltage-VS3b { + apple,key-id = "VS3b"; + label = "SMC VS3b"; + }; + + voltage-VS4b { + apple,key-id = "VS4b"; + label = "SMC VS4b"; + }; + + voltage-VS7b { + apple,key-id = "VS7b"; + label = "SMC VS7b"; + }; + + voltage-VS9b { + apple,key-id = "VS9b"; + label = "SMC VS9b"; + }; + + voltage-VS9l { + apple,key-id = "VS9l"; + label = "SMC VS9l"; + }; + + voltage-VSal { + apple,key-id = "VSal"; + label = "SMC VSal"; + }; + + voltage-VSbb { + apple,key-id = "VSbb"; + label = "SMC VSbb"; + }; + + voltage-VSeb { + apple,key-id = "VSeb"; + label = "SMC VSeb"; + }; + + voltage-Vb0f { + apple,key-id = "Vb0f"; + label = "SMC Vb0f"; + }; + + voltage-Vb1f { + apple,key-id = "Vb1f"; + label = "SMC Vb1f"; + }; + + voltage-SBA1 { + apple,key-id = "SBA1"; + label = "Battery Cell 1"; + }; + + voltage-SBA2 { + apple,key-id = "SBA2"; + label = "Battery Cell 2"; + }; + + voltage-SBA3 { + apple,key-id = "SBA3"; + label = "Battery Cell 3"; + }; + + current-I3F2 { + apple,key-id = "I3F2"; + label = "SMC I3F2"; + }; + + current-I5SR { + apple,key-id = "I5SR"; + label = "SMC I5SR"; + }; + + current-IALR { + apple,key-id = "IALR"; + label = "SMC IALR"; + }; + + current-IARR { + apple,key-id = "IARR"; + label = "SMC IARR"; + }; + + current-ICMC { + apple,key-id = "ICMC"; + label = "SMC ICMC"; + }; + + current-ID3C { + apple,key-id = "ID3C"; + label = "SMC ID3C"; + }; + + current-IDBC { + apple,key-id = "IDBC"; + label = "SMC IDBC"; + }; + + current-IDBR { + apple,key-id = "IDBR"; + label = "SMC IDBR"; + }; + + current-IFLR { + apple,key-id = "IFLR"; + label = "SMC IFLR"; + }; + + current-IH0R { + apple,key-id = "IH0R"; + label = "SMC IH0R"; + }; + + current-IHCR { + apple,key-id = "IHCR"; + label = "SMC IHCR"; + }; + + current-IIDR { + apple,key-id = "IIDR"; + label = "SMC IIDR"; + }; + + current-IINC { + apple,key-id = "IINC"; + label = "SMC IINC"; + }; + + current-IMVC { + apple,key-id = "IMVC"; + label = "SMC IMVC"; + }; + + current-IMVR { + apple,key-id = "IMVR"; + label = "SMC IMVR"; + }; + + current-IP0b { + apple,key-id = "IP0b"; + label = "SMC IP0b"; + }; + + current-IP1b { + apple,key-id = "IP1b"; + label = "SMC IP1b"; + }; + + current-IP1l { + apple,key-id = "IP1l"; + label = "SMC IP1l"; + }; + + current-IP2b { + apple,key-id = "IP2b"; + label = "SMC IP2b"; + }; + + current-IP3b { + apple,key-id = "IP3b"; + label = "SMC IP3b"; + }; + + current-IP4b { + apple,key-id = "IP4b"; + label = "SMC IP4b"; + }; + + current-IP5l { + apple,key-id = "IP5l"; + label = "SMC IP5l"; + }; + + current-IP7b { + apple,key-id = "IP7b"; + label = "SMC IP7b"; + }; + + current-IP7l { + apple,key-id = "IP7l"; + label = "SMC IP7l"; + }; + + current-IP9b { + apple,key-id = "IP9b"; + label = "SMC IP9b"; + }; + + current-IP9l { + apple,key-id = "IP9l"; + label = "SMC IP9l"; + }; + + current-IPBR { + apple,key-id = "IPBR"; + label = "SMC IPBR"; + }; + + current-IPMC { + apple,key-id = "IPMC"; + label = "SMC IPMC"; + }; + + current-IPSC { + apple,key-id = "IPSC"; + label = "SMC IPSC"; + }; + + current-IPal { + apple,key-id = "IPal"; + label = "SMC IPal"; + }; + + current-IPbb { + apple,key-id = "IPbb"; + label = "SMC IPbb"; + }; + + current-IPdb { + apple,key-id = "IPdb"; + label = "SMC IPdb"; + }; + + current-IPeb { + apple,key-id = "IPeb"; + label = "SMC IPeb"; + }; + + current-IR0b { + apple,key-id = "IR0b"; + label = "SMC IR0b"; + }; + + current-IR1b { + apple,key-id = "IR1b"; + label = "SMC IR1b"; + }; + + current-IR2b { + apple,key-id = "IR2b"; + label = "SMC IR2b"; + }; + + current-IR3b { + apple,key-id = "IR3b"; + label = "SMC IR3b"; + }; + + current-IR3l { + apple,key-id = "IR3l"; + label = "SMC IR3l"; + }; + + current-IR4b { + apple,key-id = "IR4b"; + label = "SMC IR4b"; + }; + + current-IR7b { + apple,key-id = "IR7b"; + label = "SMC IR7b"; + }; + + current-IR8l { + apple,key-id = "IR8l"; + label = "SMC IR8l"; + }; + + current-IR9b { + apple,key-id = "IR9b"; + label = "SMC IR9b"; + }; + + current-IR9l { + apple,key-id = "IR9l"; + label = "SMC IR9l"; + }; + + current-IRal { + apple,key-id = "IRal"; + label = "SMC IRal"; + }; + + current-IRbb { + apple,key-id = "IRbb"; + label = "SMC IRbb"; + }; + + current-IRdb { + apple,key-id = "IRdb"; + label = "SMC IRdb"; + }; + + current-IReb { + apple,key-id = "IReb"; + label = "SMC IReb"; + }; + + current-IS0b { + apple,key-id = "IS0b"; + label = "SMC IS0b"; + }; + + current-IS1b { + apple,key-id = "IS1b"; + label = "SMC IS1b"; + }; + + current-IS2b { + apple,key-id = "IS2b"; + label = "SMC IS2b"; + }; + + current-IS3b { + apple,key-id = "IS3b"; + label = "SMC IS3b"; + }; + + current-IS4b { + apple,key-id = "IS4b"; + label = "SMC IS4b"; + }; + + current-IS7b { + apple,key-id = "IS7b"; + label = "SMC IS7b"; + }; + + current-IS9b { + apple,key-id = "IS9b"; + label = "SMC IS9b"; + }; + + current-IS9l { + apple,key-id = "IS9l"; + label = "SMC IS9l"; + }; + + current-ISDR { + apple,key-id = "ISDR"; + label = "SMC ISDR"; }; - /* - * The speaker amps' shared shutdown line (SDZ, AP pin 54, low = - * shutdown). One fixed regulator, the SDZ supply of all six, not - * per-amp shutdown-gpios: with GPIO_SHARED six references become a - * vote, and one amp driving the line low would not shut the other - * five down. - */ - speaker_sdz: regulator-sn012776-sdz { - compatible = "regulator-fixed"; - regulator-name = "sn012776-sdz"; - gpios = <&pinctrl 54 GPIO_ACTIVE_HIGH>; - enable-active-high; + current-ISal { + apple,key-id = "ISal"; + label = "SMC ISal"; }; - /* - * The AOP firmware runs the stream (MCA cluster 0, one serializer for - * all six amps); the J616 variant of macaudio links the AOP audio - * component's speaker back-end (DAI 3) to the amps. The codecs are - * listed in the ADT provider order, so each gets its native playback - * slot (LW1 0, RW1 1, LW2 2, RW2 3, LT 4, RT 5). The idle masks are the - * ADT amp-tx-zd-config rows 0 and 1: LW1 drives zeros in the right - * amps' sense slots, RW1 in the left amps'. - */ - sound: sound { - compatible = "apple,j616-macaudio", "apple,macaudio"; - model = "MacBook Pro J616"; - #address-cells = <1>; - #size-cells = <0>; + current-ISbb { + apple,key-id = "ISbb"; + label = "SMC ISbb"; + }; - dai-link@0 { - reg = <0>; - link-name = "Speakers"; - dai-tdm-idle-mode-0 = "zero"; - dai-tdm-idle-mode-1 = "zero"; - dai-tdm-slot-tx-idle-mask-0 = <0xf0f0f0>; - dai-tdm-slot-tx-idle-mask-1 = <0x0f0f0f>; + current-ISeb { + apple,key-id = "ISeb"; + label = "SMC ISeb"; + }; - cpu { - sound-dai = <&aop_audio 3>; - }; + current-IU1C { + apple,key-id = "IU1C"; + label = "SMC IU1C"; + }; - codec { - sound-dai = <&speaker_left_woof1>, <&speaker_right_woof1>, - <&speaker_left_woof2>, <&speaker_right_woof2>, - <&speaker_left_tweet>, <&speaker_right_tweet>; - }; - }; + current-IU1R { + apple,key-id = "IU1R"; + label = "SMC IU1R"; }; - /* - * The headphone jack: the AOP audio component's jack back-end (DAI 4) - * to the CS42L84. A card of its own, so a codec that does not answer - * costs the jack only, never the speakers. - */ - sound_jack: sound-jack { - compatible = "apple,j616-macaudio-jack", "apple,macaudio"; - model = "MacBook Pro J616 Jack"; - #address-cells = <1>; - #size-cells = <0>; + current-IU2C { + apple,key-id = "IU2C"; + label = "SMC IU2C"; + }; - dai-link@0 { - reg = <0>; - link-name = "Headphone Jack"; + current-IU2R { + apple,key-id = "IU2R"; + label = "SMC IU2R"; + }; - cpu { - sound-dai = <&aop_audio 4>; - }; + current-IW3C { + apple,key-id = "IW3C"; + label = "SMC IW3C"; + }; - codec { - sound-dai = <&jack_codec>; - }; - }; + current-Ib0f { + apple,key-id = "Ib0f"; + label = "SMC Ib0f"; }; -}; -&cpuidle { - status = "okay"; -}; + current-Ib8f { + apple,key-id = "Ib8f"; + label = "SMC Ib8f"; + }; -&smc { - status = "okay"; -}; + current-SBAC { + apple,key-id = "SBAC"; + label = "Battery Current"; + }; -&smc_mbox { - status = "okay"; -}; + power-P3F2 { + apple,key-id = "P3F2"; + label = "SMC P3F2"; + }; -&nub_spmi0 { - status = "okay"; -}; + power-P5SR { + apple,key-id = "P5SR"; + label = "SMC P5SR"; + }; -&pinctrl { - status = "okay"; + power-PALR { + apple,key-id = "PALR"; + label = "SMC PALR"; + }; - wifi_clkreq_pins: wifi-clkreq-pins { - pinmux = ; + power-PARR { + apple,key-id = "PARR"; + label = "SMC PARR"; }; -}; -/* - * The AP takes none of the always-on bank's interrupts: the AOP firmware - * owns some of its pins and interrupt registers, and a chained handler here - * would see their pending bits without a handler of ours. - */ -&pinctrl_nub { - status = "okay"; - /delete-property/ interrupt-controller; - /delete-property/ #interrupt-cells; - /delete-property/ interrupts; -}; + power-PCMC { + apple,key-id = "PCMC"; + label = "SMC PCMC"; + }; -&fpwm0 { - status = "okay"; -}; + power-PD3C { + apple,key-id = "PD3C"; + label = "SMC PD3C"; + }; -/* The Type-C retimers (ADT atcrt at 0x18/0x19/0x1a) sit on this bus; no driver yet. */ -&i2c6 { - status = "okay"; -}; + power-PDBC { + apple,key-id = "PDBC"; + label = "SMC PDBC"; + }; -&mtp { - status = "okay"; -}; + power-PDBR { + apple,key-id = "PDBR"; + label = "SMC PDBR"; + }; -&mtp_mbox { - status = "okay"; -}; + power-PFLR { + apple,key-id = "PFLR"; + label = "SMC PFLR"; + }; -&mtp_dart { - status = "okay"; -}; + power-PH0R { + apple,key-id = "PH0R"; + label = "SMC PH0R"; + }; -&mtp_dockchannel { - status = "okay"; -}; + power-PHCR { + apple,key-id = "PHCR"; + label = "SMC PHCR"; + }; -&mtp_hid { - /* SMC key gp1c powers the trackpad analog front end. */ - apple,afe-smc = <&smc>; + power-PIDR { + apple,key-id = "PIDR"; + label = "SMC PIDR"; + }; - multi-touch { - firmware-name = "apple/tpmtfw-j616s.bin"; + power-PINC { + apple,key-id = "PINC"; + label = "SMC PINC"; }; - keyboard: keyboard { - hid-country-code = <0>; - apple,keyboard-layout-id = <2>; + power-PMVC { + apple,key-id = "PMVC"; + label = "SMC PMVC"; }; - stm { + power-PP0b { + apple,key-id = "PP0b"; + label = "SMC PP0b"; }; - actuator { + power-PP1b { + apple,key-id = "PP1b"; + label = "SMC PP1b"; }; - tp-accel { + power-PP1l { + apple,key-id = "PP1l"; + label = "SMC PP1l"; }; - mtp { + power-PP2b { + apple,key-id = "PP2b"; + label = "SMC PP2b"; }; -}; -&smc_hwmon { - /* - * TCMz must stay the first temperature child: the thermal zone names - * the sensor by its index, so the shared hwmon includes below only - * append after it. - */ - temperature-TCMz { - apple,key-id = "TCMz"; - label = "CPU Die Max"; + power-PP3b { + apple,key-id = "PP3b"; + label = "SMC PP3b"; + }; + + power-PP4b { + apple,key-id = "PP4b"; + label = "SMC PP4b"; + }; + + power-PP5l { + apple,key-id = "PP5l"; + label = "SMC PP5l"; + }; + + power-PP7b { + apple,key-id = "PP7b"; + label = "SMC PP7b"; + }; + + power-PP7l { + apple,key-id = "PP7l"; + label = "SMC PP7l"; + }; + + power-PP9b { + apple,key-id = "PP9b"; + label = "SMC PP9b"; + }; + + power-PP9l { + apple,key-id = "PP9l"; + label = "SMC PP9l"; + }; + + power-PPBR { + apple,key-id = "PPBR"; + label = "SMC PPBR"; + }; + + power-PPMC { + apple,key-id = "PPMC"; + label = "SMC PPMC"; + }; + + power-PPSC { + apple,key-id = "PPSC"; + label = "SMC PPSC"; + }; + + power-PPal { + apple,key-id = "PPal"; + label = "SMC PPal"; + }; + + power-PPbb { + apple,key-id = "PPbb"; + label = "SMC PPbb"; + }; + + power-PPdb { + apple,key-id = "PPdb"; + label = "SMC PPdb"; + }; + + power-PPeb { + apple,key-id = "PPeb"; + label = "SMC PPeb"; + }; + + power-PR0b { + apple,key-id = "PR0b"; + label = "SMC PR0b"; + }; + + power-PR1b { + apple,key-id = "PR1b"; + label = "SMC PR1b"; + }; + + power-PR2b { + apple,key-id = "PR2b"; + label = "SMC PR2b"; + }; + + power-PR3b { + apple,key-id = "PR3b"; + label = "SMC PR3b"; + }; + + power-PR3l { + apple,key-id = "PR3l"; + label = "SMC PR3l"; + }; + + power-PR4b { + apple,key-id = "PR4b"; + label = "SMC PR4b"; + }; + + power-PR7b { + apple,key-id = "PR7b"; + label = "SMC PR7b"; + }; + + power-PR8l { + apple,key-id = "PR8l"; + label = "SMC PR8l"; + }; + + power-PR9b { + apple,key-id = "PR9b"; + label = "SMC PR9b"; + }; + + power-PR9l { + apple,key-id = "PR9l"; + label = "SMC PR9l"; + }; + + power-PRal { + apple,key-id = "PRal"; + label = "SMC PRal"; + }; + + power-PRbb { + apple,key-id = "PRbb"; + label = "SMC PRbb"; + }; + + power-PRdb { + apple,key-id = "PRdb"; + label = "SMC PRdb"; + }; + + power-PReb { + apple,key-id = "PReb"; + label = "SMC PReb"; + }; + + power-PS0b { + apple,key-id = "PS0b"; + label = "SMC PS0b"; + }; + + power-PS1b { + apple,key-id = "PS1b"; + label = "SMC PS1b"; + }; + + power-PS2b { + apple,key-id = "PS2b"; + label = "SMC PS2b"; + }; + + power-PS3b { + apple,key-id = "PS3b"; + label = "SMC PS3b"; + }; + + power-PS4b { + apple,key-id = "PS4b"; + label = "SMC PS4b"; + }; + + power-PS7b { + apple,key-id = "PS7b"; + label = "SMC PS7b"; + }; + + power-PS9b { + apple,key-id = "PS9b"; + label = "SMC PS9b"; + }; + + power-PS9l { + apple,key-id = "PS9l"; + label = "SMC PS9l"; + }; + + power-PSDR { + apple,key-id = "PSDR"; + label = "SMC PSDR"; + }; + + power-PSal { + apple,key-id = "PSal"; + label = "SMC PSal"; + }; + + power-PSbb { + apple,key-id = "PSbb"; + label = "SMC PSbb"; + }; + + power-PSeb { + apple,key-id = "PSeb"; + label = "SMC PSeb"; + }; + + power-PTHS { + apple,key-id = "PTHS"; + label = "SMC PTHS"; + }; + + power-PU1C { + apple,key-id = "PU1C"; + label = "SMC PU1C"; + }; + + power-PU1R { + apple,key-id = "PU1R"; + label = "SMC PU1R"; + }; + + power-PU2C { + apple,key-id = "PU2C"; + label = "SMC PU2C"; + }; + + power-PU2R { + apple,key-id = "PU2R"; + label = "SMC PU2R"; + }; + + power-PW3C { + apple,key-id = "PW3C"; + label = "SMC PW3C"; + }; + + power-PZC0 { + apple,key-id = "PZC0"; + label = "SMC PZC0"; + }; + + power-PZC1 { + apple,key-id = "PZC1"; + label = "SMC PZC1"; + }; + + power-PZCB { + apple,key-id = "PZCB"; + label = "SMC PZCB"; + }; + + power-PZCU { + apple,key-id = "PZCU"; + label = "SMC PZCU"; + }; + + power-PZD1 { + apple,key-id = "PZD1"; + label = "SMC PZD1"; + }; + + power-PZT0 { + apple,key-id = "PZT0"; + label = "SMC PZT0"; + }; + + power-Pb0f { + apple,key-id = "Pb0f"; + label = "SMC Pb0f"; + }; + + power-SBAP { + apple,key-id = "SBAP"; + label = "Battery Power"; }; }; -/* Read-only fan speeds and more SMC sensors; every key exists on this machine's SMC. */ +/* + * Fan speeds, limits, targets and modes, and the common SMC sensors; every + * key exists on this machine's SMC. Both fans list their target (F0Tg, + * F1Tg) and mode (F0Md, F1Md) keys, so with the driver's fan_control + * parameter fanN_target becomes writable; by default the SMC drives the + * fans and the attributes are read-only. + */ #include "hwmon-common.dtsi" #include "hwmon-fan-dual.dtsi" #include "hwmon-laptop.dtsi" From 0d1540e6304f2850ba78ff50bcdf8078f7dde983 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Tue, 27 Oct 2026 17:16:17 +1100 Subject: [PATCH 101/184] drm/asahi: retire faulted G17 compute commands on the firmware's fault record When a compute command fails on the GPU the firmware writes a zero-duration completion record for it, which quarantines its queue, and reports the failure with a firmware event of type 7 carrying the queue and the stamp the command would have stored. It never stores that stamp. The driver ignored the event, so the quarantine waited for a retirement witness that could not arrive, and the queue kept its pool, its traversal scratch and its share of the lower GPU VA window for as long as the module stayed loaded. Under ray query stress that fails this way every few seconds (for example ray_query.stress.geometry_shader beside other clients) the retained queues filled the lower window within minutes. Every later allocation there failed: publications returned EBUSY, and gnome-shell crashed on ENOMEM creating a 16K texture after about 7 minutes. Decode the event as ComputeFault and accept its stamp as the faulted command's completion stamp for the quarantine's retirement witness, while it is newer than the stored one. The witness still requires the queue's ring to be fully consumed, so commands published after the failed one keep waiting for their own stamp. Port prerequisite for #505. Keep type 7 unknown on 25G83: the public retirement witness is qualified on 26A428 only, and a matching tag and aligned integers cannot establish T6040 DMA visibility. KUnit checks that identical words cannot become a T6040 release witness. Link: https://github.com/aurora-silicon/linux/commit/9a76756bf2d7122695042095530f03c441a9bcad Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/gpu/drm/asahi/g17/channel.rs | 6 +- drivers/gpu/drm/asahi/g17/event.rs | 7 +++ drivers/gpu/drm/asahi/g17/fw/channels.rs | 62 +++++++++++++++++++ drivers/gpu/drm/asahi/g17/queue/compute.rs | 31 +++++++++- .../drm/asahi/g17/queue/compute/retirement.rs | 15 +++++ drivers/gpu/drm/asahi/g17/runtime/events.rs | 11 +++- 6 files changed, 128 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/asahi/g17/channel.rs b/drivers/gpu/drm/asahi/g17/channel.rs index d96924cbbf9f9..b06743abfc446 100644 --- a/drivers/gpu/drm/asahi/g17/channel.rs +++ b/drivers/gpu/drm/asahi/g17/channel.rs @@ -147,6 +147,7 @@ impl Rings { /// the caller can service the copied event using the complete device owner. pub(crate) struct Events { role: Role, + graph: super::fw::variant::InitGraph, remaining: u32, unknown_reported: bool, } @@ -167,9 +168,10 @@ impl Events { Ok(consumer != producer) } - pub(crate) fn new(role: Role) -> Self { + pub(crate) fn new(role: Role, graph: super::fw::variant::InitGraph) -> Self { Self { role, + graph, remaining: EVENT_SLOTS, unknown_reported: false, } @@ -196,7 +198,7 @@ impl Events { fence(Ordering::Acquire); let offset = status::EVENT_RING as usize + consumer as usize * EVENT_RECORD_SIZE; let record = EventRecord::from_words(init.status_read_words(role, offset)?); - let event = record.decode(); + let event = record.decode_for(self.graph); fence(Ordering::SeqCst); consumer_word.store((consumer + 1) & (EVENT_SLOTS - 1), Ordering::Relaxed); fence(Ordering::SeqCst); diff --git a/drivers/gpu/drm/asahi/g17/event.rs b/drivers/gpu/drm/asahi/g17/event.rs index 9508a827be4b1..e726a75602b86 100644 --- a/drivers/gpu/drm/asahi/g17/event.rs +++ b/drivers/gpu/drm/asahi/g17/event.rs @@ -36,6 +36,8 @@ pub(super) trait Queues { fn retain_render_completions(&mut self, masks: [u64; 2]); fn complete_compute(&mut self, masks: [u64; 2]) -> Result; fn context_killed(&mut self, cookie: u64); + /// The firmware failed a compute command (see [`Event::ComputeFault`]). + fn compute_fault(&mut self, qid: u8, stamp: u32); } /// Work requested after releasing the device mutex. @@ -72,6 +74,11 @@ fn batch(host: &mut H, role: Role, masks: &mut [u64; 2]) -> Result { } Event::FreeListGrow(completion) => host.complete_freelist_grow(completion)?, Event::ContextKilled(cookie) => host.context_killed(cookie), + Event::ComputeFault { qid, stamp } => { + host.compute_fault(qid, stamp); + // Its queue retires or settles its quarantine on this pass. + masks[usize::from(qid) / 64] |= 1 << (qid % 64); + } Event::Unknown(_) => {} } Ok(true) diff --git a/drivers/gpu/drm/asahi/g17/fw/channels.rs b/drivers/gpu/drm/asahi/g17/fw/channels.rs index 524132c4ae7be..26c6adcf34c83 100644 --- a/drivers/gpu/drm/asahi/g17/fw/channels.rs +++ b/drivers/gpu/drm/asahi/g17/fw/channels.rs @@ -136,6 +136,9 @@ pub(crate) enum Event { TvbGrow(TvbGrowRequest), FreeListGrow(FreeListCompletion), ContextKilled(u64), + /// The firmware failed a compute command of queue `qid` and stores no completion stamp for + /// it: `stamp` is the value that command would have stored. + ComputeFault { qid: u8, stamp: u32 }, Unknown(u32), } @@ -145,6 +148,8 @@ impl EventRecord { const TVB_GROW: u32 = 6; const FREE_LIST_GROW: u32 = 13; const CONTEXT_KILLED: u32 = 12; + const COMPUTE_FAULT: u32 = 7; + const COMPUTE_QIDS: u32 = 128; const DIAGNOSTIC_PAIRS_MAX: u16 = 5; const TVB_SLOTS: u32 = 127; const SUBPIPES: u32 = 64; @@ -160,6 +165,16 @@ impl EventRecord { unsafe { self.words[0] } } + /// Type 7 is a retirement witness only on the firmware whose layout and + /// visibility semantics are qualified. 25G83 must keep consuming it as an + /// unknown event until its own fault contract is established. + pub(crate) fn decode_for(&self, graph: super::variant::InitGraph) -> Result { + if graph == super::variant::InitGraph::V25G83 && self.tag() == Self::COMPUTE_FAULT { + return Ok(Event::Unknown(Self::COMPUTE_FAULT)); + } + self.decode() + } + /// Validates the record's fields without interpreting resource ownership. /// A malformed known event is an error, unlike an unknown tag. pub(crate) fn decode(&self) -> Result { @@ -198,6 +213,19 @@ impl EventRecord { halt_count: record.halt_count, })) } + Self::COMPUTE_FAULT => { + // SAFETY: Every constructor initializes all integer words. + let words = unsafe { self.words }; + // Only compute faults are known to use this type: any other layout stays an + // unknown event, consumed and ignored as before. + if words[3] >= Self::COMPUTE_QIDS || words[4] & 0xff != 0 { + return Ok(Event::Unknown(Self::COMPUTE_FAULT)); + } + Ok(Event::ComputeFault { + qid: words[3] as u8, + stamp: words[4], + }) + } Self::CONTEXT_KILLED => { // SAFETY: Every constructor initializes all integer words. let words = unsafe { self.words }; @@ -525,3 +553,37 @@ impl ContextKill { } } } + +#[cfg_attr(not(test), kernel::macros::kunit_tests(asahi_g17_event_records))] +mod tests { + use super::*; + + fn record(words: &[u32]) -> EventRecord { + let mut all = [0u32; EVENT_WORDS]; + all[..words.len()].copy_from_slice(words); + EventRecord::from_words(all) + } + + #[test] + fn a_compute_fault_names_its_queue_and_stamp() { + let event = record(&[7, 1, 2, 0x39, 0x300, 0xffff_fc00, 1]).decode(); + assert_eq!(event, Ok(Event::ComputeFault { qid: 0x39, stamp: 0x300 })); + } + + #[test] + fn v25g83_does_not_accept_an_unqualified_fault_witness() { + use super::super::variant::InitGraph; + let fault = record(&[7, 1, 2, 0x39, 0x300]); + assert_eq!(fault.decode_for(InitGraph::G17P), + Ok(Event::ComputeFault { qid: 0x39, stamp: 0x300 })); + assert_eq!(fault.decode_for(InitGraph::V25G83), Ok(Event::Unknown(7))); + assert_eq!(record(&[7, 1, 2, 128, 0x301]).decode_for(InitGraph::V25G83), + Ok(Event::Unknown(7))); + } + + #[test] + fn an_unrecognized_type_7_layout_stays_unknown() { + assert_eq!(record(&[7, 1, 2, 128, 0x300]).decode(), Ok(Event::Unknown(7))); + assert_eq!(record(&[7, 1, 2, 9, 0x301]).decode(), Ok(Event::Unknown(7))); + } +} diff --git a/drivers/gpu/drm/asahi/g17/queue/compute.rs b/drivers/gpu/drm/asahi/g17/queue/compute.rs index 9c4c5c92d049d..3d5e12be64842 100644 --- a/drivers/gpu/drm/asahi/g17/queue/compute.rs +++ b/drivers/gpu/drm/asahi/g17/queue/compute.rs @@ -575,6 +575,9 @@ pub(crate) struct Queue { config_cache: Option<(QueueConfig, u64)>, ordinal: u32, submitted: u32, + /// The stamp of the newest command the firmware reported failed ([`Self::note_fault`]), + /// while it is newer than the stored stamp. + fault_stamp: Option, /// Prepared packets of the bound owner about to publish (see [`Self::claim`]). claims: u32, last_published: u64, @@ -694,6 +697,7 @@ impl Queue { config_cache: None, ordinal: 0, submitted: 0, + fault_stamp: None, claims: 0, last_published: 0, last_end: 0, @@ -714,6 +718,29 @@ impl Queue { withheld_doorbell: None, }) } + /// The firmware failed the command whose completion stamp is `stamp`. It writes a + /// zero-duration completion record for it, which quarantines the queue, but stores no + /// stamp: without this the quarantine would wait for its retirement witness forever and + /// keep the queue's backing. Commands published after the failed one still need their own + /// stamp or record. + pub(crate) fn note_fault(&mut self, stamp: u32) { + if self + .fault_stamp + .is_none_or(|old| retirement::stamp_newer(stamp, old)) + { + self.fault_stamp = Some(stamp); + } + } + + /// The stamp that proves retirement: the stored one, or a newer reported fault's. + fn witness_stamp(&self) -> Result { + let stored = self.graph.stamp()?; + Ok(match self.fault_stamp { + Some(fault) if retirement::stamp_newer(fault, stored) => fault, + _ => stored, + }) + } + pub(crate) fn qid(&self) -> u8 { self.kick.id().qid() } @@ -1096,7 +1123,7 @@ impl Queue { if !retirement::quarantined_retired( &newest, self.graph.cursors()?, - self.graph.stamp()?, + self.witness_stamp()?, self.active.len as u32, self.submitted, )? { @@ -1140,6 +1167,7 @@ impl Queue { drop(self.active.pop()); } self.awaiting_witness = false; + self.fault_stamp = None; self.retirement_proved = false; self.replay_retirement = false; self.spared_deferred = false; @@ -1584,6 +1612,7 @@ impl Queue { self.spared_deferred = false; self.retire_pending = false; self.awaiting_witness = false; + self.fault_stamp = None; self.retirement_proved = false; self.replay_retirement = false; self.replay_error = None; diff --git a/drivers/gpu/drm/asahi/g17/queue/compute/retirement.rs b/drivers/gpu/drm/asahi/g17/queue/compute/retirement.rs index c132b87a9860a..6b942dcd86a3e 100644 --- a/drivers/gpu/drm/asahi/g17/queue/compute/retirement.rs +++ b/drivers/gpu/drm/asahi/g17/queue/compute/retirement.rs @@ -52,6 +52,12 @@ impl Cursors { } } +/// Whether stamp `a` was stored after stamp `b` (both kick stamps, modulo wrap). +pub(crate) const fn stamp_newer(a: u32, b: u32) -> bool { + let delta = a.wrapping_sub(b); + delta != 0 && delta < 0x8000_0000 +} + /// Outstanding kicks span fewer than half the 24-bit stamp sequence. Zero is a valid wrap. pub(crate) const fn stamp_covers(word: u32, kick: u64) -> bool { let delta = word.wrapping_sub((kick as u32).wrapping_shl(8)); @@ -455,6 +461,15 @@ mod tests { assert!(stamp_covers(0, 0xff_ffff)); } + #[test] + fn a_reported_fault_stamp_is_newer_only_until_stored() { + assert!(stamp_newer(own(KICK), own(KICK - 1))); + assert!(!stamp_newer(own(KICK), own(KICK))); + assert!(!stamp_newer(own(KICK - 1), own(KICK))); + // Across the wrap of the 24-bit kick in the stamp word. + assert!(stamp_newer(own(0), own(0xff_ffff))); + } + #[test] fn a_late_stamp_is_waited_for() { // Under games on J700 the stamp lands up to about 4 ms after the record while the diff --git a/drivers/gpu/drm/asahi/g17/runtime/events.rs b/drivers/gpu/drm/asahi/g17/runtime/events.rs index 4cf2dd0fafb97..b410a90568d04 100644 --- a/drivers/gpu/drm/asahi/g17/runtime/events.rs +++ b/drivers/gpu/drm/asahi/g17/runtime/events.rs @@ -44,7 +44,7 @@ impl event::Transport for Service<'_> { role: Role, mut consume: impl FnMut(&mut Self, Event) -> Result, ) -> Result { - let mut events = channel::Events::new(role); + let mut events = channel::Events::new(role, self.firmware.primary.state.shared.variant.graph); while let Some(event) = events.next(&self.firmware.primary.state.shared.dev, &self.firmware.init)? { @@ -72,6 +72,15 @@ impl event::Queues for Service<'_> { fn context_killed(&mut self, cookie: u64) { self.firmware.queues.acknowledge_compute_exit(cookie); } + fn compute_fault(&mut self, qid: u8, stamp: u32) { + for entry in self.firmware.queues.compute.iter_mut().flatten() { + if let Some(mut queue) = entry.queue.as_deref_mut() { + if queue.qid() == qid { + queue.note_fault(stamp); + } + } + } + } fn flush_tvb_reply(&mut self) -> Result { self.firmware.flush_tvb_reply() } From bb047c758c11648a328729efb1669f2393d956bd Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Tue, 27 Oct 2026 17:16:17 +1100 Subject: [PATCH 102/184] drm/asahi: kill faulted G17 contexts before freeing their render work When a compute command fails, the firmware holds the commands its context published after it and never runs them; only a kill of the context returns them. The context's render work behind the same failure behaves differently. It runs about a second later, consumes its ring entries and returns its free-list blocks, but the firmware never stores its completion stamp and leaves the job linked in the context's work list until it processes a kill. None of that work could retire, so a closed context whose compute failed kept its pools, its render pairs, its memory and its scheduler teardown until the GPU firmware stopped. Let the kill proceed for such a context. A quarantined render pair whose commands all ran (both pass timestamps written, rings consumed, free list balanced) counts as idle for the kill and is held for it, and the kill no longer waits for render pools that are only released after it. A faulted compute queue with no commands behind the failure is a kill candidate as well. This applies only once the owner has closed; a live owner's quarantined pair stays quarantined. Free only after the firmware acknowledges the kill. The pair's unstamped work is retired once the context's scheduler page reports the kill, and its graph memory stays allocated and out of the reuse cache until then. The detach path enforces the same rule: a context whose work list still links a node and has no acknowledged kill never has its graphs freed or recycled. The firmware leaves a killed context's last job linked even after the kill, so the context's 32 KiB work object is kept out of reuse until the firmware stops; debugfs counts these as firmware_linked_objects and a warning is printed past 256. Reuse a killed context's render pairs last. The kill hold on them ends when the context's scheduler release is consumed. A new owner then takes a clean released pair first, then an unowned slot while the physical queue table still has room for both of its identities, and only then the killed pair released longest ago. Siblings and spares never take killed pairs, and a sibling no longer claims an unowned slot once the table is full. Admission is told whether the table has room whenever the runtime changes it. Publish a failed context's kill only while no other context has a compute command in flight, or once the GPU has retired nothing for the liveness interval. When GPU memory comes within an eighth of the device limit, at most one waiting kill per 250 ms skips that wait. debugfs reports the waits and the pressure kills, and a wait over 5 s is warned about once per kill. The order matters. Earlier versions retired and freed the pair first and killed afterwards, and the firmware then used memory that had been handed out again: GPU recoveries 4-7 ms after the kill and occasional firmware crashes when the kill followed within milliseconds, and a storm of compute watchdog timeouts when it followed minutes later. A run that handed killed pairs to new owners first stranded the teardowns of 24 contexts, and a render stall behind a failed context took the desktop down. Reusing them last without regard to the queue table ran out of physical queues after about 50 kills. A kill published beside other clients' running compute made the firmware recover the GPU twice in ten minutes of a five-lane stress load, which the wait avoids. Waiting without a bound let the dead clients' memory reach the device limit under sustained compute, and gnome-shell then crashed on ENOMEM, which the memory escape avoids. On Neo, ten minutes of a five-lane stress load (render, render2, long ALU, churn and global-priority CTS beside two ray query geometry-shader stress loops) had 52 faults and 15 kills (2 under memory pressure), all acknowledged and their pairs retired. There were 0 GPU recoveries and nothing was left pending afterwards, against 12 teardowns left pending without this change. Ten minutes of two ray query stress loops beside heavy geometry had 51 faults and 51 kills (3 under memory pressure), with 0 recoveries, the physical queue table full at 127 of 128 with no refusals, and 4 render-only teardowns left pending. In both, GPU memory peaked at 4.3 GiB of the 5.0 GiB device limit, no allocation was refused and gnome-shell stayed up. Known limits: - A killed context's 32 KiB work object stays allocated until the GPU firmware restarts. - While other clients keep compute running, a faulted client's memory stays held until its kill publishes: at the next moment without foreign compute, or through the memory escape near the device limit. - A closed context whose compute side is clean but whose render pair holds executed, unstamped work is not killed, since no compute queue is left to carry the kill. A kill that doesn't need a compute exit candidate would close this. - A kill that is safe beside running compute would remove the wait. It is not understood yet. An asahi_g17_teardown tracepoint records fault, unstamped retire, kill, acknowledgement, detach and slot-owner events. Link: https://github.com/aurora-silicon/linux/commit/078d744a260cb923d0ddfcc2b58ce8903be5a96d Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/gpu/drm/asahi/g17/context.rs | 38 ++++ drivers/gpu/drm/asahi/g17/event.rs | 1 + drivers/gpu/drm/asahi/g17/kick.rs | 5 + drivers/gpu/drm/asahi/g17/object.rs | 87 ++++++++- drivers/gpu/drm/asahi/g17/queue/admission.rs | 173 ++++++++++++++++-- drivers/gpu/drm/asahi/g17/queue/compute.rs | 12 ++ .../gpu/drm/asahi/g17/queue/compute/exit.rs | 25 ++- drivers/gpu/drm/asahi/g17/queue/render.rs | 48 ++++- .../drm/asahi/g17/queue/render/retirement.rs | 28 +++ drivers/gpu/drm/asahi/g17/runtime.rs | 7 + .../gpu/drm/asahi/g17/runtime/compute_exit.rs | 138 +++++++++++++- drivers/gpu/drm/asahi/g17/runtime/render.rs | 28 ++- .../gpu/drm/asahi/g17/runtime/render/exit.rs | 13 ++ .../drm/asahi/g17/runtime/render/submit.rs | 3 +- .../drm/asahi/g17/runtime/render/teardown.rs | 35 +++- drivers/gpu/drm/asahi/g17/runtime/teardown.rs | 12 ++ drivers/gpu/drm/asahi/g17/trace.c | 6 + drivers/gpu/drm/asahi/g17/trace.h | 19 ++ drivers/gpu/drm/asahi/g17/trace.rs | 10 + drivers/gpu/drm/asahi/g17/trace_api.h | 1 + 20 files changed, 654 insertions(+), 35 deletions(-) diff --git a/drivers/gpu/drm/asahi/g17/context.rs b/drivers/gpu/drm/asahi/g17/context.rs index 0d3948a2b69ff..ba74140d4f243 100644 --- a/drivers/gpu/drm/asahi/g17/context.rs +++ b/drivers/gpu/drm/asahi/g17/context.rs @@ -280,6 +280,28 @@ impl Context { _work: self.work.clone(), } } + /// Called once the firmware consumed this context's scheduler release. If its work list + /// still links a node, the firmware keeps referencing that node: on Neo a render command + /// that ran behind a failed compute command is never unlinked, and a later context given + /// the same work storage (the object cache hands it out again) made the firmware recover + /// the GPU. Keep the storage mapped and out of reuse until firmware stop; returns whether + /// it was linked. + pub(crate) fn retain_linked_work(&self, pool: &Arc) -> bool { + // An unreadable head counts as linked: retaining is the safe side. + let linked = self.work_head().map_or(true, |head| head != [0, self.work_head_va()]); + if linked { + self.work.lock().object.retain_until_stop(pool); + } + linked + } + + /// The firmware may still walk this context's work list into memory it names: a node is + /// linked and the scheduler page shows no kill. Unreadable state counts as linked. + pub(crate) fn firmware_may_link(&self) -> bool { + let killed = super::queue::compute::exit::State::read(self).map_or(0, |state| state.killed); + must_retain(self.work_head().ok(), self.work_head_va(), killed) + } + pub(crate) fn work_head_va(&self) -> u64 { self.work_base + (UAT_PGSZ + 0x400) as u64 } @@ -463,10 +485,26 @@ impl QosIds { } } +/// Memory a context's work list may lead the firmware to stays allocated: a linked node (or +/// an unreadable head) and no acknowledged kill. +fn must_retain(head: Option<[u64; 2]>, head_va: u64, killed: u32) -> bool { + killed == 0 && head.is_none_or(|head| head != [0, head_va]) +} + #[kunit_tests(asahi_g17_qos_capacity)] mod capacity_tests { use super::*; + #[test] + fn linked_unkilled_work_is_retained() { + let va = 0xfffffc20_0000_4400; + assert!(!must_retain(Some([0, va]), va, 0)); + assert!(must_retain(Some([0xfffffc20_0000_0000, 0xfffffc20_0000_0098]), va, 0)); + assert!(must_retain(None, va, 0)); + assert!(!must_retain(Some([0xfffffc20_0000_0000, 0xfffffc20_0000_0098]), va, 1)); + assert!(!must_retain(None, va, 1)); + } + #[test] fn failed_client_construction_returns_every_qos_id() { let ids = QosIds::new().unwrap(); diff --git a/drivers/gpu/drm/asahi/g17/event.rs b/drivers/gpu/drm/asahi/g17/event.rs index e726a75602b86..a175e58392379 100644 --- a/drivers/gpu/drm/asahi/g17/event.rs +++ b/drivers/gpu/drm/asahi/g17/event.rs @@ -75,6 +75,7 @@ fn batch(host: &mut H, role: Role, masks: &mut [u64; 2]) -> Result { Event::FreeListGrow(completion) => host.complete_freelist_grow(completion)?, Event::ContextKilled(cookie) => host.context_killed(cookie), Event::ComputeFault { qid, stamp } => { + crate::g17::trace::teardown(1, u64::from(qid), u64::from(stamp), 0); host.compute_fault(qid, stamp); // Its queue retires or settles its quarantine on this pass. masks[usize::from(qid) / 64] |= 1 << (qid % 64); diff --git a/drivers/gpu/drm/asahi/g17/kick.rs b/drivers/gpu/drm/asahi/g17/kick.rs index f694dad86bbf0..6499cae106309 100644 --- a/drivers/gpu/drm/asahi/g17/kick.rs +++ b/drivers/gpu/drm/asahi/g17/kick.rs @@ -145,6 +145,11 @@ impl QueueIds { self.render_ids() + (QID_COUNT - self.floor - self.leased) >= 2 * queues + 2 } + /// Whether a render pair not yet built can still reserve both of its identities. + pub(crate) fn render_pair_fits(&self) -> bool { + QID_COUNT - self.floor - self.leased >= 2 + } + /// Whether a fresh compute identity fits beside the render reserve of /// `queues` open logical queues, without reserving it. pub(crate) fn compute_fits(&self, queues: usize) -> bool { diff --git a/drivers/gpu/drm/asahi/g17/object.rs b/drivers/gpu/drm/asahi/g17/object.rs index f1b22ad5d7331..7cc13dbba705e 100644 --- a/drivers/gpu/drm/asahi/g17/object.rs +++ b/drivers/gpu/drm/asahi/g17/object.rs @@ -466,8 +466,17 @@ struct PoolInner { reserved: [usize; POOL_FAMILIES], bytes: usize, stopped: bool, + /// Objects the firmware may still reference after their owner dropped them, kept mapped + /// and out of reuse until firmware stop (see [`PooledObject::retain_until_stop`]). + kept: KVec, + kept_bytes: usize, + /// Kept objects still owned by their users, whose list entries are reserved. + kept_reserved: usize, } +/// Retained objects past this many are still retained, with one warning. +const KEPT_WARN: usize = 256; + /// Per-device cache-safe object recycling. Every converted mapping reserves /// one return slot before its page-table attributes change. Active leases and /// poisoned mappings count against the same capacity as reusable objects. @@ -490,6 +499,7 @@ struct CacheLease { pub(crate) struct PooledObject { object: core::mem::ManuallyDrop, lease: Option, + keep: Option>, } impl core::ops::Deref for PooledObject { @@ -503,11 +513,28 @@ impl core::ops::DerefMut for PooledObject { &mut self.object } } +impl PooledObject { + /// The firmware may still reference this object after its owner drops it: keep it mapped + /// and never hand it out again before firmware stop. It leaves the reuse cache, so it + /// does not take cache capacity. Call from sleepable context: this reserves the list + /// entry, so the drop, wherever it happens, does not allocate. + pub(crate) fn retain_until_stop(&mut self, pool: &kernel::sync::Arc) { + if self.keep.is_none() { + pool.reserve_kept(); + self.keep = Some(pool.clone()); + } + } +} + impl Drop for PooledObject { fn drop(&mut self) { // SAFETY: This destructor is the only operation that moves `object` // out; ManuallyDrop prevents a second implicit drop of the field. let object = unsafe { core::mem::ManuallyDrop::take(&mut self.object) }; + if let Some(pool) = self.keep.take() { + pool.keep_object(object, self.lease.take()); + return; + } match self.lease.take() { Some(lease) => lease.pool.return_object(object, &lease), None => drop(object), @@ -523,7 +550,10 @@ impl Pool { } kernel::sync::Arc::pin_init( pin_init!(Self { - inner <- kernel::new_mutex!(PoolInner { returned, reserved: [0; POOL_FAMILIES], bytes: 0, stopped: false }, "g17::object::Pool"), + inner <- kernel::new_mutex!(PoolInner { + returned, reserved: [0; POOL_FAMILIES], bytes: 0, stopped: false, kept: KVec::new(), + kept_bytes: 0, kept_reserved: 0, + }, "g17::object::Pool"), }), GFP_KERNEL, ) @@ -572,6 +602,7 @@ impl Pool { return Ok(PooledObject { object: core::mem::ManuallyDrop::new(entry.object), lease: Some(lease()), + keep: None, }); } let object = alloc.kernel(size, mmu::UAT_PGSZ as u64, prot, cpu)?; @@ -596,6 +627,7 @@ impl Pool { let mut result = PooledObject { object: core::mem::ManuallyDrop::new(object), lease: reserved.then(lease), + keep: None, }; if !reserved { return Ok(result); @@ -655,10 +687,58 @@ impl Pool { core::mem::forget(object); } + /// Room for one more kept object, taken by the matching `keep_object`. A failed + /// allocation is reported by that drop, which then leaks the object instead. + fn reserve_kept(&self) { + let mut inner = self.inner.lock(); + let pending = inner.kept_reserved + 1; + if inner.kept.capacity() - inner.kept.len() < pending + && inner.kept.reserve(pending, GFP_KERNEL).is_err() + { + return; + } + inner.kept_reserved = pending; + } + + fn keep_object(&self, object: KernelObject, lease: Option) { + let mut inner = self.inner.lock(); + inner.kept_reserved = inner.kept_reserved.saturating_sub(1); + if let Some(lease) = &lease { + // The object leaves the reuse cache and its capacity. + inner.reserved[lease.family as usize] -= 1; + inner.bytes -= object.size(); + } + if inner.stopped { + drop(inner); + drop(object); + return; + } + let size = object.size(); + if inner.kept.len() == KEPT_WARN { + pr_warn!("G17 keeps {} firmware-linked objects until the GPU firmware restarts\n", KEPT_WARN); + } + // retain_until_stop reserved the entry; this never allocates. + match inner.kept.push_within_capacity(object) { + Ok(()) => inner.kept_bytes += size, + Err(error) => { + // Never unmap an object the firmware may still reference. + pr_err!("G17 could not record a firmware-linked object; leaking it\n"); + core::mem::forget(error.0); + } + } + } + /// Releases retained mappings after both firmware processors have stopped. /// Active objects returned later observe the same stop witness. pub(crate) fn stop_after_firmware(&self) { - self.inner.lock().stopped = true; + let kept = { + let mut inner = self.inner.lock(); + inner.stopped = true; + inner.kept_bytes = 0; + core::mem::take(&mut inner.kept) + }; + // Mapping teardown must not run while the pool mutex is held. + drop(kept); for index in 0..POOL_FAMILIES * POOL_SLOTS { let entry = { let mut inner = self.inner.lock(); @@ -684,6 +764,7 @@ impl Pool { inner.reserved[family], POOL_SLOTS, entries.iter().flatten().count(), entries.iter().flatten().filter(|entry| entry.poisoned).count())?; } - writeln!(f, "cached_object_bytes used={} total={}", inner.bytes, POOL_BYTES) + writeln!(f, "cached_object_bytes used={} total={}", inner.bytes, POOL_BYTES)?; + writeln!(f, "firmware_linked_objects kept={} bytes={}", inner.kept.len(), inner.kept_bytes) } } diff --git a/drivers/gpu/drm/asahi/g17/queue/admission.rs b/drivers/gpu/drm/asahi/g17/queue/admission.rs index 5d770ca1f64b0..5c657444be01f 100644 --- a/drivers/gpu/drm/asahi/g17/queue/admission.rs +++ b/drivers/gpu/drm/asahi/g17/queue/admission.rs @@ -71,11 +71,19 @@ struct State { /// their own fence with EBUSY instead of a device-loss error, so the VM stays usable. spared: u64, kill_hold: u64, + /// Slots whose kill hold ended at their killed context's consumed scheduler release, + /// and when. A new owner takes such a pair last, the one released longest ago first. + killed: u64, + killed_at: [u64; cfg::RENDER_SLOTS], + /// The physical queue table still has room for a pair built in an unowned slot. The + /// runtime updates this whenever the table changes. + build_room: bool, released: u64, epoch: u64, footprints: KVec, next_ticket: u64, } + impl State { const MASK: u64 = (u64::MAX >> (64 - cfg::RENDER_SLOTS)) & !1; const fn new() -> Self { @@ -87,6 +95,9 @@ impl State { quarantined: 0, spared: 0, kill_hold: 0, + killed: 0, + killed_at: [0; cfg::RENDER_SLOTS], + build_room: true, released: 0, epoch: 0, footprints: KVec::new(), @@ -120,6 +131,15 @@ impl State { self.epochs[slot] = self.epoch; } fn take_idle(&mut self, slot: usize, owner: u64) -> u8 { + if self.owners[slot] != owner { + crate::g17::trace::teardown( + 6, + slot as u64, + self.owners[slot], + owner | u64::from(self.killed & (1u64 << slot) != 0) << 63, + ); + self.killed &= !(1u64 << slot); + } self.refs[slot] += 1; self.active |= 1u64 << slot; self.released &= !(1u64 << slot); @@ -165,13 +185,23 @@ impl State { } let free = !self.active & Self::MASK & !self.quarantined & !self.kill_hold; let eligible = |slot: usize| free & (1u64 << slot) != 0; - // Reuse a closed, exactly retired owner before consuming a new graph. + let released = |slot: usize| self.released & (1u64 << slot) != 0; + let unowned = || (0..cfg::RENDER_SLOTS).find(|slot| eligible(*slot) && self.owners[*slot] == 0); + // Reuse a closed, exactly retired owner before consuming a new graph. A killed + // context's pair comes after a new graph while one can still be built: on Neo, new + // owners of such pairs had renders that executed but never stored their stamps. + // Once the queue table is full it is reused, the one released longest ago first, + // so killed pairs never strand physical queues. let slot = (0..cfg::RENDER_SLOTS) - .filter(|slot| eligible(*slot) && self.released & (1u64 << slot) != 0) + .filter(|slot| eligible(*slot) && released(*slot) && self.killed & (1u64 << slot) == 0) .min_by_key(|slot| self.epochs[*slot]) + .or_else(|| if self.build_room { unowned() } else { None }) .or_else(|| { - (0..cfg::RENDER_SLOTS).find(|slot| eligible(*slot) && self.owners[*slot] == 0) - })?; + (0..cfg::RENDER_SLOTS) + .filter(|slot| eligible(*slot) && released(*slot)) + .min_by_key(|slot| self.killed_at[*slot]) + }) + .or_else(unowned)?; let first = self.take_idle(slot, owner); self.claim_sibling(owner); Some(first) @@ -180,7 +210,8 @@ impl State { if self.owners.iter().filter(|id| **id == owner).count() >= cfg::RENDER_PAIRS_PER_OWNER { return; } - let free = !self.active & Self::MASK & !self.quarantined & !self.kill_hold; + // A speculative sibling never takes a killed context's pair. + let free = !self.active & Self::MASK & !self.quarantined & !self.kill_hold & !self.killed; let eligible = |slot: usize| free & (1u64 << slot) != 0; let sibling = (0..cfg::RENDER_SLOTS) .filter(|slot| { @@ -190,6 +221,10 @@ impl State { }) .min_by_key(|slot| self.epochs[*slot]) .or_else(|| { + // Nor an unowned slot once no pair can be built there. + if !self.build_room { + return None; + } (0..cfg::RENDER_SLOTS).find(|slot| eligible(*slot) && self.owners[*slot] == 0) }); if let Some(slot) = sibling { @@ -223,6 +258,7 @@ impl State { let bit = 1u64 << slot; let old = self.owners[slot]; if old == 0 + || self.killed & bit != 0 || owner == Some(old) || expected.is_some_and(|expected| expected != old) || self.refs[slot] != 0 @@ -648,9 +684,27 @@ impl Pool { state.quarantined &= !released; state.spared &= !released; state.kill_hold = 0; + state.killed = 0; drop(state); self.returned(); } + + /// Whether the physical queue table can hold another pair (see `State::build_room`). + pub(crate) fn note_build_room(&self, room: bool) { + let mut state = self.state.lock(); + if state.build_room != room { + state.build_room = room; + drop(state); + self.returned(); + } + } + + /// A killed context's pair is free for a new owner. + pub(crate) fn killed_pair_free(&self) -> bool { + let state = self.state.lock(); + state.killed & state.released & !state.active & !state.quarantined & !state.kill_hold & State::MASK != 0 + } + } #[kunit_tests(asahi_g17_admission_accounting)] @@ -863,7 +917,7 @@ impl Drop for RenderLease { } impl State { - fn hold_for_exit_kill(&mut self, owner: u64, slots: u64) -> bool { + fn hold_for_exit_kill(&mut self, owner: u64, slots: u64, executed: u64) -> bool { if owner == 0 || slots & !Self::MASK != 0 { return false; } @@ -872,10 +926,10 @@ impl State { if slots & bit == 0 { continue; } + // A pair whose commands all ran is held with them (see `Pair::executed_for_kill`). if self.owners[slot] != owner - || self.refs[slot] != 0 - || self.active & bit != 0 - || self.released & bit == 0 + || (executed & bit == 0 + && (self.refs[slot] != 0 || self.active & bit != 0 || self.released & bit == 0)) { return false; } @@ -887,11 +941,11 @@ impl State { impl Pool { /// Hold the departed owner's idle pairs before publishing its exit record. /// Successful publication retains these bits until both processors stop. - pub(crate) fn hold_render_slots_for_exit_kill(&self, owner: u64, slots: u64) -> bool { - self.state.lock().hold_for_exit_kill(owner, slots) + pub(crate) fn hold_render_slots_for_exit_kill(&self, owner: u64, slots: u64, executed: u64) -> bool { + self.state.lock().hold_for_exit_kill(owner, slots, executed) } - /// Undo only a hold whose control record was not published. + /// Undo a hold whose control record was not published. pub(crate) fn release_exit_kill_hold(&self, slots: u64) { if slots == 0 { return; @@ -899,6 +953,26 @@ impl Pool { self.state.lock().kill_hold &= !slots; self.returned(); } + + /// End the kill hold of `slots` once their killed context's scheduler release was + /// consumed (the firmware no longer names these pairs). A new owner takes them last. + pub(crate) fn release_killed_slots(&self, slots: u64) { + let mut state = self.state.lock(); + let held = state.kill_hold & slots & State::MASK; + if held == 0 { + return; + } + let now = crate::g17::now_ns(); + state.kill_hold &= !held; + state.killed |= held; + for slot in 0..cfg::RENDER_SLOTS { + if held & (1u64 << slot) != 0 { + state.killed_at[slot] = now; + } + } + drop(state); + self.returned(); + } } impl Pool { @@ -972,13 +1046,86 @@ mod fault_capacity_tests { let bit = 1u64 << lease.slot(); pool.release_owner(1); drop(lease); - assert!(pool.hold_render_slots_for_exit_kill(1, bit)); + assert!(pool.hold_render_slots_for_exit_kill(1, bit, 0)); assert!(!pool.reassign_unused_spare(bit.trailing_zeros() as u8, Some(1), Some(2))); // A failed (unpublished) control may roll back its hold. Published kills // keep the existing processor-stop rule; this test must not weaken it. pool.release_retained_owners_after_processor_stop(); assert_eq!(pool.state.lock().kill_hold, 0); } + + #[test] + fn consumed_scheduler_release_ends_the_kill_hold() { + let pool = Pool::new(MAX_EXECUTION_CONTEXTS).unwrap(); + let lease = pool.acquire_render(1, None, u64::MAX, || Ok(())).unwrap(); + let slot = lease.slot(); + let bit = 1u64 << slot; + pool.release_owner(1); + drop(lease); + assert!(pool.hold_render_slots_for_exit_kill(1, bit, 0)); + assert!(!pool.reassign_unused_spare(slot, Some(1), Some(2))); + pool.release_killed_slots(bit); + assert_eq!(pool.state.lock().kill_hold, 0); + assert_eq!(pool.state.lock().killed, bit); + assert!(pool.killed_pair_free()); + // The killed context's pair is never reassigned as a spare, and a new owner takes + // a new graph before it. + assert!(!pool.reassign_unused_spare(slot, Some(1), Some(2))); + let next = pool.acquire_render(2, None, u64::MAX, || Ok(())).unwrap(); + assert_ne!(next.slot(), slot); + drop(next); + pool.release_retained_owners_after_processor_stop(); + assert_eq!(pool.state.lock().killed, 0); + } + + #[test] + fn killed_pairs_are_taken_last_oldest_first() { + let pool = Pool::new(MAX_EXECUTION_CONTEXTS).unwrap(); + { + let mut state = pool.state.lock(); + for slot in 1..cfg::RENDER_SLOTS { + state.owners[slot] = 100 + slot as u64; + state.released |= 1u64 << slot; + state.killed |= 1u64 << slot; + state.killed_at[slot] = 1000 - slot as u64; + } + // One pair released without a kill. + state.killed &= !(1u64 << 7); + } + let first = pool.acquire_render(1, None, u64::MAX, || Ok(())).unwrap(); + assert_eq!(first.slot(), 7); + let second = pool.acquire_render(2, None, u64::MAX, || Ok(())).unwrap(); + assert_eq!(usize::from(second.slot()), cfg::RENDER_SLOTS - 1); + assert_eq!(pool.state.lock().killed & (1u64 << second.slot()), 0); + } + + #[test] + fn a_full_queue_table_reuses_killed_pairs_before_unowned_slots() { + let pool = Pool::new(MAX_EXECUTION_CONTEXTS).unwrap(); + { + let mut state = pool.state.lock(); + for slot in 1..8 { + state.owners[slot] = 100 + slot as u64; + state.released |= 1u64 << slot; + state.killed |= 1u64 << slot; + state.killed_at[slot] = 1000 - slot as u64; + } + } + // With room in the queue table a new owner builds a pair in an unowned slot. + let built = pool.acquire_render(1, None, u64::MAX, || Ok(())).unwrap(); + assert!(usize::from(built.slot()) >= 8); + // Without room it takes the killed pair released longest ago. + pool.note_build_room(false); + let reused = pool.acquire_render(2, None, u64::MAX, || Ok(())).unwrap(); + assert_eq!(reused.slot(), 7); + assert_eq!(pool.state.lock().killed & (1u64 << 7), 0); + // It claims no sibling: neither another killed pair nor an unowned slot. + let state = pool.state.lock(); + for slot in 1..7 { + assert_eq!(state.owners[slot], 100 + slot as u64); + } + assert_eq!(state.owners.iter().filter(|owner| **owner == 2).count(), 1); + } } impl Pool { diff --git a/drivers/gpu/drm/asahi/g17/queue/compute.rs b/drivers/gpu/drm/asahi/g17/queue/compute.rs index 3d5e12be64842..68fd03c42b42b 100644 --- a/drivers/gpu/drm/asahi/g17/queue/compute.rs +++ b/drivers/gpu/drm/asahi/g17/queue/compute.rs @@ -566,6 +566,9 @@ pub(crate) struct Queue { replay_error: Option, replay_prepared: bool, exit: Option, + /// Since when the kill of this failed queue's context waits for other contexts' compute, + /// and whether that wait was reported. + kill_wait: Option<(u64, bool)>, installed: bool, /// Bit `p` set once a work slot of priority class `p` named this queue. outer_started: u8, @@ -691,6 +694,7 @@ impl Queue { replay_error: None, replay_prepared: false, exit: None, + kill_wait: None, installed: false, outer_started: 0, published_config: None, @@ -732,6 +736,14 @@ impl Queue { } } + /// The firmware failed a command of this quarantined queue, and the queue still waits for + /// its retirement witness: commands published after the failed one, which the firmware + /// never runs, or the failed command itself, whose ring entry it does not consume (a ray + /// traversal's, on Neo). Only a kill of the queue's context gives them back. + pub(crate) fn fault_pending(&self) -> bool { + self.fault_stamp.is_some() && self.quarantined && self.awaiting_witness && self.exit.is_none() + } + /// The stamp that proves retirement: the stored one, or a newer reported fault's. fn witness_stamp(&self) -> Result { let stored = self.graph.stamp()?; diff --git a/drivers/gpu/drm/asahi/g17/queue/compute/exit.rs b/drivers/gpu/drm/asahi/g17/queue/compute/exit.rs index d7386e335e167..ae812dd41fc12 100644 --- a/drivers/gpu/drm/asahi/g17/queue/compute/exit.rs +++ b/drivers/gpu/drm/asahi/g17/queue/compute/exit.rs @@ -83,11 +83,12 @@ impl Transaction { } impl Queue { - /// A command with ray traversal scratch is never killed. On Neo, killing a + /// A running command with ray traversal scratch is never killed. On Neo, killing a /// traversal that stopped making progress either crashed the GPU firmware /// when the queue's pool was then released, or stalled every later render /// when it was retained; left alone, the rest of the GPU kept working. Such - /// a queue stays quarantined and retained until its commands retire. + /// a queue stays quarantined and retained until its commands retire. A + /// traversal the firmware reported failed no longer runs (see `fault_pending`). fn active_traversal(&self) -> bool { self.active.iter().any(|active| { matches!( @@ -101,7 +102,7 @@ impl Queue { && self.released && self.active.len != 0 && self.exit.is_none() - && !self.active_traversal() + && (!self.active_traversal() || self.fault_pending()) && (self.quarantined || self.retired_by_teardown) && age >= if self.quarantined { @@ -124,6 +125,23 @@ impl Queue { .and_then(|active| active.ticket.map(|ticket| ticket.owner)) }) } + /// The kill of this queue's failed context waits; returns how long it has waited, and + /// whether this is the first time it crossed `slow`. + pub(crate) fn note_kill_wait(&mut self, now: u64, slow: u64) -> (u64, bool) { + let (since, warned) = self.kill_wait.get_or_insert((now, false)); + let waited = now.saturating_sub(*since); + let crossed = waited >= slow && !*warned; + *warned |= crossed; + (waited, crossed) + } + /// How long the kill of this queue's failed context has been waiting, if it is. + pub(crate) fn kill_waiting(&self, now: u64) -> Option { + self.kill_wait.map(|(since, _)| now.saturating_sub(since)) + } + /// The kill was published: how long it waited, if it did. + pub(crate) fn take_kill_wait(&mut self, now: u64) -> u64 { + self.kill_wait.take().map_or(0, |(since, _)| now.saturating_sub(since)) + } pub(crate) fn record_exit(&mut self, cookie: u64, now: u64) { self.exit = Some(Transaction { cookie, @@ -162,6 +180,7 @@ impl Queue { return false; } exit.phase = Phase::Acknowledged; + crate::g17::trace::teardown(4, cookie, u64::from(self.qid()), 0); true } pub(crate) fn settle_exit( diff --git a/drivers/gpu/drm/asahi/g17/queue/render.rs b/drivers/gpu/drm/asahi/g17/queue/render.rs index 696a1486eb0ec..bcf10f97fa2d7 100644 --- a/drivers/gpu/drm/asahi/g17/queue/render.rs +++ b/drivers/gpu/drm/asahi/g17/queue/render.rs @@ -1614,7 +1614,43 @@ impl Pair { let counters = self.free_list.counters()?; let stamps = host.stamps(self.qids())?; let (_, state) = host.epoch()?; - Ok(retirement::drained(self.ordinal, stamps, counters, state)) + Ok(retirement::drained(self.ordinal, stamps, counters, state) + || (state == 0 && self.executed_unstamped() && self.context_killed())) + } + + /// The firmware acknowledged a kill of this pair's context (scheduler page): it no longer + /// walks that context's work list, so the commands it never unlinked can be released. + fn context_killed(&self) -> bool { + crate::g17::queue::compute::exit::State::read(&self.memory.context).is_ok_and(|state| state.killed != 0) + } + + /// The pair's context may be killed although its commands were not retired: they all ran + /// (`executed_unstamped`), so the kill leaves nothing queued for the firmware to run. + /// Its memory stays until the kill is acknowledged. + pub(crate) fn executed_for_kill(&self, context: &Arc) -> bool { + Arc::ptr_eq(context, &self.memory.context) && self.executed_unstamped() + } + + /// Every command of this quarantined pair ran, its ring entries were consumed and its + /// free-list blocks returned, yet the firmware never stores the stamp of the last one. + /// On Neo this is the render work of a context whose compute command failed: it runs + /// about a second after the failure, but its stamp never arrives and it stays linked in + /// the firmware's job list, so neither the stamp proof nor the abandoned-group proof + /// ever holds and the pair would stay quarantined, keeping its context, until the + /// processors stop. + fn executed_unstamped(&self) -> bool { + self.quarantined + // Only for a closed owner: a live owner would go on publishing into a context + // whose work list still links the unstamped node. + && self.owner.is_none() + && self.prepared.is_none() + && self.active.len() != 0 + && self.free_list.counters().is_ok_and(|counters| counters[0] == counters[1]) + && self.active.iter().all(|active| { + active.outer_published + && observation::snapshot(&self.memory, active.ticket.ordinal) + .is_ok_and(retirement::Snapshot::executed_and_consumed) + }) } /// Account one witnessed tracker, preserving all completion ownership. @@ -1628,6 +1664,10 @@ impl Pair { let qids = self.qids(); let qos = self.qos_owners(); let buffer = self.buffer_id(); + // Commands the firmware never unlinked are released only after it acknowledged the + // kill of their context, on every path (see `firmware_drained`). + let unstamped = self.executed_unstamped(); + let killed = unstamped && self.context_killed(); let Some(active) = self.active.iter_mut().find(|a| !a.flist_completed) else { return Ok(false); }; @@ -1638,6 +1678,12 @@ impl Pair { }; let result = match retirement::poll(&mut active.ticket, &mut observer)? { retirement::Poll::Complete(ticks) => Ok(ticks), + // Its commands ran without a stamp (see `executed_unstamped`): they already failed + // with the quarantine, and the client sees them cancelled. + retirement::Poll::Pending | retirement::Poll::Retry if killed => { + crate::g17::trace::teardown(2, u64::from(self.slot), active.ticket.ordinal, 0); + Err(ECANCELED) + } retirement::Poll::Pending | retirement::Poll::Retry => { core::sync::atomic::fence(core::sync::atomic::Ordering::Acquire); let snapshot = observer.snapshot(active.ticket.ordinal)?; diff --git a/drivers/gpu/drm/asahi/g17/queue/render/retirement.rs b/drivers/gpu/drm/asahi/g17/queue/render/retirement.rs index 00e4997ef3661..7996970ee369f 100644 --- a/drivers/gpu/drm/asahi/g17/queue/render/retirement.rs +++ b/drivers/gpu/drm/asahi/g17/queue/render/retirement.rs @@ -127,6 +127,12 @@ impl Snapshot { pub(crate) fn untouched(self) -> bool { self.pass == [0, 0] } + + /// The command ran (tiler start and fragment end written) and the firmware consumed both + /// queues' ring entries. + pub(crate) fn executed_and_consumed(self) -> bool { + self.executed() && self.cursors.iter().all(|cursors| cursors.done == cursors.write) + } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -355,3 +361,25 @@ impl Tracker { .filter_map(Option::as_mut) } } + +#[kunit_tests(asahi_g17_render_retirement)] +mod executed_tests { + use super::*; + + fn snapshot(done: u32, write: u32, pass: [u64; 2]) -> Snapshot { + Snapshot { + cursors: [Cursors { done, read: done, write }; 2], + job_list_empty: false, + pass, + } + } + + #[test] + fn executed_and_consumed_needs_both_passes_and_consumed_rings() { + assert!(snapshot(3, 3, [1, 2]).executed_and_consumed()); + assert!(!snapshot(3, 3, [0, 0]).executed_and_consumed()); + assert!(!snapshot(3, 3, [1, 0]).executed_and_consumed()); + assert!(!snapshot(3, 3, [0, 2]).executed_and_consumed()); + assert!(!snapshot(2, 3, [1, 2]).executed_and_consumed()); + } +} diff --git a/drivers/gpu/drm/asahi/g17/runtime.rs b/drivers/gpu/drm/asahi/g17/runtime.rs index 9fe17db8a2809..1e721a51f726e 100644 --- a/drivers/gpu/drm/asahi/g17/runtime.rs +++ b/drivers/gpu/drm/asahi/g17/runtime.rs @@ -541,6 +541,7 @@ impl Registry { .qids .reserve_for(identity, DataMaster::Compute, Some(FIRST_COMPUTE_QID), queues) .map_err(|error| self.compute_admission_error(error))?; + self.note_build_room(); let reservation = ComputeReservation { slot, id, owner }; self.compute[slot] = Some(ComputeEntry { reservation, @@ -584,6 +585,7 @@ impl Registry { self.reserved(reservation)?; self.qids.cancel_unpublished(reservation.id)?; self.compute[reservation.slot] = None; + self.note_build_room(); Ok(()) } @@ -601,6 +603,7 @@ impl Registry { if unpublished { self.qids.cancel_unpublished(entry.reservation.id)?; self.compute[slot] = None; + self.note_build_room(); } Ok(()) } @@ -2373,6 +2376,10 @@ impl Registry { writeln!(f, "qos_ids used={} total=126", self.qos_ids.used())?; writeln!(f, "metrics_ids used={} total={}", self.metrics.used(), super::fw::buffer::METRICS_SLOTS)?; self.render.write_capacity(f)?; + let now = now_ns(); + let waits = || self.compute.iter().flatten().filter_map(|entry| entry.queue.as_deref()) + .filter_map(|queue| queue.kill_waiting(now)); + compute_exit::write_kill_waits(f, waits().count(), waits().max().unwrap_or(0))?; writeln!(f, "compute_slots used={} total={} reusable={} building={}", self.compute.iter().flatten().count(), self.compute.len(), self.compute.iter().flatten().filter_map(|entry| entry.queue.as_deref()).filter(|queue| queue.reusable()).count(), diff --git a/drivers/gpu/drm/asahi/g17/runtime/compute_exit.rs b/drivers/gpu/drm/asahi/g17/runtime/compute_exit.rs index bf9d023038a51..791c1fccae6ae 100644 --- a/drivers/gpu/drm/asahi/g17/runtime/compute_exit.rs +++ b/drivers/gpu/drm/asahi/g17/runtime/compute_exit.rs @@ -12,9 +12,57 @@ use crate::g17::{ timeout::LIVENESS_NS, Firmware, MSG_CONTROL_NOTIFY, }; -use core::sync::atomic::{fence, Ordering}; +use core::sync::atomic::{fence, AtomicU32, AtomicU64, Ordering}; use kernel::{prelude::*, sync::Arc}; +/// Waits of failed contexts' kills for a moment without other contexts' compute in flight, +/// by bucket (under 10 ms, 100 ms, 1 s, 5 s, and longer) and the longest. +static KILL_WAITS: [AtomicU32; 5] = [const { AtomicU32::new(0) }; 5]; +static KILL_WAIT_MAX_NS: AtomicU64 = AtomicU64::new(0); +const KILL_WAIT_BOUNDS_NS: [u64; 4] = [10_000_000, 100_000_000, 1_000_000_000, 5_000_000_000]; +/// A failed context's kill waiting this long is reported, once per kill and at most once per +/// this interval across kills. +const SLOW_KILL_WAIT_NS: u64 = 5_000_000_000; +static SLOW_KILL_WARNED_NS: AtomicU64 = AtomicU64::new(0); +/// Failed contexts' kills published beside other contexts' compute under GPU memory pressure, +/// and when the last one was: at most one per [`PRESSED_KILL_INTERVAL_NS`], so the memory of +/// one is freed before another is considered. +static PRESSED_KILLS: AtomicU32 = AtomicU32::new(0); +static PRESSED_KILL_NS: AtomicU64 = AtomicU64::new(0); +const PRESSED_KILL_INTERVAL_NS: u64 = 250_000_000; + +/// GPU memory is within an eighth of the device limit. A failed context holds its memory +/// until its kill is acknowledged, so kills waiting for other contexts' compute to pause can +/// push the device to its limit, and then every client's allocations fail: on Neo, +/// gnome-shell crashed on such an ENOMEM. A kill beside foreign compute risks one GPU +/// recovery instead. +fn memory_pressed() -> bool { + let ledger = &crate::memacct::LEDGER; + let limit = ledger.limit.load(Ordering::Relaxed); + limit != 0 + && ledger + .user_committed + .load(Ordering::Relaxed) + .saturating_add(ledger.kernel_resident.load(Ordering::Relaxed)) + > limit - limit / 8 +} + +fn record_kill_wait(waited: u64) { + let bucket = KILL_WAIT_BOUNDS_NS.iter().position(|bound| waited < *bound).unwrap_or(4); + KILL_WAITS[bucket].fetch_add(1, Ordering::Relaxed); + KILL_WAIT_MAX_NS.fetch_max(waited, Ordering::Relaxed); +} + +pub(in crate::g17) fn write_kill_waits(f: &mut kernel::fmt::Formatter<'_>, waiting: usize, longest: u64) + -> kernel::fmt::Result +{ + let count = |bucket: usize| KILL_WAITS[bucket].load(Ordering::Relaxed); + writeln!(f, "failed_kill_waits under_10ms={} under_100ms={} under_1s={} under_5s={} longer={} max_ms={} memory_pressed={}", + count(0), count(1), count(2), count(3), count(4), + KILL_WAIT_MAX_NS.load(Ordering::Relaxed) / 1_000_000, PRESSED_KILLS.load(Ordering::Relaxed))?; + writeln!(f, "failed_kills waiting={} longest_wait_ms={}", waiting, longest / 1_000_000) +} + impl super::Registry { /// ACK storage belongs to the already-retained transaction, so a full event /// batch cannot overflow a separate cache. No allocation or wait is needed. @@ -53,16 +101,22 @@ impl Firmware { let _ = queue.announce_exit(now, || self.primary.notify(MSG_CONTROL_NOTIFY)); } } + // Under memory pressure one kill skips the wait, at most one per interval. + let last = PRESSED_KILL_NS.load(Ordering::Relaxed); + let mut pressed = + (last == 0 || now.saturating_sub(last) >= PRESSED_KILL_INTERVAL_NS) && memory_pressed(); for index in 0..self.queues.teardown.len() { let Some((context, age)) = self.queues.teardown.closed(index, now) else { continue; }; + // A failed context's executed pairs keep their pool leases until the kill is + // acknowledged (`Pair::firmware_drained`), so its kill cannot wait for them. if !self.queues.render.teardown_idle(&context)? - || !self.queues.render.teardown_pools_released(&context) + || (!self.compute_failed(&context) && !self.queues.render.teardown_pools_released(&context)) { continue; } - if let Err(error) = self.publish_compute_exit(&context, age, now) { + if let Err(error) = self.publish_compute_exit(&context, age, now, &mut pressed) { dev_err!( self.primary.state.shared.dev.as_ref(), "Scheduler {} exit request failed: {:?}\n", @@ -74,7 +128,7 @@ impl Firmware { Ok(()) } - fn publish_compute_exit(&mut self, context: &Arc, age: u64, now: u64) -> Result { + fn publish_compute_exit(&mut self, context: &Arc, age: u64, now: u64, pressed: &mut bool) -> Result { let Some(index) = self.queues.compute.iter().position(|entry| { entry .as_ref() @@ -83,7 +137,36 @@ impl Firmware { }) else { return Ok(()); }; - if self.render_control_backpressured() || self.kill_unsafe(context, now) { + let failed = self.compute_failed(context); + if self.render_control_backpressured() { + return Ok(()); + } + let beside = failed && self.foreign_compute_running(context, now); + if failed { + if beside && !*pressed { + let queue = self.queues.compute[index] + .as_mut() + .and_then(|entry| entry.queue.as_deref_mut()); + if let Some(mut queue) = queue { + let (waited, crossed) = queue.note_kill_wait(now, SLOW_KILL_WAIT_NS); + let last = SLOW_KILL_WARNED_NS.load(Ordering::Relaxed); + if crossed + && (last == 0 || now.saturating_sub(last) >= SLOW_KILL_WAIT_NS) + && SLOW_KILL_WARNED_NS + .compare_exchange(last, now.max(1), Ordering::Relaxed, Ordering::Relaxed) + .is_ok() + { + dev_warn!( + self.primary.state.shared.dev.as_ref(), + "Scheduler {} kill has waited {} ms for other contexts' compute to pause\n", + context.id(), + waited / 1_000_000 + ); + } + } + return Ok(()); + } + } else if self.kill_unsafe(context, now) { return Ok(()); } let (owner, target) = { @@ -122,7 +205,7 @@ impl Firmware { && !self .queues .admission - .hold_render_slots_for_exit_kill(owner, hold) + .hold_render_slots_for_exit_kill(owner, hold, self.queues.render.executed_slots(context) & hold) { return Ok(()); } @@ -131,13 +214,23 @@ impl Firmware { self.queues.admission.release_exit_kill_hold(hold); return if error == EAGAIN { Ok(()) } else { Err(error) }; } - // Publication is irrevocable even if its doorbell fails. Keep both - // the killed graph and the held render identities until processor stop. + // Publication is irrevocable even if its doorbell fails. Keep the killed + // graph until processor stop, and the held render identities until this + // context's scheduler release is consumed or processors stop. let mut queue = self.queues.compute[index] .as_mut() .and_then(|entry| entry.queue.as_deref_mut()) .ok_or(EIO)?; + if failed { + record_kill_wait(queue.take_kill_wait(now)); + } + if beside { + *pressed = false; + PRESSED_KILLS.fetch_add(1, Ordering::Relaxed); + PRESSED_KILL_NS.store(now.max(1), Ordering::Relaxed); + } queue.record_exit(cookie, now); + crate::g17::trace::teardown(3, cookie, u64::from(context.id()), hold | u64::from(beside)); fence(Ordering::SeqCst); if let Err(error) = queue.announce_exit(now, || self.primary.notify(MSG_CONTROL_NOTIFY)) { dev_warn!( @@ -150,12 +243,39 @@ impl Firmware { Ok(()) } + /// Another context's compute command is in flight. On Neo, kills of failed contexts beside + /// other clients' compute occasionally made the firmware recover the GPU (twice in ten + /// minutes of a five-lane load with 67 faults), even with nothing freed before the kill's + /// acknowledgement; such a kill takes the next moment without foreign compute instead. + /// Render work is not waited for. A GPU that retired nothing for [`LIVENESS_NS`] has + /// stopped. + fn foreign_compute_running(&self, context: &Arc, now: u64) -> bool { + let stalled = crate::g17::recovery::since_retired(now).is_none_or(|since| since >= LIVENESS_NS); + !stalled + && self.queues.compute.iter().flatten().filter_map(|entry| entry.queue.as_deref()).any(|queue| { + queue.in_flight() && !queue.context().is_some_and(|bound| Arc::ptr_eq(bound, context)) + }) + } + + /// A compute command of `context` failed and the firmware holds commands behind it that it + /// never runs. `publish_compute_exit` is only reached once `teardown_idle` found all of + /// the context's render pairs idle; the pairs behind such a failure become idle when + /// their commands have executed (`Pair::firmware_drained`), so by then nothing of the + /// context runs any more. + fn compute_failed(&self, context: &Arc) -> bool { + self.queues.compute.iter().flatten().filter_map(|entry| entry.queue.as_deref()).any(|queue| { + queue.context().is_some_and(|bound| Arc::ptr_eq(bound, context)) && queue.fault_pending() + }) + } + /// A kill while other contexts' commands run makes the firmware restart the /// GPU, and repeated ones crash it; on an otherwise idle GPU it is safe. A /// closed context's commands left alone retire like any others once the GPU /// reaches them, so its queues stay quarantined and retained until then. Only /// a GPU that retired nothing for [`LIVENESS_NS`] has stopped, and then the - /// kill proceeds. + /// kill proceeds. A context whose compute failed ([`Self::compute_failed`]) + /// is not left alone, since its held commands never retire: its kill waits + /// only for other contexts' compute ([`Self::foreign_compute_running`]). fn kill_unsafe(&self, context: &Arc, now: u64) -> bool { let stalled = crate::g17::recovery::since_retired(now).is_none_or(|since| since >= LIVENESS_NS); if stalled { diff --git a/drivers/gpu/drm/asahi/g17/runtime/render.rs b/drivers/gpu/drm/asahi/g17/runtime/render.rs index b5649f059f5d2..22cd1f18876ca 100644 --- a/drivers/gpu/drm/asahi/g17/runtime/render.rs +++ b/drivers/gpu/drm/asahi/g17/runtime/render.rs @@ -76,6 +76,9 @@ pub(super) struct RenderState { pools: pools::Pools, grow: grow::Grow, retired: KVec, + /// Graphs of released contexts the firmware may still walk (see `detach_context`): kept + /// until the firmware stops, when this state is dropped. + kept_graphs: KVec, next_pm_generation: u64, } @@ -102,6 +105,7 @@ impl RenderState { pools: pools::Pools::new()?, grow: grow::Grow::new()?, retired: KVec::with_capacity(128, GFP_KERNEL)?, + kept_graphs: KVec::new(), next_pm_generation: 1, }) } @@ -300,6 +304,7 @@ impl Registry { if let Some(pair) = entry.pair.as_ref() { self.buffers.unreserve_owner(pair.buffer_id())?; } + self.note_build_room(); } return Ok(Some(UnusedRenderSpare { _entry: entry, @@ -357,11 +362,25 @@ impl Registry { .any(|(slot, entry)| entry.is_some() && self.admission.owns_slot(owner, slot)) } + /// Tell admission whether another render pair can still be built. + pub(super) fn note_build_room(&self) { + self.admission.note_build_room(self.qids.render_pair_fits()); + } + pub(super) fn ensure_render_ids(&mut self, slot: u8, owner: u64) -> Result { if owner == 0 || slot == 0 || usize::from(slot) >= RENDER_SLOTS { return Err(EINVAL); } - if self.render.entries[usize::from(slot)].is_none() { + if self.render.entries[usize::from(slot)].is_some() { + return Ok(()); + } + let built = self.build_render_ids(slot, owner); + self.note_build_room(); + built + } + + fn build_render_ids(&mut self, slot: u8, owner: u64) -> Result { + { let identity = self.identities.reserve(2)?.start; let tiling = self.qids.reserve(identity, DataMaster::Tiling, None)?; let fragment = match self.qids.reserve(identity + 1, DataMaster::Fragment, None) { @@ -405,6 +424,13 @@ impl Registry { /// ensure_render_ids cancels a first ID if the second cannot be reserved; /// validation, identity overflow, and cancellation errors still propagate. fn prepare_render_ids(&mut self, slot: u8, owner: u64, optional: bool) -> Result { + // A full queue table refuses a sibling without scanning it on every preparation. + if optional + && self.render.entries.get(usize::from(slot)).is_some_and(Option::is_none) + && !self.qids.render_pair_fits() + { + return Ok(false); + } match self.ensure_render_ids(slot, owner) { Ok(()) => Ok(true), Err(error) if optional && error == ENOSPC => Ok(false), diff --git a/drivers/gpu/drm/asahi/g17/runtime/render/exit.rs b/drivers/gpu/drm/asahi/g17/runtime/render/exit.rs index c47110cb76fad..25e1335c5ba13 100644 --- a/drivers/gpu/drm/asahi/g17/runtime/render/exit.rs +++ b/drivers/gpu/drm/asahi/g17/runtime/render/exit.rs @@ -26,6 +26,19 @@ impl RenderState { (own, foreign, slots) } + /// Slots of `context`'s pairs whose commands all ran but stay unretired until its kill. + pub(in crate::g17::runtime) fn executed_slots(&self, context: &Arc) -> u64 { + let mut slots = 0; + for (slot, entry) in self.entries.iter().enumerate() { + if entry.as_ref().and_then(|entry| entry.pair.as_ref()) + .is_some_and(|pair| pair.executed_for_kill(context)) + { + slots |= 1u64 << slot; + } + } + slots + } + pub(in crate::g17::runtime) fn qids_for_slot(&self, slot: u8) -> u128 { self.entries .get(usize::from(slot)) diff --git a/drivers/gpu/drm/asahi/g17/runtime/render/submit.rs b/drivers/gpu/drm/asahi/g17/runtime/render/submit.rs index 1ef6742b689a7..e6379178138d1 100644 --- a/drivers/gpu/drm/asahi/g17/runtime/render/submit.rs +++ b/drivers/gpu/drm/asahi/g17/runtime/render/submit.rs @@ -131,7 +131,8 @@ impl Backend { .reclaim_unused_render_spare(Some(self.owner), true)?; let ready = firmware.queues.has_render_ids(self.owner); drop(state); - if reclaimed.is_none() && !ready { + // A full queue table makes admission reuse a free killed pair instead. + if reclaimed.is_none() && !ready && !pool.killed_pair_free() { self.wait_render_capacity(&mut observed, started, sleep)?; } drop(reclaimed); diff --git a/drivers/gpu/drm/asahi/g17/runtime/render/teardown.rs b/drivers/gpu/drm/asahi/g17/runtime/render/teardown.rs index e54b1f4638f58..18063f0cd6a14 100644 --- a/drivers/gpu/drm/asahi/g17/runtime/render/teardown.rs +++ b/drivers/gpu/drm/asahi/g17/runtime/render/teardown.rs @@ -38,7 +38,7 @@ impl RenderState { return Ok(false); } if let Some(pair) = entry.pair.as_ref() { - if !pair.idle_for_context(context)? { + if !pair.idle_for_context(context)? && !pair.executed_for_kill(context) { return Ok(false); } } @@ -123,6 +123,7 @@ impl RenderState { if !out.has_room(count, 1) { return Err(EBUSY); } + let linked = context.firmware_may_link(); for entry in self.entries.iter_mut().flatten() { if !entry .context @@ -140,7 +141,14 @@ impl RenderState { let (mut parked, retired) = KBox::into_inner(pair).park(); // This park follows the consumed release of `context`. parked.release_consumed(context.scheduler_va()); - *target = Some(retired); + if linked { + // Guard (kill before free): the firmware may still walk this context's work + // list into the graph, so it is not freed or recycled before the firmware stops. + kernel::pr_warn_once!("G17: keeping render graphs of an unkilled context that still links work\n"); + keep_graph(&mut self.kept_graphs, retired); + } else { + *target = Some(retired); + } entry.parked = Some(parked); // Detached backing retains these references until the worker runs. entry.context = None; @@ -155,8 +163,12 @@ impl RenderState { if let Some(id) = self.retired[index].buffer_id() { buffers.unreserve_owner(id)?; } - let target = out.binding_slot()?; - *target = Some(self.retired.remove(index).map_err(|_| EIO)?); + let retired = self.retired.remove(index).map_err(|_| EIO)?; + if linked { + keep_graph(&mut self.kept_graphs, retired); + } else { + *out.binding_slot()? = Some(retired); + } } Ok(()) } @@ -181,3 +193,18 @@ impl Registry { Ok(()) } } + +/// Keeps `graph` until the firmware stops; if even that cannot be recorded, it is leaked +/// rather than freed. +fn keep_graph( + kept: &mut KVec, + graph: crate::g17::queue::render::RetiredBinding, +) { + if kept.reserve(1, GFP_KERNEL).is_ok() { + if let Err(error) = kept.push_within_capacity(graph) { + core::mem::forget(error.0); + } + } else { + core::mem::forget(graph); + } +} diff --git a/drivers/gpu/drm/asahi/g17/runtime/teardown.rs b/drivers/gpu/drm/asahi/g17/runtime/teardown.rs index 16bd5828265a4..0222ae4c478c8 100644 --- a/drivers/gpu/drm/asahi/g17/runtime/teardown.rs +++ b/drivers/gpu/drm/asahi/g17/runtime/teardown.rs @@ -100,6 +100,8 @@ impl ReclaimBatch { struct Service<'a> { render: &'a mut super::render::RenderState, + admission: &'a crate::g17::queue::admission::Pool, + objects: &'a Arc, compute: &'a [Option], buffers: &'a mut BufferIds, init: &'a InitData, @@ -137,8 +139,15 @@ impl Host for Service<'_> { self.primary.notify(MSG_CONTROL_NOTIFY) } fn detach(&mut self, context: &Arc) -> Result { + let linked = context.retain_linked_work(self.objects); + let (_, _, slots) = self.render.exit_scope(context); + crate::g17::trace::teardown(5, u64::from(context.id()), slots, u64::from(linked)); self.render .detach_context(context, self.buffers, self.reclaim)?; + // The firmware consumed this context's scheduler release after its kill (if any) + // was acknowledged and its pairs went idle: it no longer names these pairs, so + // their kill hold ends here rather than at the next processor stop. + self.admission.release_killed_slots(slots); // detach_context preflights this slot together with every graph slot. // Retain it before Pending removes its reference under the device lock. self.reclaim.context(context) @@ -173,6 +182,7 @@ impl Firmware { self.queues.render.observe_teardown_releases(&self.init)?; let recovery = self.recovery.pending() || self.init.recovery_state()? != 0; let Registry { + admission, render, compute, buffers, @@ -181,6 +191,8 @@ impl Firmware { } = &mut self.queues; let mut service = Service { render, + admission, + objects: &self.pool, compute, buffers, init: &self.init, diff --git a/drivers/gpu/drm/asahi/g17/trace.c b/drivers/gpu/drm/asahi/g17/trace.c index 23666be389b96..9e93291f5114e 100644 --- a/drivers/gpu/drm/asahi/g17/trace.c +++ b/drivers/gpu/drm/asahi/g17/trace.c @@ -54,3 +54,9 @@ void asahi_g17_trace_event(u8 phase) if (trace_asahi_g17_event_enabled()) trace_asahi_g17_event(phase, __arch_counter_get_cntvct()); } + +void asahi_g17_trace_teardown(u8 kind, u64 a, u64 b, u64 c) +{ + if (trace_asahi_g17_teardown_enabled()) + trace_asahi_g17_teardown(kind, a, b, c); +} diff --git a/drivers/gpu/drm/asahi/g17/trace.h b/drivers/gpu/drm/asahi/g17/trace.h index 7e5a50e774b59..d1529a2775fcc 100644 --- a/drivers/gpu/drm/asahi/g17/trace.h +++ b/drivers/gpu/drm/asahi/g17/trace.h @@ -125,6 +125,25 @@ TRACE_EVENT(asahi_g17_message, __entry->counter) ); +/* Failed-context teardown and render slot reuse (kind: see g17/trace.rs teardown()). */ +TRACE_EVENT(asahi_g17_teardown, + TP_PROTO(u8 kind, u64 a, u64 b, u64 c), + TP_ARGS(kind, a, b, c), + TP_STRUCT__entry( + __field(u8, kind) + __field(u64, a) + __field(u64, b) + __field(u64, c) + ), + TP_fast_assign( + __entry->kind = kind; + __entry->a = a; + __entry->b = b; + __entry->c = c; + ), + TP_printk("kind=%u a=%#llx b=%#llx c=%#llx", __entry->kind, __entry->a, __entry->b, __entry->c) +); + TRACE_EVENT(asahi_g17_event, TP_PROTO(u8 phase, u64 counter), TP_ARGS(phase, counter), diff --git a/drivers/gpu/drm/asahi/g17/trace.rs b/drivers/gpu/drm/asahi/g17/trace.rs index b76b46425d2ce..f498de82e87c6 100644 --- a/drivers/gpu/drm/asahi/g17/trace.rs +++ b/drivers/gpu/drm/asahi/g17/trace.rs @@ -16,6 +16,7 @@ unsafe extern "C" { fn asahi_g17_trace_doorbell_enabled() -> bool; fn asahi_g17_trace_doorbell(engine: u8, power: u32); fn asahi_g17_trace_event(phase: u8); + fn asahi_g17_trace_teardown(kind: u8, a: u64, b: u64, c: u64); } pub(super) const RENDER: u8 = 0; @@ -74,6 +75,15 @@ pub(super) fn message(kind: u8, value: u64) { unsafe { asahi_g17_trace_message(kind, value) }; } +/// Failed-context teardown: 1 compute fault (qid, stamp), 2 unstamped render retire (slot, +/// ordinal), 3 kill published (cookie, context, held slots | 1 if it skipped the wait for other +/// contexts' compute under memory pressure; slot 0 is never held), 4 kill acknowledged (cookie, qid), +/// 5 detach (context, slots, linked), 6 render slot to a new owner (slot, old, new | killed << 63). +pub(crate) fn teardown(kind: u8, a: u64, b: u64, c: u64) { + // SAFETY: Plain values only. + unsafe { asahi_g17_trace_teardown(kind, a, b, c) }; +} + /// Event worker pass boundaries: 0 begin, 1 fences signalled, 2 end. pub(super) fn event(phase: u8) { // SAFETY: Plain values only. diff --git a/drivers/gpu/drm/asahi/g17/trace_api.h b/drivers/gpu/drm/asahi/g17/trace_api.h index d09f529a163f0..0049aa0079634 100644 --- a/drivers/gpu/drm/asahi/g17/trace_api.h +++ b/drivers/gpu/drm/asahi/g17/trace_api.h @@ -20,5 +20,6 @@ bool asahi_g17_trace_doorbell_enabled(void); void asahi_g17_trace_doorbell(u8 engine, u32 power); void asahi_g17_trace_message(u8 kind, u64 value); void asahi_g17_trace_event(u8 phase); +void asahi_g17_trace_teardown(u8 kind, u64 a, u64 b, u64 c); #endif /* _ASAHI_G17_TRACE_API_H */ From a1eb22ddf5054cb9c738dd15aa63928a9a4b5b53 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Tue, 27 Oct 2026 17:16:26 +1100 Subject: [PATCH 103/184] drm/asahi: retire a G17 render pass on its fragment notification An event pass considered a render pair only once both of its queues had posted a stamp notification, the tiling queue's and the fragment queue's. The tiling notification adds nothing a retirement needs: the fragment kick starts only after its fragment barrier saw the tiling stamp, and the observation still requires both stamps in memory to cover the pass. Recovery attribution reads the stamps, and parameter-buffer growth arrives as its own record. Select a pair, and decide that it needs a rescan, on the fragment queue's notification alone, through one helper with KUnit coverage. A pass whose only news is a tiling stamp no longer borrows the pair for an observation that cannot retire. Link: https://github.com/aurora-silicon/linux/commit/de0b22a8370e0dec3ffb080c173aea4aa4fc331b Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/gpu/drm/asahi/g17/queue/render.rs | 48 +++++++++++++++++-- drivers/gpu/drm/asahi/g17/runtime/render.rs | 8 ++-- .../drm/asahi/g17/runtime/render/observe.rs | 4 +- 3 files changed, 48 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/asahi/g17/queue/render.rs b/drivers/gpu/drm/asahi/g17/queue/render.rs index bcf10f97fa2d7..9c0b1e4e86f63 100644 --- a/drivers/gpu/drm/asahi/g17/queue/render.rs +++ b/drivers/gpu/drm/asahi/g17/queue/render.rs @@ -1034,11 +1034,7 @@ impl Pair { ] } fn selected(&self, masks: Option<[u64; 2]>) -> bool { - masks.is_none_or(|masks| { - self.qids() - .into_iter() - .all(|qid| masks[usize::from(qid) / 64] & (1u64 << (qid % 64)) != 0) - }) + masks.is_none_or(|masks| completion_noted(self.qids(), masks)) } /// The oldest published group still running, for cross-VM timeout attribution /// (crate::g17::timeout). A quarantined pair's work holds the GPU until the firmware drains @@ -2246,3 +2242,45 @@ impl Pair { Ok(()) } } + +/// Whether `masks`, the stamp notifications collected for a pair whose tiling and fragment +/// queues are `qids`, can mean a finished pass: the fragment queue's bit. A fragment kick starts +/// only after its fragment barrier saw the tiling stamp, and retirement still checks both stamps +/// in memory, so the tiling notification is not needed to retire. +pub(crate) fn completion_noted(qids: [u8; 2], masks: [u64; 2]) -> bool { + let fragment = usize::from(qids[1]); + masks[fragment / 64] & (1u64 << (fragment % 64)) != 0 +} + +#[kunit_tests(asahi_g17_render_completion_noted)] +mod completion_noted_tests { + use super::completion_noted; + + fn bit(qid: u8) -> [u64; 2] { + let mut masks = [0; 2]; + masks[usize::from(qid) / 64] |= 1u64 << (qid % 64); + masks + } + + #[test] + fn fragment_notification_alone_selects() { + assert!(completion_noted([3, 4], bit(4))); + assert!(completion_noted([70, 100], bit(100))); + } + + #[test] + fn tiling_notification_alone_does_not_select() { + assert!(!completion_noted([3, 4], bit(3))); + assert!(!completion_noted([70, 100], bit(70))); + assert!(!completion_noted([3, 4], [0; 2])); + } + + #[test] + fn other_queues_do_not_select() { + assert!(!completion_noted([3, 4], bit(5))); + assert!(!completion_noted([3, 68], bit(4))); + let mut both = bit(3); + both[1] |= bit(68)[1]; + assert!(completion_noted([3, 68], both)); + } +} diff --git a/drivers/gpu/drm/asahi/g17/runtime/render.rs b/drivers/gpu/drm/asahi/g17/runtime/render.rs index 22cd1f18876ca..b62f677b014f0 100644 --- a/drivers/gpu/drm/asahi/g17/runtime/render.rs +++ b/drivers/gpu/drm/asahi/g17/runtime/render.rs @@ -1057,10 +1057,10 @@ impl Registry { |pair| pair.published_in_flight(), ); active - && entry.reservation.ids.into_iter().all(|id| { - let qid = usize::from(id.qid()); - entry.event_masks[qid / 64] & (1u64 << (qid % 64)) != 0 - }) + && crate::g17::queue::render::completion_noted( + entry.reservation.ids.map(kick::Id::qid), + entry.event_masks, + ) }) } pub(in crate::g17) fn defer_render_timeout( diff --git a/drivers/gpu/drm/asahi/g17/runtime/render/observe.rs b/drivers/gpu/drm/asahi/g17/runtime/render/observe.rs index 52bdd964ca4ae..a0a92c2f21b89 100644 --- a/drivers/gpu/drm/asahi/g17/runtime/render/observe.rs +++ b/drivers/gpu/drm/asahi/g17/runtime/render/observe.rs @@ -26,9 +26,7 @@ impl crate::g17::Firmware { }; let qids = pair.qids(); if !pair.published_in_flight() - || !qids - .into_iter() - .all(|qid| entry.event_masks[usize::from(qid) / 64] & (1u64 << (qid % 64)) != 0) + || !crate::g17::queue::render::completion_noted(qids, entry.event_masks) { continue; } From e105f47bdf1e430e187e04cc794c36ad34fef49c Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Tue, 27 Oct 2026 17:16:26 +1100 Subject: [PATCH 104/184] drm/asahi: set the G17 tiling first-kick word on a pair's first kick only The tiling descriptor has a word at the position of the fragment descriptor's first-kick word, which the fragment stage sets only on a queue pair's first kick. The tiling setup set it on every kick. With it set, the firmware also notifies the host of the tiling queue's completion on every pass, so each render cost two firmware notifications, two mailbox interrupts and two event passes, while retirement needs only the fragment queue's notification (previous commit). Name the word first_kick and set it for ordinal 0 only, as for the fragment stage, on the A18 Pro firmware (26A428). The M4 Pro firmware (25G83) keeps the old value on every kick until it is measured there. Measured on Neo in one boot through a diagnostic switch that toggled only this word (3 rounds each, medians, us per submit): GPU mailbox IRQs per submit (render.light d32) 2.18 -> 1.10 render.light tri queue d1 105.26 -> 104.12 render.light tri queue d8 30.98 -> 29.30 render.light tri queue d32 29.67 -> 27.80 render.light tri queue d64 29.67 -> 27.78 render.light tri procs d2 52.12 -> 49.73 render.light tri queues d2 52.16 -> 49.65 compute.light queue d1 103.39 -> 104.08 sync.signal queues d2 timeline / binary 22.79 / 22.37 -> 22.61 / 22.61 Link: https://github.com/aurora-silicon/linux/commit/5073eac15192ff8aec118ed2dc85a3d632a6f845 Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/gpu/drm/asahi/g17/fw/render.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/asahi/g17/fw/render.rs b/drivers/gpu/drm/asahi/g17/fw/render.rs index aa8693bf7140b..a6bc1704d1418 100644 --- a/drivers/gpu/drm/asahi/g17/fw/render.rs +++ b/drivers/gpu/drm/asahi/g17/fw/render.rs @@ -1023,7 +1023,11 @@ pub(crate) struct TaDescriptor { /// QID of the tiling queue. pub(crate) qid: u32, pub(crate) unk_8be: u32, - pub(crate) unk_8c2: u32, + /// Non-zero on the first kick of a queue pair, like the fragment descriptor's word at the + /// same position. Set on every kick, the firmware also notifies the host of each pass's + /// tiling completion, which no retirement needs. The 25G83 firmware keeps it set on every + /// kick until this is measured there. + pub(crate) first_kick: u32, /// Completion value of the tiling kick (see /// [`KickTimestamp::completion_value`]). pub(crate) completion: u64, @@ -1073,7 +1077,6 @@ impl TaDescriptor { const ARRAY_OFFSET: u64 = core::mem::offset_of!(Self, registers) as u64; const UNK_876: u32 = u32::MAX; const UNK_892: u32 = 1; - const UNK_8C2: u32 = 1; const UNK_932: u8 = 0x44; const UNK_93C: u8 = 1; const UNK_93E_FIRST_KICK: [u8; 2] = [0xd0, 0x91]; @@ -1127,7 +1130,10 @@ impl TaDescriptor { self.aux_stamp_va = args.ta.aux_stamp_va; self.stamp_value_2 = stamp_value; self.qid = args.ta.qid.into(); - self.unk_8c2 = Self::UNK_8C2; + self.first_kick = match args.programs == RegisterPrograms::V25G83 { + true => 1, + false => u32::from(args.ordinal == 0), + }; self.current_id = resources.ta_ids.current.into(); self.ordinal = args.ordinal as u32; self.timestamp_start_va = resources.timestamp_va; From e4ee19363ec942580be56321b7fca5dee46eb0cc Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Tue, 27 Oct 2026 17:17:47 +1100 Subject: [PATCH 105/184] drm/asahi: preserve raw G17 firmware reports for clock and fault qualification G17 discarded statistics payloads and unknown event words before returning ring slots. Keep the latest full statistics sample per role/type with host monotonic time and count in read-only debugfs, and add a static-key raw report tracepoint for complete trace/statistics/event timelines. Copy before publishing the consumer; retain the existing tag and transport quarantine rules. Debugfs owns the session and samples under the device mutex without waking firmware or reading gated core registers. No decoded achieved-clock/power ABI is available for these firmware builds. Do not relabel legacy pstate fields, requested OPPs or advertised maxima as achieved frequency. Preserve raw observations so their semantics can be qualified independently. KUnit covers full payload preservation, role/type separation and invalid-tag bounds. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/gpu/drm/asahi/g17/channel.rs | 34 ++++++---- drivers/gpu/drm/asahi/g17/mod.rs | 9 ++- drivers/gpu/drm/asahi/g17/reports.rs | 97 +++++++++++++++++++++++++++ drivers/gpu/drm/asahi/g17/trace.c | 10 +++ drivers/gpu/drm/asahi/g17/trace.h | 21 ++++++ drivers/gpu/drm/asahi/g17/trace.rs | 16 +++++ drivers/gpu/drm/asahi/g17/trace_api.h | 3 + 7 files changed, 177 insertions(+), 13 deletions(-) create mode 100644 drivers/gpu/drm/asahi/g17/reports.rs diff --git a/drivers/gpu/drm/asahi/g17/channel.rs b/drivers/gpu/drm/asahi/g17/channel.rs index b06743abfc446..568647ebb444a 100644 --- a/drivers/gpu/drm/asahi/g17/channel.rs +++ b/drivers/gpu/drm/asahi/g17/channel.rs @@ -43,16 +43,21 @@ impl ReportRing { /// Host-side receive-ring state. A quarantined ring stays stopped until the /// device is destroyed; firmware restart does not clear it. -#[derive(Debug)] pub(crate) struct Rings { quarantined: [[bool; 2]; 2], + reports: super::reports::Reports, } impl Rings { - pub(crate) const fn new() -> Self { - Self { + pub(crate) fn new() -> Result { + Ok(Self { quarantined: [[false; 2]; 2], - } + reports: super::reports::Reports::new()?, + }) + } + + pub(super) fn write_reports(&self, f: &mut kernel::fmt::Formatter<'_>) -> kernel::fmt::Result { + self.reports.write(f) } pub(crate) fn drain_trace(&mut self, dev: &Device, init: &InitData, role: Role) { @@ -68,7 +73,7 @@ impl Rings { if *quarantined { return; } - if let Err(error) = Self::consume_report(init, role, ring) { + if let Err(error) = Self::consume_report(init, role, ring, &mut self.reports) { *quarantined = true; dev_err!( dev.as_ref(), @@ -80,7 +85,7 @@ impl Rings { } } - fn consume_report(init: &InitData, role: Role, ring: ReportRing) -> Result { + fn consume_report(init: &InitData, role: Role, ring: ReportRing, reports: &mut super::reports::Reports) -> Result { let (state, entries, capacity, allowed_types) = ring.geometry(); let consumer_word = init.status_word(role, state)?; let producer_word = init.status_word(role, state + RX_PRODUCER_OFFSET)?; @@ -94,14 +99,17 @@ impl Rings { } while consumer != producer { let offset = entries + consumer as usize * EVENT_RECORD_SIZE; - let tag = match ring { - // Copy the entire trace entry before returning its slot. - ReportRing::Trace => init.status_read_words::(role, offset)?[0], - ReportRing::Statistics => init.status_read_words::<1>(role, offset)?[0], - }; + // Copy all words before returning the slot. Interpretation is a + // separate firmware qualification step, never a scheduling input. + let words = init.status_read_words::(role, offset)?; + super::trace::report(role as u8, ring as u8, &words); + let tag = words[0]; if tag >= u32::BITS || allowed_types & (1 << tag) == 0 { return Err(EIO); } + if matches!(ring, ReportRing::Statistics) { + reports.record(role, words, super::now_ns()); + } consumer = (consumer + 1) & (capacity - 1); fence(Ordering::SeqCst); consumer_word.store(consumer, Ordering::Relaxed); @@ -197,7 +205,9 @@ impl Events { // Observe the producer before reading the record it publishes. fence(Ordering::Acquire); let offset = status::EVENT_RING as usize + consumer as usize * EVENT_RECORD_SIZE; - let record = EventRecord::from_words(init.status_read_words(role, offset)?); + let words = init.status_read_words(role, offset)?; + super::trace::report(role as u8, 2, &words); + let record = EventRecord::from_words(words); let event = record.decode_for(self.graph); fence(Ordering::SeqCst); consumer_word.store((consumer + 1) & (EVENT_SLOTS - 1), Ordering::Relaxed); diff --git a/drivers/gpu/drm/asahi/g17/mod.rs b/drivers/gpu/drm/asahi/g17/mod.rs index 78a924782a1a2..d7a42c5c82bdb 100644 --- a/drivers/gpu/drm/asahi/g17/mod.rs +++ b/drivers/gpu/drm/asahi/g17/mod.rs @@ -50,6 +50,7 @@ mod kick; mod object; mod port; pub(crate) mod power; +mod reports; mod preparation; mod qos; mod queue; @@ -1099,6 +1100,7 @@ pub(crate) struct Gpu { ids: gpu::SequenceIDs, shared: Arc, _capacity: Pin>>, + _reports: Pin>>, } impl Gpu { @@ -1206,6 +1208,10 @@ impl Gpu { directory.read_only_file(c"capacity", capacity::View(shared.clone())), GFP_KERNEL, )?; + let reports = KBox::pin_init( + directory.read_only_file(c"firmware_reports", reports::View(shared.clone())), + GFP_KERNEL, + )?; let gpu = Arc::new( Gpu { cfg, @@ -1215,6 +1221,7 @@ impl Gpu { ids: Default::default(), shared, _capacity: capacity, + _reports: reports, }, GFP_KERNEL, )?; @@ -1356,7 +1363,7 @@ impl Gpu { bridge, port: port::Port::new()?, seen_submissions: 0, - rings: channel::Rings::new(), + rings: channel::Rings::new()?, recovery: recovery::State::new(), effort: power::Effort::new(), hold: power::Hold::new(), diff --git a/drivers/gpu/drm/asahi/g17/reports.rs b/drivers/gpu/drm/asahi/g17/reports.rs new file mode 100644 index 0000000000000..7e1afef70cad2 --- /dev/null +++ b/drivers/gpu/drm/asahi/g17/reports.rs @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! Preserve the firmware's statistics without assigning unqualified units or +//! treating another firmware's pstate report as an achieved GPU frequency. +//! Writers and readers hold the device mutex; reads never power the GPU up. + +use super::{fw::channels::{EVENT_WORDS, STATISTICS_TYPES}, Role}; +use kernel::prelude::*; + +#[derive(Clone, Copy, Default)] +struct Sample { + count: u64, + host_ns: u64, + words: [u32; EVENT_WORDS], +} + +pub(super) struct Reports { + samples: KVec, +} + +impl Reports { + pub(super) fn new() -> Result { + // Allocate before firmware starts; receive and debugfs paths allocate nothing. + let mut samples = KVec::with_capacity(64, GFP_KERNEL)?; + for _ in 0..64 { + samples.push(Sample::default(), GFP_KERNEL)?; + } + Ok(Self { samples }) + } + + pub(super) fn record(&mut self, role: Role, words: [u32; EVENT_WORDS], host_ns: u64) { + let tag = words[0]; + if tag >= u32::BITS || STATISTICS_TYPES & (1 << tag) == 0 { + return; + } + let sample = &mut self.samples[role as usize * 32 + tag as usize]; + sample.count = sample.count.saturating_add(1); + sample.host_ns = host_ns; + sample.words = words; + } + + pub(super) fn write(&self, f: &mut kernel::fmt::Formatter<'_>) -> kernel::fmt::Result { + writeln!(f, "format=1 units=raw host_time=monotonic_ns achieved_frequency=unqualified")?; + for (index, sample) in self.samples.iter().enumerate().filter(|(_, s)| s.count != 0) { + writeln!(f, "role={} tag={:#x} count={} host_ns={} words={:08x?}", + index / 32, index % 32, sample.count, sample.host_ns, sample.words)?; + } + Ok(()) + } +} + +/// The view owns the session, not a pointer into a detachable Firmware. +pub(super) struct View(pub(super) kernel::sync::Arc); + +impl kernel::debugfs::Writer for View { + fn write(&self, f: &mut kernel::fmt::Formatter<'_>) -> kernel::fmt::Result { + let state = self.0.state.lock(); + let Some(firmware) = state.as_deref() else { return writeln!(f, "online=0"); }; + writeln!(f, "online=1 graph={:?} sampled_ns={}", self.0.variant.graph, super::now_ns())?; + firmware.rings.write_reports(f) + } +} + +#[kernel::macros::kunit_tests(asahi_g17_reports)] +mod tests { + use super::*; + + #[test] + fn retains_all_words_and_separates_role_and_tag() { + let mut reports = Reports::new().unwrap(); + let mut words = [0; EVENT_WORDS]; + words[0] = 16; + words[17] = 0xdeadbeef; + reports.record(Role::Primary, words, 100); + reports.record(Role::Primary, words, 101); + reports.record(Role::Secondary, words, 102); + assert_eq!(reports.samples[16].count, 2); + assert_eq!(reports.samples[16].host_ns, 101); + assert_eq!(reports.samples[16].words, words); + assert_eq!(reports.samples[48].count, 1); + words[0] = 17; + reports.record(Role::Primary, words, 103); + assert_eq!(reports.samples[17].count, 1); + assert_eq!(reports.samples[16].count, 2); + } + + #[test] + fn rejects_invalid_tags_before_indexing() { + let mut reports = Reports::new().unwrap(); + for tag in [0, 15, 29, 31, 32, u32::MAX] { + let mut words = [0; EVENT_WORDS]; + words[0] = tag; + reports.record(Role::Primary, words, 1); + } + assert!(reports.samples.iter().all(|s| s.count == 0)); + } +} diff --git a/drivers/gpu/drm/asahi/g17/trace.c b/drivers/gpu/drm/asahi/g17/trace.c index 9e93291f5114e..59f0094d129e9 100644 --- a/drivers/gpu/drm/asahi/g17/trace.c +++ b/drivers/gpu/drm/asahi/g17/trace.c @@ -60,3 +60,13 @@ void asahi_g17_trace_teardown(u8 kind, u64 a, u64 b, u64 c) if (trace_asahi_g17_teardown_enabled()) trace_asahi_g17_teardown(kind, a, b, c); } + +bool asahi_g17_trace_report_enabled(void) +{ + return trace_asahi_g17_report_enabled(); +} + +void asahi_g17_trace_report(u8 role, u8 ring, const u32 *words) +{ + trace_asahi_g17_report(role, ring, words, __arch_counter_get_cntvct()); +} diff --git a/drivers/gpu/drm/asahi/g17/trace.h b/drivers/gpu/drm/asahi/g17/trace.h index d1529a2775fcc..36e1b445f1b77 100644 --- a/drivers/gpu/drm/asahi/g17/trace.h +++ b/drivers/gpu/drm/asahi/g17/trace.h @@ -158,6 +158,27 @@ TRACE_EVENT(asahi_g17_event, TP_printk("phase=%u counter=%llu", __entry->phase, __entry->counter) ); +/* Raw 18-word records: trace=0, statistics=1, event=2. No unit inference. */ +TRACE_EVENT(asahi_g17_report, + TP_PROTO(u8 role, u8 ring, const u32 *words, u64 counter), + TP_ARGS(role, ring, words, counter), + TP_STRUCT__entry( + __field(u8, role) + __field(u8, ring) + __array(u32, words, 18) + __field(u64, counter) + ), + TP_fast_assign( + __entry->role = role; + __entry->ring = ring; + memcpy(__entry->words, words, sizeof(__entry->words)); + __entry->counter = counter; + ), + TP_printk("role=%u ring=%u words=%s counter=%llu", __entry->role, + __entry->ring, __print_array(__entry->words, 18, sizeof(u32)), + __entry->counter) +); + #endif #undef TRACE_INCLUDE_PATH diff --git a/drivers/gpu/drm/asahi/g17/trace.rs b/drivers/gpu/drm/asahi/g17/trace.rs index f498de82e87c6..cd38c95193585 100644 --- a/drivers/gpu/drm/asahi/g17/trace.rs +++ b/drivers/gpu/drm/asahi/g17/trace.rs @@ -89,3 +89,19 @@ pub(super) fn event(phase: u8) { // SAFETY: Plain values only. unsafe { asahi_g17_trace_event(phase) }; } + +unsafe extern "C" { + fn asahi_g17_trace_report_enabled() -> bool; + fn asahi_g17_trace_report(role: u8, ring: u8, words: *const u32); +} + +/// Copies a complete firmware record while the host still owns its slot. +/// Static-key gated; no MMIO reads or firmware requests are issued. +pub(super) fn report(role: u8, ring: u8, words: &[u32; super::fw::channels::EVENT_WORDS]) { + // SAFETY: Pure static-key query. + if !unsafe { asahi_g17_trace_report_enabled() } { + return; + } + // SAFETY: C synchronously copies the complete initialized 18-word array. + unsafe { asahi_g17_trace_report(role, ring, words.as_ptr()) }; +} diff --git a/drivers/gpu/drm/asahi/g17/trace_api.h b/drivers/gpu/drm/asahi/g17/trace_api.h index 0049aa0079634..be5eff4c8161b 100644 --- a/drivers/gpu/drm/asahi/g17/trace_api.h +++ b/drivers/gpu/drm/asahi/g17/trace_api.h @@ -22,4 +22,7 @@ void asahi_g17_trace_message(u8 kind, u64 value); void asahi_g17_trace_event(u8 phase); void asahi_g17_trace_teardown(u8 kind, u64 a, u64 b, u64 c); +bool asahi_g17_trace_report_enabled(void); +void asahi_g17_trace_report(u8 role, u8 ring, const u32 *words); + #endif /* _ASAHI_G17_TRACE_API_H */ From 22c3c57a7c1597175e9424782b10595e40d0e382 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Tue, 27 Oct 2026 17:18:33 +1100 Subject: [PATCH 106/184] drm/asahi: snapshot the blamed render before acknowledging GPU recovery Retain bounded first-fault evidence from the already mapped render graph: queue identities, completion stamps, free-list balance and the oldest four commands' ring cursors, pass timestamps and scheduler identity. Sample while firmware is halted at state 1 before publishing recovery targets and before acknowledging the restart. Explicitly report a borrowed/unavailable pair. The existing recovery reason and blame alone do not distinguish a stalled tiler from fragment execution, lost completion visibility or work that never started. This observation makes that distinction possible without new MMIO addresses, invented reason codes, timeout changes or weaker release rules. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/gpu/drm/asahi/g17/queue/render.rs | 13 +++++++++++++ drivers/gpu/drm/asahi/g17/recovery.rs | 5 +++++ drivers/gpu/drm/asahi/g17/runtime/events.rs | 3 +++ .../gpu/drm/asahi/g17/runtime/render/recovery.rs | 14 ++++++++++++++ 4 files changed, 35 insertions(+) diff --git a/drivers/gpu/drm/asahi/g17/queue/render.rs b/drivers/gpu/drm/asahi/g17/queue/render.rs index 9c0b1e4e86f63..921f6eb3fb6eb 100644 --- a/drivers/gpu/drm/asahi/g17/queue/render.rs +++ b/drivers/gpu/drm/asahi/g17/queue/render.rs @@ -1033,6 +1033,19 @@ impl Pair { }, ] } + /// Bounded first-fault evidence while the recovery owner has firmware + /// halted, before it acknowledges state 1. Never changes retirement. + pub(crate) fn report_recovery(&self, stamps: Result<[u32; 2]>) { + pr_warn!("G17: recovery render slot={} qids={:?} active={} prepared={} quarantined={} stamps={:?} freelist={:?}\n", + self.slot, self.qids(), self.active.len(), self.prepared.is_some(), + self.quarantined, stamps, self.free_list.counters()); + for active in self.active.iter().take(4) { + pr_warn!("G17: recovery render ordinal={} published={} ctx={} seq={} snapshot={:?}\n", + active.ticket.ordinal, active.outer_published, active.packet.context.id(), + active.packet.order.sequence, observation::snapshot(&self.memory, active.ticket.ordinal)); + } + } + fn selected(&self, masks: Option<[u64; 2]>) -> bool { masks.is_none_or(|masks| completion_noted(self.qids(), masks)) } diff --git a/drivers/gpu/drm/asahi/g17/recovery.rs b/drivers/gpu/drm/asahi/g17/recovery.rs index 8bffec476c9ac..7787485fa713a 100644 --- a/drivers/gpu/drm/asahi/g17/recovery.rs +++ b/drivers/gpu/drm/asahi/g17/recovery.rs @@ -103,6 +103,8 @@ pub(super) trait Queues { /// partly executed, so it must never report success; see /// `Completion::note_interrupted`. fn note_interrupted(&mut self, qid: u8, stamp: u32) -> Interrupted; + /// Optional diagnostics only; absence cannot alter attribution or release. + fn report_blamed_render(&self, _qid: u8) {} fn settle_blamed_render(&mut self, qid: u8, error: Error); fn settle_cancelled_renders(&mut self, qids: u128); fn rescan_compute(&mut self) -> Result; @@ -245,6 +247,9 @@ pub(super) fn service( } let mut sources = host.fault_sources()?; sources.report(request.generation, super::queue::compute::retirement::stampless_total()); + if let Some(qid) = request.blamed { + host.report_blamed_render(qid); + } let target_error = host.publish_recovery_targets(); host.transition(1, 2)?; let mut recovered = false; diff --git a/drivers/gpu/drm/asahi/g17/runtime/events.rs b/drivers/gpu/drm/asahi/g17/runtime/events.rs index b410a90568d04..26ba3f0219697 100644 --- a/drivers/gpu/drm/asahi/g17/runtime/events.rs +++ b/drivers/gpu/drm/asahi/g17/runtime/events.rs @@ -228,6 +228,9 @@ impl recovery::Queues for Service<'_> { queue.note_interrupted(stamp) }) } + fn report_blamed_render(&self, qid: u8) { + self.firmware.queues.report_render_recovery(qid, &self.firmware.port); + } fn settle_blamed_render(&mut self, qid: u8, error: Error) { let result = self .firmware diff --git a/drivers/gpu/drm/asahi/g17/runtime/render/recovery.rs b/drivers/gpu/drm/asahi/g17/runtime/render/recovery.rs index 3a5b042fff683..20f537ab69598 100644 --- a/drivers/gpu/drm/asahi/g17/runtime/render/recovery.rs +++ b/drivers/gpu/drm/asahi/g17/runtime/render/recovery.rs @@ -6,6 +6,20 @@ use crate::g17::runtime::Registry; use kernel::prelude::*; impl Registry { + pub(in crate::g17::runtime) fn report_render_recovery(&self, qid: u8, port: &crate::g17::port::Port) { + for entry in self.render.entries.iter().flatten().filter(|entry| { + entry.reservation.ids.iter().any(|id| id.qid() == qid) + }) { + if let Some(pair) = &entry.pair { + let qids = pair.qids(); + pair.report_recovery((|| Ok([port.stamp(qids[0])?, port.stamp(qids[1])?]))()); + } else { + pr_warn!("G17: recovery render queue={} unavailable borrowed={} constructing={}\n", + qid, entry.borrowed_active, entry.constructing); + } + } + } + pub(in crate::g17::runtime) fn render_pass_started(&self, qid: u8, stamp: u64) -> Option { self.render.entries.iter().flatten() .filter_map(|entry| entry.pair.as_ref()) From 99725fd07818ba58a4336e34d3428a1f920a21ab Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Tue, 27 Oct 2026 17:18:33 +1100 Subject: [PATCH 107/184] soc: apple: clpc: expose the graphics input holding the memory floor Expose the last sampled graphics PMGR status, whether it asserted demand, and read-error count alongside existing CPU requests and per-rail votes. A greeter's held floor cannot distinguish GPU power demand from CPU activity or failed status reads. Keep the existing fail-open performance policy and 32 ms release unchanged; status reads never add a vote or wake graphics. The debugfs values are independently sampled diagnostics, not one atomic hardware snapshot. Qualify long-idle/no-client release before changing the power-domain input or memory-floor policy. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/soc/apple/clpc.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/drivers/soc/apple/clpc.c b/drivers/soc/apple/clpc.c index d51501db045cb..1acfcc0b598dd 100644 --- a/drivers/soc/apple/clpc.c +++ b/drivers/soc/apple/clpc.c @@ -101,6 +101,9 @@ struct apple_clpc { struct dentry *debugfs; struct regmap *gpu_pmgr; u32 gpu_offset; + u32 gpu_status; + bool gpu_active; + unsigned long gpu_read_errors; }; /* cpufreq policies can appear after probe; pick up new ones as they do. */ @@ -254,8 +257,14 @@ static void apple_clpc_sample(struct apple_clpc *clpc) /* Keep the qualified floor while graphics is powered. A failed * read must not silently starve a running graphics consumer. */ - if (regmap_read(clpc->gpu_pmgr, clpc->gpu_offset, &status) || - (status & GENMASK(7, 4)) == GENMASK(7, 4)) + if (regmap_read(clpc->gpu_pmgr, clpc->gpu_offset, &status)) { + clpc->gpu_read_errors++; + clpc->gpu_active = true; + } else { + clpc->gpu_status = status; + clpc->gpu_active = (status & GENMASK(7, 4)) == GENMASK(7, 4); + } + if (clpc->gpu_active) for (rail = 0; rail < APPLE_CLPC_RAILS; rail++) demand[rail] = clpc->hw->map[rail][APPLE_CLPC_LEVELS - 1]; } @@ -299,6 +308,9 @@ static int apple_clpc_status_show(struct seq_file *s, void *unused) clpc->hw->rail[rail]), clpc->hw->map[rail][0], clpc->hw->map[rail][1], clpc->hw->map[rail][2], clpc->hw->map[rail][3]); + seq_printf(s, "gpu: present %u active %u status %#x read_errors %lu\n", + !!clpc->gpu_pmgr, READ_ONCE(clpc->gpu_active), + READ_ONCE(clpc->gpu_status), READ_ONCE(clpc->gpu_read_errors)); seq_printf(s, "samples: %lu update errors: %lu\n", clpc->samples, clpc->update_errors); for (i = 0; i < clpc->nr_clusters; i++) From ce7122191c726e180ca7e5829aad0084ea97db3a Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Tue, 27 Oct 2026 17:20:54 +1100 Subject: [PATCH 108/184] docs: record g4 runtime applicability and firmware telemetry gates Pin the public #496/#504/#505 ports and distinguish shared retention fixes from firmware-qualified fault witnesses and descriptor changes. Document raw observability and the first-fault/idle-floor evidence needed before claiming achieved-clock, CTS or remaining performance parity. Assisted-by: LLM Signed-off-by: Ryan Murray --- GAPS.md | 42 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/GAPS.md b/GAPS.md index b958e0225f815..7944948712418 100644 --- a/GAPS.md +++ b/GAPS.md @@ -12,8 +12,9 @@ T6040 data, honors the existing DT idle-delay policy, and closes a compute release-witness gap across recovery. GPU voltages, firmware features and the submission algorithm remain unchanged from the reconstructed base. The major T6040 calibration and image-admission changes requested by this audit are -already in the baseline. Software parity does not establish benchmark or CTS -parity. There are no J616s results for this series yet. +already in the baseline. Software parity does not establish all-workload benchmark or CTS +parity. The operator has selected g3 as the measured J616s baseline; g4 +retains it and adds the qualified shared ports and diagnostics below. ## Gap table @@ -30,7 +31,7 @@ parity. There are no J616s results for this series yet. | Periodic preemption | Context-switch timer mask is zero | Same zero mask; two-cluster operand-state allocation differs by hardware layout | Same runtime behavior | `g17/fw/initdata.rs::CONTEXT_SWITCH_TIMER_MASK`, `g17/fw/compute.rs::OperandState`; no evidence supports changing the mask | | High-priority queues | HIGH / MEDIUM / LOW firmware scheduling profiles | Same scheduling-policy selector and priority work rings | Same runtime behavior | `g17/mod.rs::scheduling_policy`, `g17/fw/channels.rs`, `g17/qos.rs` | | Timestamp publication | Retains one CPU mapping for each firmware timestamp buffer | Same shared timestamp buffer and completion publication | Host timestamp suite again compiles and executes the production retained-mapping writer | `timestamp.rs`, `mmu.rs::TimestampVmap`, `tools/testing/selftests/drivers/gpu/asahi-m4/test-timestamps.py` | -| Statistics | Drains firmware statistics; no `agx_stats` snapshot | Same; `stats_snapshot()` returns `None` on both | Same runtime behavior | `g17/channel.rs`, `g17/runtime/events.rs`, `g17/mod.rs`; statistics decoding/export remains a shared limitation | +| Statistics | Drains firmware statistics; no decoded `agx_stats` snapshot | Same; payloads discarded | g4 preserves full per-type/per-role raw snapshots and static-key report tracing | `g17/reports.rs`, `g17/channel.rs`, `g17/trace.*`; achieved-frequency/power decoding remains gated on firmware semantics | | Firmware startup / registration | Two instances, message acknowledgement, POWER_ON_DONE and MMIO render-pair registration; no lead commands | One merged 25G83 graph, status acknowledgement, no POWER_ON_DONE, no MMIO pair registration; lead commands required | Same runtime behavior | `g17/fw/variant.rs`, `g17/initdata/v25g83.rs`; startup differences are ABI requirements, while MMIO pair registration on 25G83 remains unqualified | | Queue pointer / restart layout | Producer at +0x40, recovery target at +0x50 | Producer at +0x50, no recovery-target word | Same runtime behavior | `g17/fw/variant.rs::PointerLayout`, `g17/fw/queue.rs`; do not copy another firmware build's offsets | | Core topology / mapping ownership | One six-core cluster; two firmware roots and fabric bridge | Qualified two-cluster, 20-core layout; reserved firmware tables, one root and no fabric bridge | Same runtime behavior | `hw/t6040.rs`, `hw/t8140.rs`, `g17/mod.rs`; other fused core counts are still refused, and this audit does not expand that admission | @@ -83,7 +84,7 @@ an offset, voltage, descriptor ABI, or Linux residency measurement. | Gated path | Exact facts needed for T6040/25G83 | Current evidence and disposition | | --- | --- | --- | -| Sustained top clock | DT OPP count, both voltage lanes, hardware A/B selector maps, selected logical-state limit; independently decoded achieved frequency or residency, counter units/timebase/wrap, request versus achieved-state semantics and limiting reason under sustained load | Per-chip DT handoff and maps are already mandatory; both maps select the final raw OPP. `max_frequency_khz` is an advertised maximum, not achieved telemetry. G17 `stats_snapshot()` returns None; ring drain alone proves no residency. Keep voltages/maps unchanged and require a measured Linux load trace. | +| Sustained top clock | DT OPP count, both voltage lanes, hardware A/B selector maps, selected logical-state limit; independently decoded achieved frequency or residency, counter units/timebase/wrap, request versus achieved-state semantics and limiting reason under sustained load | Per-chip DT handoff and maps are already mandatory; both maps select the final raw OPP. `max_frequency_khz` is an advertised maximum, not achieved telemetry. G17 `stats_snapshot()` returns None; g4 raw `firmware_reports` and `asahi_g17_report` preserve payloads but establish no decoded residency. Keep voltages/maps unchanged and require a measured Linux load trace. | | Raw versus logical state count | Meaning of the ADT count properties for this firmware, both SGX map banks and number of entries actually consumed by the 25G83 power tables | Public `HwData25G83`, calibration validation and loader handoff select eleven logical states from sixteen raw OPPs. Do not resize the firmware structure from the ADT property name or discard its unsorted OPPs. Compare actual top selection and achieved clock. | | DVFS ramp / idle / fast-die | 25G83 meaning and units of ramp/effort controls, idle/off delays, temperature limits and hysteresis; PMP/AP/CLPC request ownership, achieved floors and release under idle and load | Existing shared submission/idle policy, DT suppliers and T6040 power-state graph are enabled. Power-state presence is not proof of load tracking. Require live requests, actual floor release and thermal traces; opaque power words are not tunable parameters. | | Recovery slot attribution | Exact 25G83 slot-key storage range, lifetime, cache policy and slot stride; key QID/stamp bit fields; slot-mask meanings, progress monitor semantics and association with an actual started render | `recovery_slot_keys=None`; never copy T8140's physical address. Preserve firmware blame. Progress-view reads no longer depend on knowing the key address; bounded diagnostics report unknown ownership explicitly. | @@ -104,6 +105,39 @@ and page-fault fallback. Diagnostic counters explicitly identify lifetime stampless totals; they are not a count of commands retired by one restart. +## g4 shared runtime ports and observability + +The operator has selected g3 as the hardware baseline. This follow-on retains +its CLPC/PMP ports, DT idle hold and recovery-generation release guard. +Individual hardware records remain in the private qualification workspace; +no raw lab capture is a public implementation source. + +| Gap | Public source behavior | T6040 g4 disposition | Qualification | +| --- | --- | --- | --- | +| #504 fragment notification | `de0b22a8370e` selects a render pair on its fragment notification; final retirement still requires both stored stamps | Included for both firmware graphs; KUnit covers each mask bank and unrelated queues | Render-light latency/IRQ rate, growth/partial renders, recovery and CTS | +| #504 tiling first-kick | `5073eac15192` changes the word only for the 26A428 register program | Included unchanged; 25G83 retains 1 on every kick | No T6040 notification-halving claim; requires 25G83 descriptor/notification facts and controlled qualification | +| #496 compute-fault witness | `9a76756bf2d7` uses event 7 words 3/4 as QID/stamp on Neo, with consumed-ring guard | Imported prerequisite, but the existing firmware graph keeps 25G83 event 7 unknown | Exact 25G83 layout, queue identity, stamp semantics and DMA/ring visibility must be established before it can release memory | +| #505 closed-context teardown | `078d744a260c` kills before freeing executed unstamped render graphs, retains firmware-linked work storage, and reuses killed pairs last with QID capacity | Shared safety/retention paths included; compute-fault-driven kill depends on the graph-qualified #496 witness | Closed mixed compute/render faults, acknowledgement ordering, memory pressure and slot/QID reuse; does not claim to cure a live render's first fault | +| First fault | Reason and blame alone cannot identify engine progress | At halted state 1, before acknowledgement, g4 logs the blamed pair's stored stamps, free-list balance and up to four commands' pass/cursor snapshots; borrowed pairs are explicitly unavailable | Correlate exact CTS operation and original error with raw event report and snapshots; do not label reason 2 as page fault without semantics | +| Raw firmware statistics | Complete 18-word statistics records already have checked ring geometry | Read-only `firmware_reports` exposes last full sample, count and host monotonic time for each role/type. Static-key `asahi_g17_report` copies trace/statistics/event words before slot return | Match payloads to independently decoded achieved-clock/power/residency and freshness/units; legacy statistics tags are a different ABI | +| Idle memory floor | PMGR graphics power is a supplier lifetime, not itself an idle-work witness | Existing policy retained; CLPC status now exposes the sampled graphics status/active input and read-error count alongside CPU requests and rail votes | Long idle with no GPU clients, then load and release, must distinguish GPU power, CPU demand, failed reads and other PMC agents; never infer idle from an open client alone | + +Remaining exact facts for telemetry are the 25G83/26A428 tag-to-layout map, +field width/signedness/endianness, sample/timebase/wrap and freshness, +power units/averaging interval, logical/raw/cluster pstate selection, requested +versus achieved-state semantics, and limiting/thermal reason meanings. Raw +samples and the DT maximum are not MHz. Reads issue no firmware request and +never touch gated core registers. `agx_stats` remains unsupported for decoded +G17 data until those semantics are established. + +For the two deterministic CTS cases, retain the first failing operation before +cleanup, full unnormalized recovery logs, raw event trace, and pre-ack render +snapshots. The large-buffer and extreme-point-size cases can stress tiling, +but neither case name proves a TLB, timeout or TVB cause. No timeout, voltage, +feature-enable or buffer-size guess is included in g4. Multi-queue sync needs +its publish/GPU-span/event/fence timeline; #504's compute control does not +promise to halve that path's latency. + ## Source and validation boundaries The implementation references are the licensed public Aurora/Asahi kernel and From 32f7efefcb39734a758d8dbbd3708099f288c067 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Tue, 27 Oct 2026 17:35:32 +1100 Subject: [PATCH 109/184] soc: apple: drive T6040 GPU memory demand from publication and continuous idle T6040 keeps the GFX supplier powered for the firmware's lifetime, as its PMGR DT documents. Using that supplier as CLPC demand therefore pins DCS 5 / fabric 2 even after the GPU cores gate. Neo's activity-following supplier is not a valid T6040 idle witness. Add a DT-selected CLPC demand consumer. Every actual GPU publication asserts it, including off-lock direct publication. The existing race-checked 16 ms continuous-idle effort clear releases it; quarantined work does not count as execution. CLPC retains its existing 32 ms hold and CPU arbitration. Firmware shutdown and consumer destruction clear the request. An independent refcounted request allocation is safe across provider removal, and a device link orders the supplier. The hot path uses only a leaf spinlock, with no sleeping call, allocation or GPU callback. Missing requested providers defer probe. Expose last requested floor, reason bits and host timestamps alongside actual PMC readback, and expose safe core-power plus last publication time in the read-only G17 firmware_reports view. Generic runtime PM remains distinct from firmware core gating: do not relabel unsupported generic PM as active/suspended. Keep Neo's existing PMGR input and all voltages, floors and delays unchanged. Assisted-by: LLM Signed-off-by: Ryan Murray --- .../devicetree/bindings/gpu/apple,agx.yaml | 7 + arch/arm64/boot/dts/apple/t6040.dtsi | 3 +- drivers/gpu/drm/asahi/Makefile | 2 +- drivers/gpu/drm/asahi/g17/clpc.c | 16 ++ drivers/gpu/drm/asahi/g17/clpc.h | 9 + drivers/gpu/drm/asahi/g17/mod.rs | 4 +- drivers/gpu/drm/asahi/g17/port.rs | 19 ++- drivers/gpu/drm/asahi/g17/power.rs | 40 +++++ drivers/gpu/drm/asahi/g17/reports.rs | 5 + drivers/soc/apple/clpc.c | 158 +++++++++++++++++- include/linux/soc/apple/clpc.h | 24 +++ 11 files changed, 279 insertions(+), 8 deletions(-) create mode 100644 drivers/gpu/drm/asahi/g17/clpc.c create mode 100644 drivers/gpu/drm/asahi/g17/clpc.h create mode 100644 include/linux/soc/apple/clpc.h diff --git a/Documentation/devicetree/bindings/gpu/apple,agx.yaml b/Documentation/devicetree/bindings/gpu/apple,agx.yaml index 145e277d6be4d..f5b1afd7d7dd3 100644 --- a/Documentation/devicetree/bindings/gpu/apple,agx.yaml +++ b/Documentation/devicetree/bindings/gpu/apple,agx.yaml @@ -72,6 +72,13 @@ properties: - enum: [hw-cal-b, fw-text, l2-pagetables] - enum: [globals, fw-data, fw-text] + apple,clpc: + $ref: /schemas/types.yaml#/definitions/phandle + description: | + Host performance controller receiving GPU submission and idle demand. + GFX supplier power cannot represent GPU work on firmware that keeps + its coprocessor domain powered while the cores are idle. + apple,idle-off-delay-ms: minimum: 2 maximum: 1000 diff --git a/arch/arm64/boot/dts/apple/t6040.dtsi b/arch/arm64/boot/dts/apple/t6040.dtsi index 1cbc2a97a51c1..01486e0f5ac65 100644 --- a/arch/arm64/boot/dts/apple/t6040.dtsi +++ b/arch/arm64/boot/dts/apple/t6040.dtsi @@ -617,6 +617,7 @@ reg-names = "asc", "sgx"; mboxes = <&gfx_mbox>; power-domains = <&pmp_gfx>; + apple,clpc = <&clpc>; status = "disabled"; }; @@ -1025,7 +1026,7 @@ clpc: performance-controller@1 { compatible = "apple,t6041-clpc"; reg = <1>; - apple,gpu-power-state = <&ps_gfx>; + }; }; diff --git a/drivers/gpu/drm/asahi/Makefile b/drivers/gpu/drm/asahi/Makefile index db46090abd943..fd8421f27590d 100644 --- a/drivers/gpu/drm/asahi/Makefile +++ b/drivers/gpu/drm/asahi/Makefile @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_DRM_ASAHI) += asahi.o -asahi-y := asahi_core.o fwid.o sysfs.o g17/sync_input.o g17/sync_output.o g17/sync_scope.o g17/trace.o +asahi-y := asahi_core.o fwid.o sysfs.o g17/sync_input.o g17/sync_output.o g17/sync_scope.o g17/trace.o g17/clpc.o CFLAGS_g17/trace.o := -I$(src)/g17 asahi-$(CONFIG_DRM_ASAHI_G15) += g15/glue.o diff --git a/drivers/gpu/drm/asahi/g17/clpc.c b/drivers/gpu/drm/asahi/g17/clpc.c new file mode 100644 index 0000000000000..b8ae1242cd8aa --- /dev/null +++ b/drivers/gpu/drm/asahi/g17/clpc.c @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +#include +#include "clpc.h" + +int asahi_clpc_gpu_get(struct device *dev, struct apple_clpc_gpu **out) +{ + return apple_clpc_gpu_get(dev, out); +} +void asahi_clpc_gpu_set_active(struct apple_clpc_gpu *gpu, bool active) +{ + apple_clpc_gpu_set_active(gpu, active); +} +void asahi_clpc_gpu_put(struct apple_clpc_gpu *gpu) +{ + apple_clpc_gpu_put(gpu); +} diff --git a/drivers/gpu/drm/asahi/g17/clpc.h b/drivers/gpu/drm/asahi/g17/clpc.h new file mode 100644 index 0000000000000..3fc6f75473454 --- /dev/null +++ b/drivers/gpu/drm/asahi/g17/clpc.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0-only OR MIT */ +#ifndef _ASAHI_G17_CLPC_H +#define _ASAHI_G17_CLPC_H +struct device; +struct apple_clpc_gpu; +int asahi_clpc_gpu_get(struct device *dev, struct apple_clpc_gpu **out); +void asahi_clpc_gpu_set_active(struct apple_clpc_gpu *gpu, bool active); +void asahi_clpc_gpu_put(struct apple_clpc_gpu *gpu); +#endif diff --git a/drivers/gpu/drm/asahi/g17/mod.rs b/drivers/gpu/drm/asahi/g17/mod.rs index d7a42c5c82bdb..a3c83bca59b21 100644 --- a/drivers/gpu/drm/asahi/g17/mod.rs +++ b/drivers/gpu/drm/asahi/g17/mod.rs @@ -644,6 +644,7 @@ struct Firmware { impl Drop for Firmware { fn drop(&mut self) { + self.port.set_effort_full(false); if let Some(secondary) = self.secondary.as_mut() { secondary.stop(); } @@ -694,6 +695,7 @@ impl Firmware { /// Called at actual submission publication, not while a queued job is merely being built. fn note_submission(&mut self) -> Result { + self.port.note_gpu_activity(); self.effort.submit(); self.apply_effort(power::FULL_EFFORT) } @@ -1361,7 +1363,7 @@ impl Gpu { secondary, primary, bridge, - port: port::Port::new()?, + port: port::Port::new(power::Demand::new(pdev.as_ref().as_raw())?)?, seen_submissions: 0, rings: channel::Rings::new()?, recovery: recovery::State::new(), diff --git a/drivers/gpu/drm/asahi/g17/port.rs b/drivers/gpu/drm/asahi/g17/port.rs index db043ef9d6f91..243fbfce07f9e 100644 --- a/drivers/gpu/drm/asahi/g17/port.rs +++ b/drivers/gpu/drm/asahi/g17/port.rs @@ -105,6 +105,8 @@ pub(super) struct Port { compute_announced: [AtomicU64; PRIORITIES], /// Full firmware effort is applied; cleared before an idle clear rechecks `submissions`. effort_full: AtomicBool, + demand: super::power::Demand, + last_submission_ns: AtomicU64, submissions: AtomicU64, } @@ -142,7 +144,7 @@ impl Drop for OuterClaim<'_> { } impl Port { - pub(super) fn new() -> Result> { + pub(super) fn new(demand: super::power::Demand) -> Result> { Arc::pin_init( pin_init!(Self { views: SetOnce::new(), @@ -155,6 +157,8 @@ impl Port { compute_written: [const { AtomicU64::new(0) }; PRIORITIES], compute_announced: [const { AtomicU64::new(0) }; PRIORITIES], effort_full: AtomicBool::new(false), + demand, + last_submission_ns: AtomicU64::new(0), submissions: AtomicU64::new(0), }), GFP_KERNEL, @@ -453,6 +457,7 @@ impl Port { /// clear sees this submission or this submission sees the flag withdrawn. pub(super) fn note_submission(&self) -> bool { self.submissions.fetch_add(1, Ordering::SeqCst); + self.note_gpu_activity(); self.effort_full.load(Ordering::SeqCst) } @@ -462,5 +467,17 @@ impl Port { pub(super) fn set_effort_full(&self, full: bool) { self.effort_full.store(full, Ordering::SeqCst); + self.demand.set_active(full); + } + + pub(super) fn note_gpu_activity(&self) { + self.last_submission_ns.store(super::now_ns(), Ordering::Relaxed); + self.demand.set_active(true); + } + + pub(super) fn write_power(&self, f: &mut kernel::fmt::Formatter<'_>) -> kernel::fmt::Result { + writeln!(f, "effort_full={} direct_submissions={} last_submission_ns={}", + self.effort_full.load(Ordering::SeqCst), self.submissions(), + self.last_submission_ns.load(Ordering::Relaxed)) } } diff --git a/drivers/gpu/drm/asahi/g17/power.rs b/drivers/gpu/drm/asahi/g17/power.rs index bbd10b204edd7..075d2829e4243 100644 --- a/drivers/gpu/drm/asahi/g17/power.rs +++ b/drivers/gpu/drm/asahi/g17/power.rs @@ -300,3 +300,43 @@ mod tests { assert_eq!(hold.step(1, false, 2, FIRMWARE_IDLE_OFF_MS), HoldAction::Release); } } + +unsafe extern "C" { + fn asahi_clpc_gpu_get(dev: *mut kernel::bindings::device, out: *mut *mut core::ffi::c_void) -> core::ffi::c_int; + fn asahi_clpc_gpu_set_active(gpu: *mut core::ffi::c_void, active: bool); + fn asahi_clpc_gpu_put(gpu: *mut core::ffi::c_void); +} + +/// DT-selected memory-demand provider. Its independent allocation outlives a +/// detached provider; a device link orders supplier removal and suspend. +pub(super) struct Demand(*mut core::ffi::c_void); + +// SAFETY: the provider's leaf spinlock serializes all access; no GPU callback. +unsafe impl Send for Demand {} +// SAFETY: shared access only changes that locked request, never its lifetime. +unsafe impl Sync for Demand {} + +impl Demand { + pub(super) fn new(dev: *mut kernel::bindings::device) -> kernel::error::Result { + let mut out = core::ptr::null_mut(); + // SAFETY: live platform device and writable result; C returns an owned reference. + let ret = unsafe { asahi_clpc_gpu_get(dev, &mut out) }; + if ret < 0 { + Err(kernel::error::Error::from_errno(ret)) + } else { + Ok(Self(out)) + } + } + + pub(super) fn set_active(&self, active: bool) { + // SAFETY: the retained reference is live; NULL denotes no DT provider. + unsafe { asahi_clpc_gpu_set_active(self.0, active) }; + } +} + +impl Drop for Demand { + fn drop(&mut self) { + // SAFETY: releases exactly the reference returned by get, clearing demand first. + unsafe { asahi_clpc_gpu_put(self.0) }; + } +} diff --git a/drivers/gpu/drm/asahi/g17/reports.rs b/drivers/gpu/drm/asahi/g17/reports.rs index 7e1afef70cad2..62ed791322ad9 100644 --- a/drivers/gpu/drm/asahi/g17/reports.rs +++ b/drivers/gpu/drm/asahi/g17/reports.rs @@ -57,6 +57,11 @@ impl kernel::debugfs::Writer for View { let state = self.0.state.lock(); let Some(firmware) = state.as_deref() else { return writeln!(f, "online=0"); }; writeln!(f, "online=1 graph={:?} sampled_ns={}", self.0.variant.graph, super::now_ns())?; + let core_power = firmware.bridge.regs.g17_core_power(); + writeln!(f, "power_management=firmware core_power={:#x} cores_powered={} idle_hold_ms={}", + core_power, core_power & 0xf != 0, + super::power::IDLE_HOLD_MS.load(core::sync::atomic::Ordering::Relaxed))?; + firmware.port.write_power(f)?; firmware.rings.write_reports(f) } } diff --git a/drivers/soc/apple/clpc.c b/drivers/soc/apple/clpc.c index 1acfcc0b598dd..53318ec78ced8 100644 --- a/drivers/soc/apple/clpc.c +++ b/drivers/soc/apple/clpc.c @@ -21,6 +21,12 @@ */ #include +#include +#include +#include +#include +#include +#include #include #include #include @@ -78,6 +84,15 @@ struct apple_clpc_vote { unsigned long last_demand; }; +/* Independent lifetime: a late consumer put never dereferences a removed provider. */ +struct apple_clpc_gpu { + struct kref ref; + spinlock_t lock; + bool active; + u64 updated_ns; + u64 sequence; +}; + struct apple_clpc { struct device *dev; struct device *pmc; @@ -100,12 +115,102 @@ struct apple_clpc { unsigned long update_errors; struct dentry *debugfs; struct regmap *gpu_pmgr; + struct apple_clpc_gpu *gpu; + u32 last_floor; + u32 last_reason; + u64 last_vote_ns; u32 gpu_offset; u32 gpu_status; bool gpu_active; unsigned long gpu_read_errors; }; +static void apple_clpc_gpu_free(struct kref *ref) +{ + kfree(container_of(ref, struct apple_clpc_gpu, ref)); +} + +void apple_clpc_gpu_set_active(struct apple_clpc_gpu *gpu, bool active) +{ + unsigned long flags; + + if (!gpu) + return; + spin_lock_irqsave(&gpu->lock, flags); + gpu->active = active; + gpu->updated_ns = ktime_get_mono_fast_ns(); + gpu->sequence++; + spin_unlock_irqrestore(&gpu->lock, flags); +} +EXPORT_SYMBOL_GPL(apple_clpc_gpu_set_active); + +void apple_clpc_gpu_put(struct apple_clpc_gpu *gpu) +{ + if (gpu) { + apple_clpc_gpu_set_active(gpu, false); + kref_put(&gpu->ref, apple_clpc_gpu_free); + } +} +EXPORT_SYMBOL_GPL(apple_clpc_gpu_put); + +int apple_clpc_gpu_get(struct device *dev, struct apple_clpc_gpu **out) +{ + struct device_node *np; + struct platform_device *provider; + struct apple_clpc *clpc; + struct device_link *link; + int ret = -EPROBE_DEFER; + + *out = NULL; + np = of_parse_phandle(dev->of_node, "apple,clpc", 0); + if (!np) + return of_find_property(dev->of_node, "apple,clpc", NULL) ? -EINVAL : 0; + if (!of_device_is_compatible(np, "apple,t6040-clpc")) { + of_node_put(np); + return -EINVAL; + } + provider = of_find_device_by_node(np); + of_node_put(np); + if (!provider) + return ret; + link = device_link_add(dev, &provider->dev, DL_FLAG_AUTOREMOVE_CONSUMER); + if (!link) { + put_device(&provider->dev); + return -ENOMEM; + } + device_lock(&provider->dev); + clpc = device_is_bound(&provider->dev) ? dev_get_drvdata(&provider->dev) : NULL; + if (clpc && clpc->gpu && refcount_read(&clpc->gpu->ref.refcount) == 1) { + kref_get(&clpc->gpu->ref); + *out = clpc->gpu; + ret = 0; + } + device_unlock(&provider->dev); + put_device(&provider->dev); + return ret; +} +EXPORT_SYMBOL_GPL(apple_clpc_gpu_get); + +static bool apple_clpc_gpu_active(struct apple_clpc_gpu *gpu, u64 *updated, u64 *sequence) +{ + unsigned long flags; + bool active; + + spin_lock_irqsave(&gpu->lock, flags); + active = gpu->active; + *updated = gpu->updated_ns; + *sequence = gpu->sequence; + spin_unlock_irqrestore(&gpu->lock, flags); + return active; +} + +static void apple_clpc_gpu_owner_put(void *data) +{ + struct apple_clpc_gpu *gpu = data; + + kref_put(&gpu->ref, apple_clpc_gpu_free); +} + /* cpufreq policies can appear after probe; pick up new ones as they do. */ static void apple_clpc_find_clusters(struct apple_clpc *clpc) { @@ -236,6 +341,7 @@ static void apple_clpc_sample(struct apple_clpc *clpc) unsigned int state[APPLE_CLPC_RAILS]; unsigned long now = jiffies; unsigned int i, rail; + u32 reason = 0, floor; clpc->samples++; if (!cpumask_equal(clpc->covered, cpu_possible_mask) && @@ -251,6 +357,18 @@ static void apple_clpc_sample(struct apple_clpc *clpc) for (rail = 0; rail < APPLE_CLPC_RAILS; rail++) demand[rail] = max(demand[rail], cl->request[rail]); } + for (rail = 0; rail < APPLE_CLPC_RAILS; rail++) + if (demand[rail]) + reason |= 1; + if (clpc->gpu) { + u64 updated, sequence; + + if (apple_clpc_gpu_active(clpc->gpu, &updated, &sequence)) { + reason |= 2; + for (rail = 0; rail < APPLE_CLPC_RAILS; rail++) + demand[rail] = clpc->hw->map[rail][APPLE_CLPC_LEVELS - 1]; + } + } if (clpc->gpu_pmgr) { u32 status; @@ -259,11 +377,14 @@ static void apple_clpc_sample(struct apple_clpc *clpc) */ if (regmap_read(clpc->gpu_pmgr, clpc->gpu_offset, &status)) { clpc->gpu_read_errors++; + reason |= 8; clpc->gpu_active = true; } else { clpc->gpu_status = status; clpc->gpu_active = (status & GENMASK(7, 4)) == GENMASK(7, 4); } + if (clpc->gpu_active) + reason |= 4; if (clpc->gpu_active) for (rail = 0; rail < APPLE_CLPC_RAILS; rail++) demand[rail] = clpc->hw->map[rail][APPLE_CLPC_LEVELS - 1]; @@ -273,8 +394,13 @@ static void apple_clpc_sample(struct apple_clpc *clpc) state[rail] = apple_clpc_vote(&clpc->vote[rail], demand[rail], now); - if (apple_pmc_floor_update(clpc->pmc, clpc->agent, clpc->mask, - apple_clpc_floor(clpc, state))) + floor = apple_clpc_floor(clpc, state); + if (!reason && floor) + reason = 16; + clpc->last_floor = floor; + clpc->last_reason = reason; + clpc->last_vote_ns = ktime_get_mono_fast_ns(); + if (apple_pmc_floor_update(clpc->pmc, clpc->agent, clpc->mask, floor)) clpc->update_errors++; apple_clpc_queue(clpc, !apple_clpc_floor(clpc, state)); @@ -308,6 +434,16 @@ static int apple_clpc_status_show(struct seq_file *s, void *unused) clpc->hw->rail[rail]), clpc->hw->map[rail][0], clpc->hw->map[rail][1], clpc->hw->map[rail][2], clpc->hw->map[rail][3]); + seq_printf(s, "last_vote: %#x reason_bits %#x sampled_ns %llu (cpu=1 host_gpu=2 pmgr_gpu=4 read_error=8 hold=16)\n", + READ_ONCE(clpc->last_floor), READ_ONCE(clpc->last_reason), + READ_ONCE(clpc->last_vote_ns)); + if (clpc->gpu) { + u64 updated, sequence; + bool active = apple_clpc_gpu_active(clpc->gpu, &updated, &sequence); + + seq_printf(s, "host_gpu: active %u request_ns %llu sequence %llu\n", + active, updated, sequence); + } seq_printf(s, "gpu: present %u active %u status %#x read_errors %lu\n", !!clpc->gpu_pmgr, READ_ONCE(clpc->gpu_active), READ_ONCE(clpc->gpu_status), READ_ONCE(clpc->gpu_read_errors)); @@ -377,6 +513,19 @@ static int apple_clpc_probe(struct platform_device *pdev) clpc->dev = dev; clpc->pmc = dev->parent; clpc->hw = of_device_get_match_data(dev); + /* T6040 firmware keeps GFX powered for its whole lifetime. A separate + * host publication/idle input, not that supplier state, drives demand. + */ + if (of_device_is_compatible(dev->of_node, "apple,t6040-clpc")) { + clpc->gpu = kzalloc_obj(*clpc->gpu); + if (!clpc->gpu) + return -ENOMEM; + kref_init(&clpc->gpu->ref); + spin_lock_init(&clpc->gpu->lock); + ret = devm_add_action_or_reset(dev, apple_clpc_gpu_owner_put, clpc->gpu); + if (ret) + return ret; + } if (clpc->hw->gpu_demand) { struct device_node *gpu = of_parse_phandle(dev->of_node, "apple,gpu-power-state", 0); @@ -521,7 +670,8 @@ static const struct apple_clpc_hw apple_clpc_hw_t8140 = { * a single busy sample raised the floor about 60 % of the time, from short * bursts of background work; three in a row bring that to about 11 %. * GPU demand bypasses the CPU streak and uses the same qualified DCS 5 / - * fabric 2 states until the graphics state powers off and HOLD_MS expires. + * fabric 2 states until publication/in-flight demand ends and HOLD_MS expires. + * GFX supplier power cannot drive this input: firmware keeps it always on. */ static const struct apple_clpc_hw apple_clpc_hw_t6040 = { .rail = { [APPLE_CLPC_DCS] = 0, [APPLE_CLPC_SOC] = 1 }, @@ -530,7 +680,7 @@ static const struct apple_clpc_hw apple_clpc_hw_t6040 = { [APPLE_CLPC_SOC] = { 0, 1, 2, 2 }, }, .sustain = 3, - .gpu_demand = true, + .gpu_demand = false, }; static const struct of_device_id apple_clpc_of_match[] = { diff --git a/include/linux/soc/apple/clpc.h b/include/linux/soc/apple/clpc.h new file mode 100644 index 0000000000000..37b2cfce5594a --- /dev/null +++ b/include/linux/soc/apple/clpc.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0-only OR MIT */ +#ifndef _LINUX_SOC_APPLE_CLPC_H +#define _LINUX_SOC_APPLE_CLPC_H + +#include +#include +#include + +struct apple_clpc_gpu; + +#if IS_REACHABLE(CONFIG_APPLE_CLPC) +int apple_clpc_gpu_get(struct device *dev, struct apple_clpc_gpu **out); +void apple_clpc_gpu_set_active(struct apple_clpc_gpu *gpu, bool active); +void apple_clpc_gpu_put(struct apple_clpc_gpu *gpu); +#else +static inline int apple_clpc_gpu_get(struct device *dev, struct apple_clpc_gpu **out) +{ + *out = NULL; + return device_property_present(dev, "apple,clpc") ? -ENODEV : 0; +} +static inline void apple_clpc_gpu_set_active(struct apple_clpc_gpu *gpu, bool active) {} +static inline void apple_clpc_gpu_put(struct apple_clpc_gpu *gpu) {} +#endif +#endif From 07a2cd1bc82b22ad1781342c1aa49e2b6a66817f Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Tue, 27 Oct 2026 17:36:05 +1100 Subject: [PATCH 110/184] soc: apple: qualify the T6040 host-demand input and release lifetime Use the existing t6041-clpc compatible selected by the T6040 DT, rather than a nonexistent compatible. Extend the production CLPC host fixture with an always-powered supplier and explicit GPU activity/idle input, reason/timestamp checks, CPU arbitration and negative release controls. KUnit exercises the real request's locking/reference lifetime and clears on consumer removal, including a late put after provider removal. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/soc/apple/clpc.c | 59 +++++++++++++++++++++++++++- tools/asahi/clpc-gpu-demand-tests.py | 39 ++++++++++++++++-- 2 files changed, 92 insertions(+), 6 deletions(-) diff --git a/drivers/soc/apple/clpc.c b/drivers/soc/apple/clpc.c index 53318ec78ced8..3491030c670dd 100644 --- a/drivers/soc/apple/clpc.c +++ b/drivers/soc/apple/clpc.c @@ -165,7 +165,7 @@ int apple_clpc_gpu_get(struct device *dev, struct apple_clpc_gpu **out) np = of_parse_phandle(dev->of_node, "apple,clpc", 0); if (!np) return of_find_property(dev->of_node, "apple,clpc", NULL) ? -EINVAL : 0; - if (!of_device_is_compatible(np, "apple,t6040-clpc")) { + if (!of_device_is_compatible(np, "apple,t6041-clpc")) { of_node_put(np); return -EINVAL; } @@ -516,7 +516,7 @@ static int apple_clpc_probe(struct platform_device *pdev) /* T6040 firmware keeps GFX powered for its whole lifetime. A separate * host publication/idle input, not that supplier state, drives demand. */ - if (of_device_is_compatible(dev->of_node, "apple,t6040-clpc")) { + if (of_device_is_compatible(dev->of_node, "apple,t6041-clpc")) { clpc->gpu = kzalloc_obj(*clpc->gpu); if (!clpc->gpu) return -ENOMEM; @@ -683,6 +683,61 @@ static const struct apple_clpc_hw apple_clpc_hw_t6040 = { .gpu_demand = false, }; +#if IS_BUILTIN(CONFIG_KUNIT) && !defined(MODULE) +#include + +static void apple_clpc_gpu_request_lifetime_test(struct kunit *test) +{ + struct apple_clpc_gpu *gpu = kzalloc_obj(*gpu); + u64 updated, sequence; + + KUNIT_ASSERT_NOT_NULL(test, gpu); + kref_init(&gpu->ref); + spin_lock_init(&gpu->lock); + KUNIT_EXPECT_FALSE(test, apple_clpc_gpu_active(gpu, &updated, &sequence)); + KUNIT_EXPECT_EQ(test, sequence, 0ull); + /* Provider and one consumer own independent references. */ + kref_get(&gpu->ref); + apple_clpc_gpu_set_active(gpu, true); + KUNIT_EXPECT_TRUE(test, apple_clpc_gpu_active(gpu, &updated, &sequence)); + KUNIT_EXPECT_GT(test, updated, 0ull); + KUNIT_EXPECT_EQ(test, sequence, 1ull); + apple_clpc_gpu_put(gpu); + KUNIT_EXPECT_FALSE(test, apple_clpc_gpu_active(gpu, &updated, &sequence)); + KUNIT_EXPECT_EQ(test, sequence, 2ull); + kref_put(&gpu->ref, apple_clpc_gpu_free); +} + +static void apple_clpc_gpu_provider_removal_test(struct kunit *test) +{ + struct apple_clpc_gpu *gpu = kzalloc_obj(*gpu); + u64 updated, sequence; + + KUNIT_ASSERT_NOT_NULL(test, gpu); + kref_init(&gpu->ref); + spin_lock_init(&gpu->lock); + kref_get(&gpu->ref); + /* Provider removal must not leave a late consumer with freed storage. */ + kref_put(&gpu->ref, apple_clpc_gpu_free); + apple_clpc_gpu_set_active(gpu, true); + KUNIT_EXPECT_TRUE(test, apple_clpc_gpu_active(gpu, &updated, &sequence)); + apple_clpc_gpu_set_active(gpu, false); + KUNIT_EXPECT_FALSE(test, apple_clpc_gpu_active(gpu, &updated, &sequence)); + apple_clpc_gpu_put(gpu); +} + +static struct kunit_case apple_clpc_cases[] = { + KUNIT_CASE(apple_clpc_gpu_request_lifetime_test), + KUNIT_CASE(apple_clpc_gpu_provider_removal_test), + {} +}; +static struct kunit_suite apple_clpc_suite = { + .name = "apple-clpc-gpu", + .test_cases = apple_clpc_cases, +}; +kunit_test_suite(apple_clpc_suite); +#endif + static const struct of_device_id apple_clpc_of_match[] = { { .compatible = "apple,t8132-clpc", .data = &apple_clpc_hw_t8132 }, { .compatible = "apple,t8140-clpc", .data = &apple_clpc_hw_t8140 }, diff --git a/tools/asahi/clpc-gpu-demand-tests.py b/tools/asahi/clpc-gpu-demand-tests.py index b39b0848ed74a..379de4946bd19 100644 --- a/tools/asahi/clpc-gpu-demand-tests.py +++ b/tools/asahi/clpc-gpu-demand-tests.py @@ -39,6 +39,7 @@ def main(): #include typedef uint8_t u8; typedef uint32_t u32; +typedef uint64_t u64; #define APPLE_CLPC_LEVELS 4 #define APPLE_CLPC_HOLD_MS 32 #define HZ 1000 @@ -49,6 +50,7 @@ def main(): #define msecs_to_jiffies(ms) (ms) enum apple_clpc_rail { APPLE_CLPC_DCS, APPLE_CLPC_SOC, APPLE_CLPC_RAILS }; ''' + types + r''' +struct apple_clpc_gpu { bool active; }; struct apple_clpc_cluster { unsigned int request[APPLE_CLPC_RAILS]; }; struct apple_clpc { const struct apple_clpc_hw *hw; @@ -57,9 +59,15 @@ def main(): unsigned int nr_clusters, agent; unsigned long samples, next_scan, update_errors; void *covered, *pmc, *gpu_pmgr; - u32 gpu_offset, mask; + u32 gpu_offset, mask, gpu_status, last_floor, last_reason; + bool gpu_active; + unsigned long gpu_read_errors; + u64 last_vote_ns; + struct apple_clpc_gpu *gpu; }; static unsigned long jiffies; +static u64 ktime_get_mono_fast_ns(void) { return jiffies * 1000000ull; } +static bool apple_clpc_gpu_active(struct apple_clpc_gpu *g, u64 *u, u64 *s) { *u=jiffies; *s=1; return g->active; } static void *cpu_possible_mask; static u32 pmgr_status, published; static int read_error, vote_error; @@ -76,6 +84,7 @@ def main(): static void apple_clpc_queue(struct apple_clpc *c, bool idle) { (void)c; queued_idle=idle; } ''' + tables + '\n' + methods + r''' static void check(const struct apple_clpc_hw *hw, u32 top) { + assert(hw->gpu_demand); struct apple_clpc c={.hw=hw,.agent=1,.mask=3,.gpu_pmgr=hw->gpu_demand ? (void *)1 : NULL}; jiffies=0; read_error=vote_error=0; pmgr_status=0; published=~0u; apple_clpc_sample(&c); assert(published==0 && queued_idle); @@ -98,10 +107,31 @@ def main(): vote_error=-EIO; apple_clpc_sample(&c); assert(c.update_errors==1); } int main(void) { - check(&apple_clpc_hw_t6040,0x25); + // T6040's supplier is always powered: it must NEVER substitute for work. + struct apple_clpc_gpu gpu={0}; + struct apple_clpc c={.hw=&apple_clpc_hw_t6040,.agent=1,.mask=3,.gpu=&gpu}; + assert(!c.hw->gpu_demand); + jiffies=0; read_error=vote_error=0; pmgr_status=0xf0; published=~0u; + apple_clpc_sample(&c); assert(published==0 && queued_idle && c.last_reason==0); + gpu.active=true; jiffies=100; apple_clpc_sample(&c); + assert(published==0x25 && c.last_reason==2 && c.last_floor==0x25); + assert(c.last_vote_ns==100000000ull); + // Core/firmware supplier is still powered, but continuous idle ends demand. + gpu.active=false; jiffies=131; apple_clpc_sample(&c); + assert(published==0x25 && c.last_reason==16); + jiffies=132; apple_clpc_sample(&c); + assert(published==0 && queued_idle && c.last_reason==0); + // A new submission reasserts demand; independent CPU requests survive release. + c.nr_clusters=1; c.clusters[0].request[0]=2; c.clusters[0].request[1]=1; + gpu.active=true; jiffies=200; apple_clpc_sample(&c); + assert(published==0x25 && c.last_reason==3); + gpu.active=false; jiffies=232; apple_clpc_sample(&c); + assert(published==0x12 && c.last_reason==1); + c.clusters[0].request[0]=c.clusters[0].request[1]=0; + jiffies=264; apple_clpc_sample(&c); assert(published==0); check(&apple_clpc_hw_t8140,0x34); check(&apple_clpc_hw_t8132,0x35); - puts("PASS: T6040 5/2, T8140 4/3 and T8132 5/3 GPU floors; 32 ms idle release; partial state, read failure, CPU demand and vote-error controls"); + puts("PASS: T6040 5/2, T8140 4/3 and T8132 5/3 GPU floors; host-idle release despite always-on GFX, reason/timestamp, 32 ms hold, legacy PMGR/read-error and CPU controls"); } ''' code = code.replace('#define APPLE_PMC_RAIL_SHIFT(r) ((r)*8)', rail_shift) @@ -109,7 +139,8 @@ def main(): root = Path(tmp) for name, text in [ ('fixed', code), - ('omit-gpu-demand', code.replace('if (clpc->gpu_pmgr) {', 'if (false) {')), + ('omit-host-demand', code.replace('if (clpc->gpu) {', 'if (false) {')), + ('omit-pmgr-demand', code.replace('if (clpc->gpu_pmgr) {', 'if (false) {')), ('release-too-early', code.replace('msecs_to_jiffies(APPLE_CLPC_HOLD_MS)', '0')), ('retain-at-idle', code.replace('vote->state = 0;', '')), ]: From 6c97d25086a07f970ae9bc2266c33b6b2bd98c52 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Tue, 27 Oct 2026 17:37:23 +1100 Subject: [PATCH 111/184] tools/asahi: follow the shared fault witness and render teardown interfaces Keep host resource/ownership tests compiling the production methods after #496/#505. Include the actual fault-stamp comparison, model the new queue field, and supply inert tracing/capacity notifications. The owner-ordering fixture models no executed unstamped pair; that predicate and its lifetime are covered in kernel KUnit. Preserve each original negative control. Assisted-by: LLM Signed-off-by: Ryan Murray --- tools/asahi/g17-capacity-fault-tests.py | 8 ++++---- tools/asahi/g17-owner-release-order-tests.py | 2 ++ tools/asahi/g17-pool-release-tests.py | 2 ++ tools/asahi/g17-resource-tests.py | 4 ++-- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/tools/asahi/g17-capacity-fault-tests.py b/tools/asahi/g17-capacity-fault-tests.py index 2d0e755b5b01f..c47d193a74c55 100644 --- a/tools/asahi/g17-capacity-fault-tests.py +++ b/tools/asahi/g17-capacity-fault-tests.py @@ -28,13 +28,13 @@ def fixture(): retirement = (ROOT / 'queue/compute/retirement.rs').read_text() previous = item(source, 'struct Previous<') + '\n' + item(source, 'impl Previous') methods = '\n'.join(item(source, sig) for sig in [ - 'pub(crate) fn release_owner(', 'pub(crate) fn reusable(', + 'fn witness_stamp(', 'pub(crate) fn release_owner(', 'pub(crate) fn reusable(', 'pub(crate) fn recycle_quarantined_idle(', 'pub(crate) fn recycle_killed(', 'fn recycle_settled(', 'fn release_witnessed_inner(', 'pub(crate) fn share_idle(', 'pub(crate) fn shareable(', 'pub(crate) fn can_rearm(', 'pub(crate) fn recycled_pending(']) exit_source = (ROOT / 'queue/compute/exit.rs').read_text() methods += '\n' + item(exit_source, 'pub(crate) fn settle_exit(').replace('super::Previous', 'Previous').replace('crate::g17::qos::Owner', 'qos::Owner') drain = item((ROOT / 'mod.rs').read_text(), 'fn drain_reclaims(') - proof = item(retirement, 'pub(crate) const fn stamp_covers(') + '\n' + item(retirement, 'pub(crate) fn quarantined_retired(') + proof = item(retirement, 'pub(crate) const fn stamp_newer(') + '\n' + item(retirement, 'pub(crate) const fn stamp_covers(') + '\n' + item(retirement, 'pub(crate) fn quarantined_retired(') return r''' #![allow(dead_code)] use std::sync::{Arc, atomic::{AtomicUsize, AtomicU32, Ordering}}; @@ -108,7 +108,7 @@ def fixture(): retired_by_teardown: bool, spared_quarantine: bool, spared_deferred: bool, retire_pending: bool, retirement_proved: bool, replay_retirement: bool, replay_error: Option, replay_prepared: bool, submitted: u32, kick: Kick, - config_cache: Option<()>, source_hint: ReaderHint, source_readers: AtomicU32, claims: u32, + config_cache: Option<()>, source_hint: ReaderHint, source_readers: AtomicU32, claims: u32, fault_stamp: Option, } impl Queue { fn valid_ticket(&self, _: &Active, _: &Ticket) -> Result { Ok(()) } @@ -128,7 +128,7 @@ def fixture(): retired_by_teardown: false, spared_quarantine: false, spared_deferred: false, retire_pending: false, retirement_proved: false, replay_retirement: false, replay_error: None, replay_prepared: false, submitted: 0, kick: Kick, - config_cache: None, source_hint: ReaderHint { possible: false }, source_readers: AtomicU32::new(0), claims: 0, + fault_stamp: None, config_cache: None, source_hint: ReaderHint { possible: false }, source_readers: AtomicU32::new(0), claims: 0, } } fn fault(live: &Arc) -> Queue { diff --git a/tools/asahi/g17-owner-release-order-tests.py b/tools/asahi/g17-owner-release-order-tests.py index ad17fa4048726..3f68e930f9001 100755 --- a/tools/asahi/g17-owner-release-order-tests.py +++ b/tools/asahi/g17-owner-release-order-tests.py @@ -121,6 +121,8 @@ def fixture(): struct Packet { context: Arc } struct Pair { executed: u64, switch: Option<(u64, u64)>, replay: Vec<()>, active: Vec, idle: bool } impl Pair { + // This ordering fixture models no faulted/executed unstamped pair. + fn executed_for_kill(&self, _: &Arc) -> bool { false } SWITCH /// Host stand-in for a successful publication of `ordinal` (stand-in, not production). fn publish(&mut self, packet: &Packet, ordinal: u64) { diff --git a/tools/asahi/g17-pool-release-tests.py b/tools/asahi/g17-pool-release-tests.py index 2ad4c6901cba4..70004fe94ecf9 100644 --- a/tools/asahi/g17-pool-release-tests.py +++ b/tools/asahi/g17-pool-release-tests.py @@ -98,6 +98,8 @@ def fixture(): admission: Admission, } impl Registry { + // Physical-QID capacity notification does not change this pool-release fixture. + fn note_build_room(&self) {} METHODS fn poll(&self) -> bool { self.compute.iter().flatten().filter_map(|entry| entry.queue.as_deref())PREDICATE diff --git a/tools/asahi/g17-resource-tests.py b/tools/asahi/g17-resource-tests.py index cf93704966c33..ae7e53ec6cf6f 100644 --- a/tools/asahi/g17-resource-tests.py +++ b/tools/asahi/g17-resource-tests.py @@ -24,7 +24,7 @@ def item(start): limits='\n'.join(re.search(r'const '+name+r': [^;]+;',mmu).group(0) for name in ['EXECUTION_CONTEXT_MASK','MAX_EXECUTION_CONTEXTS']) header='''#![allow(dead_code, unused_macros)] type Result = std::result::Result; - mod g17 { pub mod capacity { + mod g17 { pub mod trace { pub fn teardown(_:u8,_:u64,_:u64,_:u64) {} } pub mod capacity { pub enum Site { RenderSlots, LogicalQueues, ComputeReserve, PhysicalQids } pub fn refused(_: Site) -> i32 { -28 } }} @@ -137,7 +137,7 @@ def item(s,start): constants=re.search(r'const QID_MAX: [^;]+;',fw).group(0)+'\n'+'\n'.join(re.search(r'const '+name+r': [^;]+;',s).group(0) for name in ['QID_COUNT','RENDER_HEADROOM','FULL_RENDER_RESERVE']) header='''#![allow(dead_code, unused_macros)] type Result = std::result::Result; - mod g17 { pub mod capacity { + mod g17 { pub mod trace { pub fn teardown(_:u8,_:u64,_:u64,_:u64) {} } pub mod capacity { pub enum Site { RenderSlots, LogicalQueues, ComputeReserve, PhysicalQids } pub fn refused(_: Site) -> i32 { -28 } }} From 61a2fec62a709db5b435a9a41ff51270362d4a80 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Tue, 27 Oct 2026 17:37:42 +1100 Subject: [PATCH 112/184] drm/asahi: pass an opaque device across the CLPC FFI boundary Keep the C wrapper responsible for the device layout, as the existing sysfs wrappers do. Rust bindings include private configuration-dependent scheduler members and cannot be named in a WERROR-safe extern signature. The live platform device and owned request lifetime remain unchanged. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/gpu/drm/asahi/g17/clpc.c | 2 +- drivers/gpu/drm/asahi/g17/clpc.h | 2 +- drivers/gpu/drm/asahi/g17/power.rs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/asahi/g17/clpc.c b/drivers/gpu/drm/asahi/g17/clpc.c index b8ae1242cd8aa..58adaba19910f 100644 --- a/drivers/gpu/drm/asahi/g17/clpc.c +++ b/drivers/gpu/drm/asahi/g17/clpc.c @@ -2,7 +2,7 @@ #include #include "clpc.h" -int asahi_clpc_gpu_get(struct device *dev, struct apple_clpc_gpu **out) +int asahi_clpc_gpu_get(void *dev, struct apple_clpc_gpu **out) { return apple_clpc_gpu_get(dev, out); } diff --git a/drivers/gpu/drm/asahi/g17/clpc.h b/drivers/gpu/drm/asahi/g17/clpc.h index 3fc6f75473454..372820ade7eff 100644 --- a/drivers/gpu/drm/asahi/g17/clpc.h +++ b/drivers/gpu/drm/asahi/g17/clpc.h @@ -3,7 +3,7 @@ #define _ASAHI_G17_CLPC_H struct device; struct apple_clpc_gpu; -int asahi_clpc_gpu_get(struct device *dev, struct apple_clpc_gpu **out); +int asahi_clpc_gpu_get(void *dev, struct apple_clpc_gpu **out); void asahi_clpc_gpu_set_active(struct apple_clpc_gpu *gpu, bool active); void asahi_clpc_gpu_put(struct apple_clpc_gpu *gpu); #endif diff --git a/drivers/gpu/drm/asahi/g17/power.rs b/drivers/gpu/drm/asahi/g17/power.rs index 075d2829e4243..a2e293df91510 100644 --- a/drivers/gpu/drm/asahi/g17/power.rs +++ b/drivers/gpu/drm/asahi/g17/power.rs @@ -302,7 +302,7 @@ mod tests { } unsafe extern "C" { - fn asahi_clpc_gpu_get(dev: *mut kernel::bindings::device, out: *mut *mut core::ffi::c_void) -> core::ffi::c_int; + fn asahi_clpc_gpu_get(dev: *mut core::ffi::c_void, out: *mut *mut core::ffi::c_void) -> core::ffi::c_int; fn asahi_clpc_gpu_set_active(gpu: *mut core::ffi::c_void, active: bool); fn asahi_clpc_gpu_put(gpu: *mut core::ffi::c_void); } @@ -320,7 +320,7 @@ impl Demand { pub(super) fn new(dev: *mut kernel::bindings::device) -> kernel::error::Result { let mut out = core::ptr::null_mut(); // SAFETY: live platform device and writable result; C returns an owned reference. - let ret = unsafe { asahi_clpc_gpu_get(dev, &mut out) }; + let ret = unsafe { asahi_clpc_gpu_get(dev.cast(), &mut out) }; if ret < 0 { Err(kernel::error::Error::from_errno(ret)) } else { From 460e4606950d5358128cb09f5b131a476a37be42 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Tue, 27 Oct 2026 17:39:16 +1100 Subject: [PATCH 113/184] drm/asahi: expose firmware-managed GPU core power as a read-only view Provide a concise debugfs power view with the existing always-accessible core power witness, host hold state, idle policy, effort, direct-publication count and last publication time. Read under the live firmware owner without waking the GPU. The same values accompany raw firmware reports. Generic Linux runtime PM does not manage this firmware's autonomous core gating and may correctly report unsupported. Report the actual firmware power witness without enabling a fictitious runtime-suspend implementation. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/gpu/drm/asahi/g17/mod.rs | 6 ++++++ drivers/gpu/drm/asahi/g17/reports.rs | 25 ++++++++++++++++++++----- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/asahi/g17/mod.rs b/drivers/gpu/drm/asahi/g17/mod.rs index a3c83bca59b21..0e49420f044a1 100644 --- a/drivers/gpu/drm/asahi/g17/mod.rs +++ b/drivers/gpu/drm/asahi/g17/mod.rs @@ -1103,6 +1103,7 @@ pub(crate) struct Gpu { shared: Arc, _capacity: Pin>>, _reports: Pin>>, + _power: Pin>>, } impl Gpu { @@ -1214,6 +1215,10 @@ impl Gpu { directory.read_only_file(c"firmware_reports", reports::View(shared.clone())), GFP_KERNEL, )?; + let power = KBox::pin_init( + directory.read_only_file(c"power", reports::PowerView(shared.clone())), + GFP_KERNEL, + )?; let gpu = Arc::new( Gpu { cfg, @@ -1224,6 +1229,7 @@ impl Gpu { shared, _capacity: capacity, _reports: reports, + _power: power, }, GFP_KERNEL, )?; diff --git a/drivers/gpu/drm/asahi/g17/reports.rs b/drivers/gpu/drm/asahi/g17/reports.rs index 62ed791322ad9..b67512f7eec6b 100644 --- a/drivers/gpu/drm/asahi/g17/reports.rs +++ b/drivers/gpu/drm/asahi/g17/reports.rs @@ -57,15 +57,30 @@ impl kernel::debugfs::Writer for View { let state = self.0.state.lock(); let Some(firmware) = state.as_deref() else { return writeln!(f, "online=0"); }; writeln!(f, "online=1 graph={:?} sampled_ns={}", self.0.variant.graph, super::now_ns())?; - let core_power = firmware.bridge.regs.g17_core_power(); - writeln!(f, "power_management=firmware core_power={:#x} cores_powered={} idle_hold_ms={}", - core_power, core_power & 0xf != 0, - super::power::IDLE_HOLD_MS.load(core::sync::atomic::Ordering::Relaxed))?; - firmware.port.write_power(f)?; + write_power(firmware, f)?; firmware.rings.write_reports(f) } } +fn write_power(firmware: &super::Firmware, f: &mut kernel::fmt::Formatter<'_>) -> kernel::fmt::Result { + let core_power = firmware.bridge.regs.g17_core_power(); + writeln!(f, "power_management=firmware sampled_ns={} core_power={:#x} cores_powered={} host_hold={} idle_hold_ms={}", + super::now_ns(), core_power, core_power & 0xf != 0, firmware.hold.active(), + super::power::IDLE_HOLD_MS.load(core::sync::atomic::Ordering::Relaxed))?; + firmware.port.write_power(f) +} + +/// Actual firmware-managed core gating, separate from generic Linux runtime PM. +pub(super) struct PowerView(pub(super) kernel::sync::Arc); + +impl kernel::debugfs::Writer for PowerView { + fn write(&self, f: &mut kernel::fmt::Formatter<'_>) -> kernel::fmt::Result { + let state = self.0.state.lock(); + let Some(firmware) = state.as_deref() else { return writeln!(f, "online=0"); }; + write_power(firmware, f) + } +} + #[kernel::macros::kunit_tests(asahi_g17_reports)] mod tests { use super::*; From e3c8798c137bf697e8efd153af3150b96d1bac78 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Tue, 27 Oct 2026 17:39:16 +1100 Subject: [PATCH 114/184] tools/asahi: expose the production fault-stamp helper in the host fixture Re-export the extracted stamp comparison through the fixture module used by the production witness method. Keep the release predicates and negative controls unchanged. Assisted-by: LLM Signed-off-by: Ryan Murray --- tools/asahi/g17-capacity-fault-tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/asahi/g17-capacity-fault-tests.py b/tools/asahi/g17-capacity-fault-tests.py index c47d193a74c55..7c33e92a74812 100644 --- a/tools/asahi/g17-capacity-fault-tests.py +++ b/tools/asahi/g17-capacity-fault-tests.py @@ -63,7 +63,7 @@ def fixture(): } #[derive(Clone, Copy)] struct Cursors { count: u32, producer: u32, consumer: u32 } ''' + proof + r''' -mod retirement { pub(crate) use super::quarantined_retired; } +mod retirement { pub(crate) use super::{quarantined_retired, stamp_newer}; } struct Graph { cursors: Cursors, stamp: u32, fail: bool, resets: usize, owner: u64 } impl Graph { fn set_owner(&mut self, context: &Context) -> Result { if self.fail { Err(EIO) } else { self.owner = context.id; Ok(()) } } From 1fce2b110d1f83ec65a5b04d521226d3b5476dcf Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Tue, 27 Oct 2026 17:40:56 +1100 Subject: [PATCH 115/184] docs: record the T6040 supplier-lifetime latch and release qualification Distinguish Neo activity from the T6040 always-powered firmware supplier and document the DT-linked host demand, race/lifetime guards and read-only power/vote evidence. Keep blank-panel release and g3 gain retention as hardware gates. Assisted-by: LLM Signed-off-by: Ryan Murray --- GAPS.md | 45 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/GAPS.md b/GAPS.md index 7944948712418..dcbda1083f7c8 100644 --- a/GAPS.md +++ b/GAPS.md @@ -24,7 +24,7 @@ retains it and adds the qualified shared ports and diagnostics below. | Raw OPP count versus logical states | Firmware consumes 11 logical states | 16 raw GPU OPPs; 11 selectors in each A/B map; both end at raw OPP 15; unused handoff slots must be zero | KUnit now uses valid complete maps and checks the final state's second voltage lane | `g17/calibration.rs`; a count of 15 in an ADT property is not evidence for expanding this firmware layout | | Full clock eligibility | Platform OPPs feed firmware performance tables | DT supplies both cluster voltages and GPU/CS/AFR frequencies; maps and relative-B are checked against the GPU OPP table | Same runtime behavior | `g17/initdata/v25g83.rs`, `g17/fw/initdata/v25g83.rs`; confirm sustained clocks and limiting reasons on hardware | | DVFS ramp / effort | Full effort on publication, cleared after 16 ms of continuous idle | Same `Firmware::note_submission`, direct-port accounting and race-checked idle clear | Same runtime behavior | `g17/mod.rs`, `g17/port.rs`, `g17/power.rs`; opaque firmware power words are not a documented ramp control | -| GPU-driven fabric/memory floor | #440 lifts DCS 4/fabric 3 for GPU demand | T6040 CPU-only demand, gpu_demand=false and no power-state phandle | Port 0d63308820ab enables the shared mechanism with existing T6040 DCS 5/fabric 2 data | clpc.c and T6040 DT; g1/g2 P/C/R/H/I measurements required | +| GPU-driven fabric/memory floor | #440 uses an activity-following graphics PMGR input | T6040 CPU-only demand; g3 port sampled the firmware-lifetime GFX supplier | g4 uses DT-selected publication/continuous-idle demand, preserving DCS 5/fabric 2 and 32 ms CLPC hold | `clpc.c`, `g17/power.rs`, `g17/port.rs`, T6040 DT; blank-panel release and bandwidth retention require hardware qualification | | PMP PMC vote cooperation | PMP/CLPC/AP agents enabled | T6040 static voters omit PMP 0 from pre-working-PMP bring-up | Port 772a9d3104ff adds the existing PMP supplier dependency and voter 0 on T6040 DCS/SOC rails | pmc.c and T6040 DT; live votes, idle release and thermal qualification required | | Idle / off policy | Shared host hold and firmware device-control path | Same host implementation; source documents a 25G83 effective 2 ms off delay and approximately 1 ms wake cost | J616s requests a bounded 16 ms host hold through existing `apple,idle-off-delay-ms`; absent policy retains 2 ms | `g17/power.rs`, `g17/mod.rs`, J616s DT and binding; compare short/long gaps, depth-one latency and idle power | | Fast-die / PMP | Platform-specific firmware power graph | 25G83 AON table and separate power-state target/release (101/96 C) are already populated; DT enables the GFX suppliers | Same runtime behavior | `g17/fw/initdata/v25g83.rs::POWER_STATE_WORDS`, `g17/initdata/v25g83.rs`, J616s DT; verify clocks with PMP running | @@ -58,8 +58,10 @@ for its acknowledgement. It does not encode a DCS/fabric performance request. The GPU firmware owns the PMP's performance demand. Before this series, PMC's static voter list omitted PMP agent 0, and CLPC lacked a GPU power-state input. The two existing ports enable PMP agent 0 on T6040's DCS/SOC rails and make -CLPC request the already qualified DCS 5 / fabric 2 floor while graphics is -powered. They preserve the existing 32 ms memory-floor release. Prove live +CLPC request the already qualified DCS 5 / fabric 2 floor. G3 incorrectly +used the T6040 supplier lifetime as activity. G4 replaces that input with +DT-selected publication/continuous-idle demand and preserves the existing +32 ms memory-floor release. Prove live request, enabled-agent and actual-state agreement; supplier binding alone is not proof of an honoured vote. @@ -120,7 +122,7 @@ no raw lab capture is a public implementation source. | #505 closed-context teardown | `078d744a260c` kills before freeing executed unstamped render graphs, retains firmware-linked work storage, and reuses killed pairs last with QID capacity | Shared safety/retention paths included; compute-fault-driven kill depends on the graph-qualified #496 witness | Closed mixed compute/render faults, acknowledgement ordering, memory pressure and slot/QID reuse; does not claim to cure a live render's first fault | | First fault | Reason and blame alone cannot identify engine progress | At halted state 1, before acknowledgement, g4 logs the blamed pair's stored stamps, free-list balance and up to four commands' pass/cursor snapshots; borrowed pairs are explicitly unavailable | Correlate exact CTS operation and original error with raw event report and snapshots; do not label reason 2 as page fault without semantics | | Raw firmware statistics | Complete 18-word statistics records already have checked ring geometry | Read-only `firmware_reports` exposes last full sample, count and host monotonic time for each role/type. Static-key `asahi_g17_report` copies trace/statistics/event words before slot return | Match payloads to independently decoded achieved-clock/power/residency and freshness/units; legacy statistics tags are a different ABI | -| Idle memory floor | PMGR graphics power is a supplier lifetime, not itself an idle-work witness | Existing policy retained; CLPC status now exposes the sampled graphics status/active input and read-error count alongside CPU requests and rail votes | Long idle with no GPU clients, then load and release, must distinguish GPU power, CPU demand, failed reads and other PMC agents; never infer idle from an open client alone | +| Idle memory floor | Neo graphics PMGR follows activity; T6040 GFX is held for firmware lifetime | g4 replaces T6040 PMGR demand with a DT-linked CLPC request, asserted at publication and released by the existing race-checked 16 ms continuous-idle clear; 32 ms CLPC hold remains | Read-only last vote/reason/time, host request and last submission, safe core power/host hold; require blank-panel quiet release and load/release cycles | Remaining exact facts for telemetry are the 25G83/26A428 tag-to-layout map, field width/signedness/endianness, sample/timebase/wrap and freshness, @@ -138,6 +140,41 @@ feature-enable or buffer-size guess is included in g4. Multi-queue sync needs its publish/GPU-span/event/fence timeline; #504's compute control does not promise to halve that path's latency. +### T6040 idle-floor release correction + +The PMGR DT explicitly documents that GFX cannot power down while firmware +runs. This is a source-established difference from the Neo CLPC input, so +using GFX as T6040 work demand can never establish idle release. G4 removes +that input on T6040 and adds `apple,clpc` from the GPU consumer to the existing +host performance controller. This is a provider relationship, not a feature +flag or user knob. A requested but unavailable provider defers GPU probe. + +Publication asserts the independent, refcounted request through a leaf lock; +o sleeping callback or allocation runs at submission. Both mutex-backed and +off-lock direct paths are covered. The existing effort clear checks in-flight +work and 16 ms continuous idle, withdraws its flag, then rechecks the direct +submission counter. A racing publisher reasserts demand. The CLPC vote keeps +its existing 32 ms hold; CPU arbitration and Neo's PMGR input are unchanged. +Firmware stop and request destruction clear demand. Provider removal cannot +free storage retained by a late consumer, and a device link orders dependencies. + +CLPC status now shows the last requested floor, reason bits and monotonic +time, plus the host request's activity/update sequence. Actual PMC readback +and write errors remain distinct. Read-only `asahi-g17/power` reports the +existing always-accessible core power witness, host hold, effort and last +publication time, without waking firmware. Generic runtime PM may remain +unsupported because autonomous core gating is firmware-managed; no fictitious +runtime-suspend implementation is enabled. Raw stats still do not decode MHz. + +The production CLPC host suite tests an always-powered supplier with absent, +busy and ended GPU demand, 32 ms hold, CPU coexistence, reason/time and legacy +Neo behavior; removing demand or idle release fails its negative controls. +C KUnit checks actual request lifetime and a late consumer after provider +removal. Hardware acceptance requires a locked/blank panel with publications +observably quiet, release after the hold, then three load/idle cycles and +retention of g3's benchmark gains. An active compositor alone proves neither +quiescence nor a latch. Keep failed measurements and rollback candidates. + ## Source and validation boundaries The implementation references are the licensed public Aurora/Asahi kernel and From 7ab77755a0538e685d891449180e82c874587b5a Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Tue, 27 Oct 2026 17:41:59 +1100 Subject: [PATCH 116/184] soc: apple: serialize CLPC demand lookup with provider removal Publish and withdraw provider drvdata under a lifetime mutex before devres can free it, taking a request reference while still protected. Avoid nesting the supplier device lock under a probing consumer lock. Submission only uses the independent request allocation and leaf lock. Report a held rail even when another rail has active CPU demand. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/soc/apple/clpc.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/drivers/soc/apple/clpc.c b/drivers/soc/apple/clpc.c index 3491030c670dd..90e449d048c97 100644 --- a/drivers/soc/apple/clpc.c +++ b/drivers/soc/apple/clpc.c @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -125,6 +126,9 @@ struct apple_clpc { unsigned long gpu_read_errors; }; +/* Protect publication/removal of provider drvdata, never a submission path. */ +static DEFINE_MUTEX(apple_clpc_clients_lock); + static void apple_clpc_gpu_free(struct kref *ref) { kfree(container_of(ref, struct apple_clpc_gpu, ref)); @@ -178,14 +182,14 @@ int apple_clpc_gpu_get(struct device *dev, struct apple_clpc_gpu **out) put_device(&provider->dev); return -ENOMEM; } - device_lock(&provider->dev); - clpc = device_is_bound(&provider->dev) ? dev_get_drvdata(&provider->dev) : NULL; + mutex_lock(&apple_clpc_clients_lock); + clpc = dev_get_drvdata(&provider->dev); if (clpc && clpc->gpu && refcount_read(&clpc->gpu->ref.refcount) == 1) { kref_get(&clpc->gpu->ref); *out = clpc->gpu; ret = 0; } - device_unlock(&provider->dev); + mutex_unlock(&apple_clpc_clients_lock); put_device(&provider->dev); return ret; } @@ -395,8 +399,9 @@ static void apple_clpc_sample(struct apple_clpc *clpc) now); floor = apple_clpc_floor(clpc, state); - if (!reason && floor) - reason = 16; + for (rail = 0; rail < APPLE_CLPC_RAILS; rail++) + if (state[rail] > demand[rail]) + reason |= 16; clpc->last_floor = floor; clpc->last_reason = reason; clpc->last_vote_ns = ktime_get_mono_fast_ns(); @@ -494,6 +499,9 @@ static void apple_clpc_stop(void *data) { struct apple_clpc *clpc = data; + mutex_lock(&apple_clpc_clients_lock); + dev_set_drvdata(clpc->dev, NULL); + mutex_unlock(&apple_clpc_clients_lock); apple_clpc_halt(clpc); debugfs_remove_recursive(clpc->debugfs); apple_clpc_free_clusters(clpc); @@ -592,7 +600,9 @@ static int apple_clpc_probe(struct platform_device *pdev) if (ret) return ret; + mutex_lock(&apple_clpc_clients_lock); platform_set_drvdata(pdev, clpc); + mutex_unlock(&apple_clpc_clients_lock); apple_clpc_queue(clpc, true); return 0; } From 8d96fbc8ab4458f3cd5d30047588bb3bb6cd2c22 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Tue, 27 Oct 2026 17:46:20 +1100 Subject: [PATCH 117/184] soc: apple: wake the CLPC sampler on a cold GPU demand edge An idle vote uses a deferrable timer. A GPU-only publisher can wake another CPU without servicing that timer, so request a nondeferrable immediate sample on the inactive-to-active edge. Keep the submission path atomic: queue work under the request leaf lock, never touch PMC there. Serialize the normal and deferrable sampling passes and collapse their cadence after the edge. Provider removal withdraws the wake pointer under the same request lock before draining work. Late consumers retain only an independent request, never a pointer to freed work storage. KUnit exercises the real cold-demand workqueue wake in addition to the lifetime controls. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/soc/apple/clpc.c | 70 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 66 insertions(+), 4 deletions(-) diff --git a/drivers/soc/apple/clpc.c b/drivers/soc/apple/clpc.c index 90e449d048c97..892b9a8af2646 100644 --- a/drivers/soc/apple/clpc.c +++ b/drivers/soc/apple/clpc.c @@ -92,6 +92,7 @@ struct apple_clpc_gpu { bool active; u64 updated_ns; u64 sequence; + struct delayed_work *wake; }; struct apple_clpc { @@ -102,6 +103,7 @@ struct apple_clpc { /* Samples while a vote is held; idle_work defers to busy CPUs. */ struct delayed_work work; struct delayed_work idle_work; + struct mutex sample_lock; bool stopped; struct apple_clpc_cluster clusters[APPLE_CLPC_MAX_CLUSTERS]; unsigned int nr_clusters; @@ -141,6 +143,11 @@ void apple_clpc_gpu_set_active(struct apple_clpc_gpu *gpu, bool active) if (!gpu) return; spin_lock_irqsave(&gpu->lock, flags); + /* A cold GPU publisher must not wait for a deferrable CPU-idle timer. + * Removal clears wake under this same lock before draining work. + */ + if (active && !gpu->active && gpu->wake) + mod_delayed_work(system_freezable_power_efficient_wq, gpu->wake, 0); gpu->active = active; gpu->updated_ns = ktime_get_mono_fast_ns(); gpu->sequence++; @@ -334,6 +341,10 @@ static void apple_clpc_queue(struct apple_clpc *clpc, bool idle) * stops; with no vote held, sampling can wait for a CPU that wakes up * anyway rather than waking an idle one every period. */ + /* A demand edge can queue work while the deferrable pass is pending. + * Keep one sampling cadence once the serialized pass settles. + */ + cancel_delayed_work(idle ? &clpc->work : &clpc->idle_work); queue_delayed_work(system_freezable_power_efficient_wq, idle ? &clpc->idle_work : &clpc->work, msecs_to_jiffies(APPLE_CLPC_SAMPLE_MS)); @@ -413,14 +424,22 @@ static void apple_clpc_sample(struct apple_clpc *clpc) static void apple_clpc_work(struct work_struct *work) { - apple_clpc_sample(container_of(to_delayed_work(work), struct apple_clpc, - work)); + struct apple_clpc *clpc = container_of(to_delayed_work(work), struct apple_clpc, work); + + mutex_lock(&clpc->sample_lock); + if (!READ_ONCE(clpc->stopped)) + apple_clpc_sample(clpc); + mutex_unlock(&clpc->sample_lock); } static void apple_clpc_idle_work(struct work_struct *work) { - apple_clpc_sample(container_of(to_delayed_work(work), struct apple_clpc, - idle_work)); + struct apple_clpc *clpc = container_of(to_delayed_work(work), struct apple_clpc, idle_work); + + mutex_lock(&clpc->sample_lock); + if (!READ_ONCE(clpc->stopped)) + apple_clpc_sample(clpc); + mutex_unlock(&clpc->sample_lock); } static int apple_clpc_status_show(struct seq_file *s, void *unused) @@ -502,6 +521,13 @@ static void apple_clpc_stop(void *data) mutex_lock(&apple_clpc_clients_lock); dev_set_drvdata(clpc->dev, NULL); mutex_unlock(&apple_clpc_clients_lock); + if (clpc->gpu) { + unsigned long flags; + + spin_lock_irqsave(&clpc->gpu->lock, flags); + clpc->gpu->wake = NULL; + spin_unlock_irqrestore(&clpc->gpu->lock, flags); + } apple_clpc_halt(clpc); debugfs_remove_recursive(clpc->debugfs); apple_clpc_free_clusters(clpc); @@ -589,8 +615,11 @@ static int apple_clpc_probe(struct platform_device *pdev) clpc->mask; apple_clpc_find_clusters(clpc); clpc->next_scan = jiffies + HZ; + mutex_init(&clpc->sample_lock); INIT_DELAYED_WORK(&clpc->work, apple_clpc_work); INIT_DEFERRABLE_WORK(&clpc->idle_work, apple_clpc_idle_work); + if (clpc->gpu) + clpc->gpu->wake = &clpc->work; clpc->debugfs = debugfs_create_dir(dev_name(dev), NULL); debugfs_create_file("status", 0400, clpc->debugfs, clpc, @@ -695,6 +724,7 @@ static const struct apple_clpc_hw apple_clpc_hw_t6040 = { #if IS_BUILTIN(CONFIG_KUNIT) && !defined(MODULE) #include +#include static void apple_clpc_gpu_request_lifetime_test(struct kunit *test) { @@ -736,7 +766,39 @@ static void apple_clpc_gpu_provider_removal_test(struct kunit *test) apple_clpc_gpu_put(gpu); } +struct apple_clpc_wake_test { + struct delayed_work work; + struct completion done; +}; + +static void apple_clpc_wake_complete(struct work_struct *work) +{ + struct apple_clpc_wake_test *wake = container_of(to_delayed_work(work), + struct apple_clpc_wake_test, work); + complete(&wake->done); +} + +static void apple_clpc_gpu_cold_wake_test(struct kunit *test) +{ + struct apple_clpc_gpu *gpu = kzalloc_obj(*gpu); + struct apple_clpc_wake_test wake; + + KUNIT_ASSERT_NOT_NULL(test, gpu); + kref_init(&gpu->ref); + spin_lock_init(&gpu->lock); + INIT_DELAYED_WORK(&wake.work, apple_clpc_wake_complete); + init_completion(&wake.done); + gpu->wake = &wake.work; + apple_clpc_gpu_set_active(gpu, true); + KUNIT_EXPECT_NE(test, wait_for_completion_timeout(&wake.done, HZ), 0ul); + /* Model provider withdrawal before its work storage is destroyed. */ + gpu->wake = NULL; + cancel_delayed_work_sync(&wake.work); + apple_clpc_gpu_put(gpu); +} + static struct kunit_case apple_clpc_cases[] = { + KUNIT_CASE(apple_clpc_gpu_cold_wake_test), KUNIT_CASE(apple_clpc_gpu_request_lifetime_test), KUNIT_CASE(apple_clpc_gpu_provider_removal_test), {} From fa12ae88cc55fbda94349566ca606f78482a6d24 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Tue, 27 Oct 2026 17:48:41 +1100 Subject: [PATCH 118/184] drm/asahi: retain memory demand until both firmware processors stop Clear the owned CLPC request after the processor stop sequence, so a slow shutdown does not begin the memory-floor idle hold while firmware can still consume work. Late request destruction remains independently safe and clears the request again without accessing provider storage. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/gpu/drm/asahi/g17/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/asahi/g17/mod.rs b/drivers/gpu/drm/asahi/g17/mod.rs index 0e49420f044a1..c95be04f01222 100644 --- a/drivers/gpu/drm/asahi/g17/mod.rs +++ b/drivers/gpu/drm/asahi/g17/mod.rs @@ -644,11 +644,11 @@ struct Firmware { impl Drop for Firmware { fn drop(&mut self) { - self.port.set_effort_full(false); if let Some(secondary) = self.secondary.as_mut() { secondary.stop(); } self.primary.stop(); + self.port.set_effort_full(false); self.pool.stop_after_firmware(); // RTKit destruction releases the mailbox power references. Clear the bridge while // those references still keep the register block powered. From 2aa5f642c9f7382e46796b524606d0be430baf7c Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Tue, 27 Oct 2026 17:57:00 +1100 Subject: [PATCH 119/184] drm/asahi: keep G17 long jobs alive beside other long jobs A published job's deadline is renewed, up to thirty seconds, while some command retired within the last five seconds: the GPU is then shared, not hung. Two ray traversal dispatches side by side retire nothing until they end, though both progress. Alone each took about 6.3 s; side by side they took 10 to 17 s, past the ten seconds a long job gets without that renewal, so the watchdog failed most of them with ETIMEDOUT and their clients lost the device. Also count the GPU as shared while a long job of another queue runs beside a long job. The thirty-second bound still ends a pair that never finishes. The shared host policy also covers bounded AS-build commands on T6040; its native traversal gate remains unchanged. No firmware watchdog field or numeric host renewal bound changes. This does not classify or cure the reason-2 live-render CTS faults. Link: https://github.com/aurora-silicon/linux/commit/3db24da5c830344e1c1449d38ec9ae0de1396a3d Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/gpu/drm/asahi/g17/job.rs | 12 +++--- drivers/gpu/drm/asahi/g17/queue/compute.rs | 10 +++++ drivers/gpu/drm/asahi/g17/runtime/backend.rs | 10 +++++ drivers/gpu/drm/asahi/g17/timeout.rs | 42 ++++++++++++++------ 4 files changed, 57 insertions(+), 17 deletions(-) diff --git a/drivers/gpu/drm/asahi/g17/job.rs b/drivers/gpu/drm/asahi/g17/job.rs index 8175c992b8a62..61033aa9e52b4 100644 --- a/drivers/gpu/drm/asahi/g17/job.rs +++ b/drivers/gpu/drm/asahi/g17/job.rs @@ -293,6 +293,8 @@ pub(crate) trait Backend: Send + Sync { fn cancel(&self, packet: Arc); /// Transfer a timeout to an outstanding off-lock graph transaction, if any. fn defer_timeout(&self, packet: &Arc, renewals: u8) -> bool; + /// A long job of another queue is running beside `packet`. + fn long_job_beside(&self, _packet: &Arc) -> bool { false } /// Return true only after taking publication ownership and recording its result. fn try_early(&self, packet: &Arc) -> bool; fn count_backlog(&self, packet: &Packet); @@ -346,11 +348,11 @@ impl sched::JobImpl for Job { ) { return true; } - // Other work retiring means the GPU is shared, not hung. - if super::timeout::renew_shared( - job.packet.is_published(), super::recovery::since_retired(super::now_ns()), - &mut job.shared_renewals, - ) { + // Other work retiring means the GPU is shared, not hung. So does another long job + // running beside a long job, though neither retires until it ends. + let shared = super::timeout::retiring(super::recovery::since_retired(super::now_ns())) + || (job.packet.command.long_job() && job.backend.long_job_beside(&job.packet)); + if super::timeout::renew_shared(job.packet.is_published(), shared, &mut job.shared_renewals) { return true; } let deferred = job.backend.defer_timeout(&job.packet, job.victim_renewals); diff --git a/drivers/gpu/drm/asahi/g17/queue/compute.rs b/drivers/gpu/drm/asahi/g17/queue/compute.rs index 68fd03c42b42b..c40992e50eff6 100644 --- a/drivers/gpu/drm/asahi/g17/queue/compute.rs +++ b/drivers/gpu/drm/asahi/g17/queue/compute.rs @@ -786,6 +786,16 @@ impl Queue { pub(crate) fn in_flight(&self) -> bool { self.active.len != 0 && !self.quarantined } + /// The oldest command is a published long job (ray traversal or AS build) whose + /// completion record the driver has not seen yet. + pub(crate) fn long_job_running(&self) -> bool { + !self.quarantined + && self.active.front().is_some_and(|active| { + active.observed.is_none() + && active.packet.is_published() + && active.packet.command.long_job() + }) + } /// Quarantines without a pending witness no longer describe live pipe order. pub(crate) fn timeout_head(&self) -> Option { if self.quarantined && !self.awaiting_witness { diff --git a/drivers/gpu/drm/asahi/g17/runtime/backend.rs b/drivers/gpu/drm/asahi/g17/runtime/backend.rs index d19f51c08dd29..bf0f989f042a5 100644 --- a/drivers/gpu/drm/asahi/g17/runtime/backend.rs +++ b/drivers/gpu/drm/asahi/g17/runtime/backend.rs @@ -297,6 +297,16 @@ impl job::Backend for Backend { } } + fn long_job_beside(&self, _packet: &Arc) -> bool { + let state = self.shared.state.lock(); + let Some(firmware) = (*state).as_deref() else { + return false; + }; + firmware.queues.compute.iter().flatten() + .filter_map(|entry| entry.queue.as_deref()) + .any(|other| other.owner() != Some(self.owner) && other.long_job_running()) + } + fn defer_timeout(&self, packet: &Arc, renewals: u8) -> bool { if packet.engine() == Engine::Compute { return self.defer_compute_timeout(packet, renewals); diff --git a/drivers/gpu/drm/asahi/g17/timeout.rs b/drivers/gpu/drm/asahi/g17/timeout.rs index 7b4ef4137b795..b83ea5d07f406 100644 --- a/drivers/gpu/drm/asahi/g17/timeout.rs +++ b/drivers/gpu/drm/asahi/g17/timeout.rs @@ -66,13 +66,18 @@ const SHARED_RENEWALS: u8 = 14; /// nothing for this long has stopped. pub(crate) const LIVENESS_NS: u64 = 5_000_000_000; -/// Renews a published job's deadline while some command retired within -/// [`LIVENESS_NS`] (`since_retired`), at most [`SHARED_RENEWALS`] times. -pub(crate) fn renew_shared(published: bool, since_retired: Option, renewals: &mut u8) -> bool { - if !published - || !since_retired.is_some_and(|since| since < LIVENESS_NS) - || *renewals >= SHARED_RENEWALS - { +/// Whether some command retired within [`LIVENESS_NS`] (`since_retired`). +pub(crate) fn retiring(since_retired: Option) -> bool { + since_retired.is_some_and(|since| since < LIVENESS_NS) +} + +/// Renews a published job's deadline while the GPU is `shared`, at most +/// [`SHARED_RENEWALS`] times. It is shared while some command retires +/// ([`retiring`]), or while a long job of another queue runs beside a long job: +/// two ray traversal dispatches of about 6 s each took 10 to 17 s side by side, and +/// neither retires anything until it ends. +pub(crate) fn renew_shared(published: bool, shared: bool, renewals: &mut u8) -> bool { + if !published || !shared || *renewals >= SHARED_RENEWALS { return false; } *renewals += 1; @@ -244,21 +249,34 @@ mod tests { fn a_live_gpu_renews_a_published_job_for_thirty_seconds() { let mut renewals = 0; for poll in 1..=14u8 { - assert!(renew_shared(true, Some(4_900_000_000), &mut renewals)); + assert!(renew_shared(true, retiring(Some(4_900_000_000)), &mut renewals)); assert_eq!(renewals, poll); } - assert!(!renew_shared(true, Some(0), &mut renewals)); + assert!(!renew_shared(true, true, &mut renewals)); assert_eq!(POLL_MS * (u32::from(renewals) + 1), 30_000); } #[test] fn a_stopped_gpu_or_unpublished_job_keeps_the_deadline() { let mut renewals = 0; - assert!(!renew_shared(true, Some(LIVENESS_NS), &mut renewals)); - assert!(!renew_shared(true, None, &mut renewals)); - assert!(!renew_shared(false, Some(0), &mut renewals)); + assert!(!renew_shared(true, retiring(Some(LIVENESS_NS)), &mut renewals)); + assert!(!renew_shared(true, retiring(None), &mut renewals)); + assert!(!renew_shared(false, true, &mut renewals)); assert_eq!(renewals, 0); } #[test] + fn a_long_job_beside_another_shares_the_gpu_without_retirements() { + // Two traversal dispatches side by side: nothing retires, yet both progress. + let mut renewals = 0; + let long_job_beside = true; + let shared = retiring(Some(LIVENESS_NS + 1)) || long_job_beside; + for poll in 1..=14u8 { + assert!(renew_shared(true, shared, &mut renewals)); + assert_eq!(renewals, poll); + } + // The thirty-second bound still ends a pair that never finishes. + assert!(!renew_shared(true, true, &mut renewals)); + } + #[test] fn exhausted_or_invalid_credit_never_wraps() { for value in 4..=u8::MAX { let mut renewals = value; From 9f52d7e79529b4d6aaaec60df0cc64b76b21a930 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Tue, 27 Oct 2026 17:57:55 +1100 Subject: [PATCH 120/184] docs: record the shared bounded long-job renewal port Distinguish the host AS-build sharing rule from gated T6040 traversal, firmware watchdog semantics and the unresolved live-render faults. Assisted-by: LLM Signed-off-by: Ryan Murray --- GAPS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/GAPS.md b/GAPS.md index dcbda1083f7c8..ccc3dc4016d59 100644 --- a/GAPS.md +++ b/GAPS.md @@ -120,6 +120,7 @@ no raw lab capture is a public implementation source. | #504 tiling first-kick | `5073eac15192` changes the word only for the 26A428 register program | Included unchanged; 25G83 retains 1 on every kick | No T6040 notification-halving claim; requires 25G83 descriptor/notification facts and controlled qualification | | #496 compute-fault witness | `9a76756bf2d7` uses event 7 words 3/4 as QID/stamp on Neo, with consumed-ring guard | Imported prerequisite, but the existing firmware graph keeps 25G83 event 7 unknown | Exact 25G83 layout, queue identity, stamp semantics and DMA/ring visibility must be established before it can release memory | | #505 closed-context teardown | `078d744a260c` kills before freeing executed unstamped render graphs, retains firmware-linked work storage, and reuses killed pairs last with QID capacity | Shared safety/retention paths included; compute-fault-driven kill depends on the graph-qualified #496 witness | Closed mixed compute/render faults, acknowledgement ordering, memory pressure and slot/QID reuse; does not claim to cure a live render's first fault | +| #499 long-job sharing | `3db24da5c830` recognizes another published, unobserved long compute job as sharing, without changing numeric renewal bounds | Included in the shared host path; T6040 bounded AS-build classification can use it, while native traversal remains gated | KUnit verifies renewals stop at the existing bound; no claim that this cures a firmware reason-2 render fault or makes skipped traversal CTS pass | | First fault | Reason and blame alone cannot identify engine progress | At halted state 1, before acknowledgement, g4 logs the blamed pair's stored stamps, free-list balance and up to four commands' pass/cursor snapshots; borrowed pairs are explicitly unavailable | Correlate exact CTS operation and original error with raw event report and snapshots; do not label reason 2 as page fault without semantics | | Raw firmware statistics | Complete 18-word statistics records already have checked ring geometry | Read-only `firmware_reports` exposes last full sample, count and host monotonic time for each role/type. Static-key `asahi_g17_report` copies trace/statistics/event words before slot return | Match payloads to independently decoded achieved-clock/power/residency and freshness/units; legacy statistics tags are a different ABI | | Idle memory floor | Neo graphics PMGR follows activity; T6040 GFX is held for firmware lifetime | g4 replaces T6040 PMGR demand with a DT-linked CLPC request, asserted at publication and released by the existing race-checked 16 ms continuous-idle clear; 32 ms CLPC hold remains | Read-only last vote/reason/time, host request and last submission, safe core power/host hold; require blank-panel quiet release and load/release cycles | From bc0370654f2c00cd5199c50d36285f24a8da9025 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Tue, 27 Oct 2026 18:06:29 +1100 Subject: [PATCH 121/184] hwmon: macsmc: Unlock fan control on firmware that keeps fans in system mode On the M4 Pro MacBook Pro (J616s) both fans start in mode 3, the firmware's own fan policy, and the SMC refuses the manual mode write of the fan_control path with an error, so fan control from Linux did nothing. Public reports for M4 describe the same refusal and the Ftst key that releases the fans to the OS. When userspace first asks for a target on a fan whose mode write is refused while it reads mode 3, set Ftst, wait for the fan to leave system mode and ask again. When the last manual fan goes back to auto, clear Ftst and wait for every fan to return to mode 3. Firmware that takes the mode write directly keeps the mode 0 restore; firmware that refuses it without an Ftst key gets the error as before. Hand every manual fan back on unbind or remove, at shutdown and before suspend, and whenever a write in the sequence fails. Targets are now clamped to the fan's minimum and maximum instead of refused, so a target can never go below the minimum. Everything stays behind the existing fan_control parameter; without it nothing is written. The new KUnit suite models locked and legacy firmware. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/hwmon/Kconfig | 7 +- drivers/hwmon/macsmc-hwmon-test.c | 262 +++++++++++++++++++++++++++++- drivers/hwmon/macsmc-hwmon.c | 261 +++++++++++++++++++++++++---- 3 files changed, 497 insertions(+), 33 deletions(-) diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 54eb5745105ff..9b6b0593310da 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1252,10 +1252,13 @@ config SENSORS_MACSMC_HWMON_KUNIT_TEST depends on SENSORS_MACSMC_HWMON && KUNIT=y default KUNIT_ALL_TESTS help - Unit tests for the channel numbering of the Apple SMC hwmon driver. + Unit tests for the channel numbering and the manual fan control of + the Apple SMC hwmon driver. Thermal zones refer to temperature channels by index, so a sensor that cannot be set up must keep its channel; the tests check that. - No SMC is needed. Only useful for kernel developers. + No SMC is needed: the fan tests model its fan keys and need + MFD_MACSMC_KUNIT_TEST for its read and write stubs. Only useful for + kernel developers. config SENSORS_MAX1111 tristate "Maxim MAX1111 Serial 8-bit ADC chip and compatibles" diff --git a/drivers/hwmon/macsmc-hwmon-test.c b/drivers/hwmon/macsmc-hwmon-test.c index e7239986ce3c9..596e904a6f3e8 100644 --- a/drivers/hwmon/macsmc-hwmon-test.c +++ b/drivers/hwmon/macsmc-hwmon-test.c @@ -1,11 +1,12 @@ // SPDX-License-Identifier: GPL-2.0-only OR MIT /* * KUnit tests for the Apple SMC hwmon driver: channel numbering of the - * temperature sensors, which thermal zones depend on. + * temperature sensors, which thermal zones depend on, and manual fan control. * * Included by macsmc-hwmon.c. */ +#include #include /* What macsmc_hwmon_create_sensor() leaves behind for one node. */ @@ -84,4 +85,261 @@ static struct kunit_suite macsmc_hwmon_test_suite = { .test_cases = macsmc_hwmon_test_cases, }; -kunit_test_suite(macsmc_hwmon_test_suite); +/* + * Manual fan control against a model of the SMC fan keys. In "locked" firmware + * the fans start in system mode (3) and refuse manual mode until Ftst is 1; + * Ftst back to 0 returns them to system mode. "Legacy" firmware takes the mode + * write directly. The model needs the SMC read/write stubs of + * CONFIG_MFD_MACSMC_KUNIT_TEST. + */ +#define FAN_TEST_F1350 0x44a8c000 /* 1350.0f */ +#define FAN_TEST_F2500 0x451c4000 /* 2500.0f */ +#define FAN_TEST_F5777 0x45b48800 /* 5777.0f */ + +struct macsmc_fan_test { + struct apple_smc smc; + struct kunit *test; + struct macsmc_hwmon hwmon; + struct macsmc_hwmon_fan fans[2]; + bool legacy; + u8 mode[2]; + u32 target[2]; + u8 ftst; + int ftst_writes; + int refused; + smc_key fail_key; +}; + +static int fan_test_index(smc_key key) +{ + return ((key >> 16) & 0xff) - '0'; +} + +static int fan_test_read(struct apple_smc *smc, smc_key key, void *buf, size_t size) +{ + struct macsmc_fan_test *t = container_of(smc, struct macsmc_fan_test, smc); + u32 value; + int i = fan_test_index(key); + + switch (key & 0xff00ffff) { + case __SMC_KEY('F', 0, 'M', 'd'): + value = t->mode[i]; + break; + case __SMC_KEY('F', 0, 'M', 'n'): + value = FAN_TEST_F1350; + break; + case __SMC_KEY('F', 0, 'M', 'x'): + value = FAN_TEST_F5777; + break; + case __SMC_KEY('F', 0, 'T', 'g'): + value = t->target[i]; + break; + default: + KUNIT_FAIL(t->test, "unexpected read of %08x", key); + return -EINVAL; + } + memcpy(buf, &value, size); + return size; +} + +static int fan_test_write(struct apple_smc *smc, smc_key key, const void *buf, size_t size) +{ + struct macsmc_fan_test *t = container_of(smc, struct macsmc_fan_test, smc); + u32 value = 0; + int i = fan_test_index(key); + + memcpy(&value, buf, min(size, sizeof(value))); + if (key == t->fail_key) + return -EIO; + + if (key == SMC_KEY(Ftst)) { + t->ftst_writes++; + t->ftst = value; + for (i = 0; i < ARRAY_SIZE(t->mode); i++) { + if (value && t->mode[i] == MACSMC_FAN_MODE_SYSTEM) + t->mode[i] = MACSMC_FAN_MODE_AUTO; + else if (!value) + t->mode[i] = MACSMC_FAN_MODE_SYSTEM; + } + return size; + } + + switch (key & 0xff00ffff) { + case __SMC_KEY('F', 0, 'M', 'd'): + if (!t->legacy && t->mode[i] == MACSMC_FAN_MODE_SYSTEM) { + t->refused++; + return -EIO; + } + t->mode[i] = value; + return size; + case __SMC_KEY('F', 0, 'T', 'g'): + t->target[i] = value; + return size; + } + KUNIT_FAIL(t->test, "unexpected write of %08x", key); + return -EINVAL; +} + +static void fan_test_key(struct macsmc_hwmon_sensor *sensor, const char *key, u32 type) +{ + sensor->macsmc_key = _SMC_KEY(key); + sensor->info.type_code = type; + sensor->info.flags = APPLE_SMC_READABLE | APPLE_SMC_WRITABLE; +} + +static int macsmc_fan_test_init(struct kunit *test) +{ + static const char *const keys[2][5] = { + { "F0Ac", "F0Mn", "F0Mx", "F0Tg", "F0Md" }, + { "F1Ac", "F1Mn", "F1Mx", "F1Tg", "F1Md" }, + }; + const u32 flt = __SMC_KEY('f', 'l', 't', ' '), ui8 = __SMC_KEY('u', 'i', '8', ' '); + struct macsmc_fan_test *t; + int i; + + if (!IS_ENABLED(CONFIG_MFD_MACSMC_KUNIT_TEST)) + kunit_skip(test, "needs CONFIG_MFD_MACSMC_KUNIT_TEST for the SMC stubs"); + + t = kunit_kzalloc(test, sizeof(*t), GFP_KERNEL); + if (!t) + return -ENOMEM; + t->test = test; + for (i = 0; i < 2; i++) { + fan_test_key(&t->fans[i].now, keys[i][0], flt); + fan_test_key(&t->fans[i].min, keys[i][1], flt); + fan_test_key(&t->fans[i].max, keys[i][2], flt); + fan_test_key(&t->fans[i].set, keys[i][3], flt); + fan_test_key(&t->fans[i].mode, keys[i][4], ui8); + t->mode[i] = MACSMC_FAN_MODE_SYSTEM; + } + t->hwmon.smc = &t->smc; + t->hwmon.fan.fans = t->fans; + t->hwmon.fan.count = 2; + t->hwmon.fan.unlock_key = SMC_KEY(Ftst); + mutex_init(&t->hwmon.fan.lock); + test->priv = t; + fan_control = true; + kunit_activate_static_stub(test, apple_smc_read, fan_test_read); + kunit_activate_static_stub(test, apple_smc_write, fan_test_write); + return 0; +} + +static void macsmc_fan_test_exit(struct kunit *test) +{ + fan_control = false; +} + +static void fan_test_released(struct kunit *test, struct macsmc_fan_test *t) +{ + KUNIT_EXPECT_EQ(test, t->ftst, 0); + KUNIT_EXPECT_EQ(test, t->mode[0], MACSMC_FAN_MODE_SYSTEM); + KUNIT_EXPECT_EQ(test, t->mode[1], MACSMC_FAN_MODE_SYSTEM); + KUNIT_EXPECT_EQ(test, t->hwmon.fan.manual, 0); + KUNIT_EXPECT_FALSE(test, t->hwmon.fan.unlocked); + KUNIT_EXPECT_FALSE(test, t->fans[0].manual); +} + +/* Locked firmware: unlock on the first manual request, release on auto. */ +static void macsmc_fan_test_unlock_and_release(struct kunit *test) +{ + struct macsmc_fan_test *t = test->priv; + + KUNIT_ASSERT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 0, 2500), 0); + KUNIT_EXPECT_EQ(test, t->ftst, 1); + KUNIT_EXPECT_EQ(test, t->mode[0], MACSMC_FAN_MODE_MANUAL); + KUNIT_EXPECT_EQ(test, t->target[0], FAN_TEST_F2500); + KUNIT_EXPECT_EQ(test, t->hwmon.fan.manual, 1); + + /* A second target needs no new unlock. */ + KUNIT_ASSERT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 0, 2500), 0); + KUNIT_EXPECT_EQ(test, t->ftst_writes, 1); + + KUNIT_ASSERT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 0, 0), 0); + fan_test_released(test, t); + KUNIT_EXPECT_EQ(test, t->ftst_writes, 2); +} + +/* Targets are clamped to [min, max]; negative ones are refused. */ +static void macsmc_fan_test_clamp(struct kunit *test) +{ + struct macsmc_fan_test *t = test->priv; + + KUNIT_ASSERT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 0, 100), 0); + KUNIT_EXPECT_EQ(test, t->target[0], FAN_TEST_F1350); + KUNIT_ASSERT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 0, 100000), 0); + KUNIT_EXPECT_EQ(test, t->target[0], FAN_TEST_F5777); + KUNIT_EXPECT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 0, -1), -EINVAL); + KUNIT_ASSERT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 0, 0), 0); + fan_test_released(test, t); +} + +/* A failed target write hands the fan straight back. */ +static void macsmc_fan_test_target_write_fails(struct kunit *test) +{ + struct macsmc_fan_test *t = test->priv; + + t->fail_key = SMC_KEY(F0Tg); + KUNIT_EXPECT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 0, 2500), -EIO); + fan_test_released(test, t); +} + +/* Two manual fans: the unlock is released only with the last one. */ +static void macsmc_fan_test_two_fans(struct kunit *test) +{ + struct macsmc_fan_test *t = test->priv; + + KUNIT_ASSERT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 0, 2500), 0); + KUNIT_ASSERT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 1, 2500), 0); + KUNIT_EXPECT_EQ(test, t->hwmon.fan.manual, 2); + KUNIT_ASSERT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 0, 0), 0); + KUNIT_EXPECT_EQ(test, t->ftst, 1); + KUNIT_EXPECT_EQ(test, t->mode[1], MACSMC_FAN_MODE_MANUAL); + /* Removal, shutdown and suspend all restore through this. */ + macsmc_hwmon_fans_restore(&t->hwmon); + fan_test_released(test, t); +} + +/* Locked firmware without the unlock key: refused, nothing else written. */ +static void macsmc_fan_test_no_unlock_key(struct kunit *test) +{ + struct macsmc_fan_test *t = test->priv; + + t->hwmon.fan.unlock_key = 0; + KUNIT_EXPECT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 0, 2500), -EIO); + KUNIT_EXPECT_EQ(test, t->ftst_writes, 0); + KUNIT_EXPECT_EQ(test, t->mode[0], MACSMC_FAN_MODE_SYSTEM); + KUNIT_EXPECT_EQ(test, t->hwmon.fan.manual, 0); +} + +/* Firmware that takes the mode directly keeps the mode 0 restore. */ +static void macsmc_fan_test_legacy(struct kunit *test) +{ + struct macsmc_fan_test *t = test->priv; + + t->legacy = true; + t->mode[0] = MACSMC_FAN_MODE_AUTO; + KUNIT_ASSERT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 0, 2500), 0); + KUNIT_EXPECT_EQ(test, t->mode[0], MACSMC_FAN_MODE_MANUAL); + KUNIT_ASSERT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 0, 0), 0); + KUNIT_EXPECT_EQ(test, t->mode[0], MACSMC_FAN_MODE_AUTO); + KUNIT_EXPECT_EQ(test, t->ftst_writes, 0); + KUNIT_EXPECT_EQ(test, t->refused, 0); +} + +static struct kunit_case macsmc_fan_test_cases[] = { + KUNIT_CASE(macsmc_fan_test_unlock_and_release), + KUNIT_CASE(macsmc_fan_test_clamp), + KUNIT_CASE(macsmc_fan_test_target_write_fails), + KUNIT_CASE(macsmc_fan_test_two_fans), + KUNIT_CASE(macsmc_fan_test_no_unlock_key), + KUNIT_CASE(macsmc_fan_test_legacy), + {} +}; + +static struct kunit_suite macsmc_fan_test_suite = { + .name = "macsmc-hwmon-fan", + .init = macsmc_fan_test_init, + .exit = macsmc_fan_test_exit, + .test_cases = macsmc_fan_test_cases, +}; +kunit_test_suites(&macsmc_hwmon_test_suite, &macsmc_fan_test_suite); diff --git a/drivers/hwmon/macsmc-hwmon.c b/drivers/hwmon/macsmc-hwmon.c index 06d2c6e2d0ac3..76f07add003d3 100644 --- a/drivers/hwmon/macsmc-hwmon.c +++ b/drivers/hwmon/macsmc-hwmon.c @@ -23,12 +23,15 @@ #include #include #include +#include #include #include #include #include +#include #include #include +#include #include #include #include @@ -189,10 +192,30 @@ struct macsmc_hwmon_sensors { u32 count; }; +/* Fan mode key values */ +#define MACSMC_FAN_MODE_AUTO 0 +#define MACSMC_FAN_MODE_MANUAL 1 +/* The firmware's own fan policy; it refuses a manual mode request. */ +#define MACSMC_FAN_MODE_SYSTEM 3 + +/* How long the firmware gets to leave or re-enter its system mode */ +#define MACSMC_FAN_MODE_TIMEOUT_MS 10000 +#define MACSMC_FAN_MODE_POLL_MS 100 + struct macsmc_hwmon_fans { struct hwmon_channel_info channel_info; struct macsmc_hwmon_fan *fans; u32 count; + /* Serialises manual fan control and its unlock. */ + struct mutex lock; + /* + * Firmware that keeps its fans in system mode releases them to the + * OS only while this key is 1. Zero if the SMC has no such key. + */ + smc_key unlock_key; + bool unlocked; + /* Fans in manual mode */ + u32 manual; }; struct macsmc_hwmon { @@ -466,57 +489,199 @@ static int macsmc_hwmon_read_fan(struct macsmc_hwmon *hwmon, u32 attr, int chan, } } -static int macsmc_hwmon_write_fan(struct device *dev, u32 attr, int channel, - long val) +static int macsmc_hwmon_fan_read_mode(struct macsmc_hwmon *hwmon, + struct macsmc_hwmon_fan *fan, u8 *mode) { - struct macsmc_hwmon *hwmon = dev_get_drvdata(dev); + return apple_smc_read_u8(hwmon->smc, fan->mode.macsmc_key, mode); +} + +/* + * Hand the fans back to the firmware once no fan is manual: clear the unlock + * key and wait for every fan to return to system mode. Called with the fan + * lock held. + */ +static int macsmc_hwmon_fans_relock(struct macsmc_hwmon *hwmon) +{ + unsigned long timeout; + u32 i; + u8 mode; + int ret; + + ret = apple_smc_write_u8(hwmon->smc, hwmon->fan.unlock_key, 0); + if (ret < 0) { + dev_err(hwmon->dev, "cannot hand the fans back to the SMC (%d)\n", ret); + return ret; + } + hwmon->fan.unlocked = false; + + timeout = jiffies + msecs_to_jiffies(MACSMC_FAN_MODE_TIMEOUT_MS); + for (i = 0; i < hwmon->fan.count; i++) { + struct macsmc_hwmon_fan *fan = &hwmon->fan.fans[i]; + + if (!fan->mode.macsmc_key) + continue; + for (;;) { + ret = macsmc_hwmon_fan_read_mode(hwmon, fan, &mode); + if (ret) + return ret; + if (mode == MACSMC_FAN_MODE_SYSTEM) + break; + if (time_after(jiffies, timeout)) { + dev_warn(hwmon->dev, "%s stays in mode %u after release\n", + fan->label, mode); + return -ETIMEDOUT; + } + msleep(MACSMC_FAN_MODE_POLL_MS); + } + } + + return 0; +} + +/* Return a fan to firmware control. Called with the fan lock held. */ +static int macsmc_hwmon_fan_auto(struct macsmc_hwmon *hwmon, + struct macsmc_hwmon_fan *fan) +{ + int ret, relock; + + if (!fan->manual) + return 0; + + ret = macsmc_hwmon_write_key(hwmon->smc, &fan->mode, MACSMC_FAN_MODE_AUTO); + fan->manual = false; + hwmon->fan.manual--; + + if (hwmon->fan.unlocked && !hwmon->fan.manual) { + relock = macsmc_hwmon_fans_relock(hwmon); + if (relock) + return relock; + } + + return ret < 0 ? ret : 0; +} + +/* + * Put a fan in manual mode. Firmware in system mode refuses that until the + * unlock key is set; it then leaves system mode and accepts the request. + * Called with the fan lock held. + */ +static int macsmc_hwmon_fan_manual(struct macsmc_hwmon *hwmon, + struct macsmc_hwmon_fan *fan) +{ + unsigned long timeout; + u8 mode; + int ret; + + if (fan->manual) + return 0; + + ret = macsmc_hwmon_write_key(hwmon->smc, &fan->mode, MACSMC_FAN_MODE_MANUAL); + if (ret < 0 && hwmon->fan.unlock_key && !hwmon->fan.unlocked && + !macsmc_hwmon_fan_read_mode(hwmon, fan, &mode) && + mode == MACSMC_FAN_MODE_SYSTEM) { + ret = apple_smc_write_u8(hwmon->smc, hwmon->fan.unlock_key, 1); + if (ret < 0) { + /* The key may have been set anyway; clear it. */ + apple_smc_write_u8(hwmon->smc, hwmon->fan.unlock_key, 0); + return ret; + } + hwmon->fan.unlocked = true; + + /* Ask again once the fan has left system mode. */ + timeout = jiffies + msecs_to_jiffies(MACSMC_FAN_MODE_TIMEOUT_MS); + for (;;) { + ret = macsmc_hwmon_fan_read_mode(hwmon, fan, &mode); + if (!ret && mode == MACSMC_FAN_MODE_SYSTEM) + ret = -EBUSY; + else if (!ret) + ret = macsmc_hwmon_write_key(hwmon->smc, &fan->mode, + MACSMC_FAN_MODE_MANUAL); + if (ret >= 0 || time_after(jiffies, timeout)) + break; + msleep(MACSMC_FAN_MODE_POLL_MS); + } + if (ret < 0) { + dev_err(hwmon->dev, "%s refused manual mode after unlock (%d)\n", + fan->label, ret); + if (!hwmon->fan.manual) + macsmc_hwmon_fans_relock(hwmon); + return ret; + } + } + if (ret < 0) + return ret; + + fan->manual = true; + hwmon->fan.manual++; + return 0; +} + +static int macsmc_hwmon_set_fan(struct macsmc_hwmon *hwmon, int channel, long val) +{ + struct macsmc_hwmon_fan *fan = &hwmon->fan.fans[channel]; long min, max; int ret; - if (!fan_control || hwmon->fan.fans[channel].mode.macsmc_key == 0) + if (!fan_control || fan->mode.macsmc_key == 0) return -EOPNOTSUPP; + if (val < 0) + return -EINVAL; + + guard(mutex)(&hwmon->fan.lock); + + /* 0 hands the fan back to the firmware. */ + if (!val) + return macsmc_hwmon_fan_auto(hwmon, fan); /* * The SMC does no sanity checks on requested fan speeds, so we need to. */ - ret = macsmc_hwmon_read_key(hwmon->smc, &hwmon->fan.fans[channel].min, - 1, &min); + ret = macsmc_hwmon_read_key(hwmon->smc, &fan->min, 1, &min); if (ret) return ret; - ret = macsmc_hwmon_read_key(hwmon->smc, &hwmon->fan.fans[channel].max, - 1, &max); + ret = macsmc_hwmon_read_key(hwmon->smc, &fan->max, 1, &max); if (ret) return ret; - if (val >= min && val <= max) { - if (!hwmon->fan.fans[channel].manual) { - /* Write 1 to mode key for manual control */ - ret = macsmc_hwmon_write_key(hwmon->smc, - &hwmon->fan.fans[channel].mode, 1); - if (ret < 0) - return ret; + if (min <= 0 || max < min) + return -EIO; + val = clamp(val, min, max); - hwmon->fan.fans[channel].manual = true; - } - return macsmc_hwmon_write_key(hwmon->smc, - &hwmon->fan.fans[channel].set, val); - } else if (!val) { - if (hwmon->fan.fans[channel].manual) { - ret = macsmc_hwmon_write_key(hwmon->smc, - &hwmon->fan.fans[channel].mode, 0); - if (ret < 0) - return ret; + ret = macsmc_hwmon_fan_manual(hwmon, fan); + if (ret) + return ret; - hwmon->fan.fans[channel].manual = false; - } - } else { - return -EINVAL; + ret = macsmc_hwmon_write_key(hwmon->smc, &fan->set, val); + if (ret < 0) { + macsmc_hwmon_fan_auto(hwmon, fan); + return ret; } return 0; } +static int macsmc_hwmon_write_fan(struct device *dev, u32 attr, int channel, + long val) +{ + return macsmc_hwmon_set_fan(dev_get_drvdata(dev), channel, val); +} + +/* Hand every manual fan back to the firmware. */ +static void macsmc_hwmon_fans_restore(struct macsmc_hwmon *hwmon) +{ + u32 i; + + guard(mutex)(&hwmon->fan.lock); + for (i = 0; i < hwmon->fan.count; i++) + macsmc_hwmon_fan_auto(hwmon, &hwmon->fan.fans[i]); +} + +static void macsmc_hwmon_fans_restore_action(void *data) +{ + macsmc_hwmon_fans_restore(data); +} + static int macsmc_hwmon_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long *val) { @@ -1343,6 +1508,11 @@ static int macsmc_hwmon_probe(struct platform_device *pdev) hwmon->dev = &pdev->dev; hwmon->smc = smc; hwmon->register_thermal_zones = true; + platform_set_drvdata(pdev, hwmon); + + ret = devm_mutex_init(&pdev->dev, &hwmon->fan.lock); + if (ret) + return ret; ret = macsmc_hwmon_populate_sensors(hwmon, hwmon->dev->of_node); if (ret) { @@ -1372,6 +1542,21 @@ static int macsmc_hwmon_probe(struct platform_device *pdev) if (ret) return ret; + if (hwmon->fan.count) { + struct apple_smc_key_info info; + + if (!apple_smc_get_key_info(smc, SMC_KEY(Ftst), &info) && + info.type_code == __SMC_KEY('u', 'i', '8', ' ') && + (info.flags & APPLE_SMC_WRITABLE)) + hwmon->fan.unlock_key = SMC_KEY(Ftst); + + /* Runs after the hwmon device is gone, so no write races it. */ + ret = devm_add_action_or_reset(&pdev->dev, + macsmc_hwmon_fans_restore_action, hwmon); + if (ret) + return ret; + } + hwmon->chip_info.ops = &macsmc_hwmon_ops; hwmon->chip_info.info = (const struct hwmon_channel_info *const *)&hwmon->channel_infos; @@ -1407,11 +1592,29 @@ static const struct of_device_id macsmc_hwmon_of_table[] = { }; MODULE_DEVICE_TABLE(of, macsmc_hwmon_of_table); +static void macsmc_hwmon_shutdown(struct platform_device *pdev) +{ + struct macsmc_hwmon *hwmon = platform_get_drvdata(pdev); + + if (hwmon) + macsmc_hwmon_fans_restore(hwmon); +} + +static int macsmc_hwmon_suspend(struct device *dev) +{ + macsmc_hwmon_fans_restore(dev_get_drvdata(dev)); + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(macsmc_hwmon_pm_ops, macsmc_hwmon_suspend, NULL); + static struct platform_driver macsmc_hwmon_driver = { .probe = macsmc_hwmon_probe, + .shutdown = macsmc_hwmon_shutdown, .driver = { .name = "macsmc-hwmon", .of_match_table = macsmc_hwmon_of_table, + .pm = pm_sleep_ptr(&macsmc_hwmon_pm_ops), }, }; module_platform_driver(macsmc_hwmon_driver); From 8bce44c04ac01e54b8425c881ba9cf3155fd23e5 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Tue, 27 Oct 2026 18:35:35 +1100 Subject: [PATCH 122/184] drm/asahi: freeze render progress before acknowledging G17 recovery Resolve slot attribution while firmware is halted in state 1, alongside its key and monitor snapshot. Previously classify() read pass timestamps after the 3->0 acknowledgement, when resumed work could change them and make a later-started peer appear responsible for the original fault. Keep the captured attribution through the handshake. Unknown 25G83 slot keys still preserve firmware blame; no new retirement or release witness is introduced. This fixes the shared sampling race, not the unqualified T6040 reason-2 first faults. KUnit drives the real service() handshake and rejects a pass read after the first acknowledgement. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/gpu/drm/asahi/g17/recovery.rs | 109 +++++++++++++++++++++++++- 1 file changed, 106 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/asahi/g17/recovery.rs b/drivers/gpu/drm/asahi/g17/recovery.rs index 7787485fa713a..dcd8d59e4c412 100644 --- a/drivers/gpu/drm/asahi/g17/recovery.rs +++ b/drivers/gpu/drm/asahi/g17/recovery.rs @@ -250,6 +250,11 @@ pub(super) fn service( if let Some(qid) = request.blamed { host.report_blamed_render(qid); } + // The keys, monitor and pass-start evidence must describe the same halted + // instant. Firmware may update pass timestamps as soon as state 3 is + // acknowledged; sampling them during classification can blame a bystander. + let attribution = sources.attribute(request.blamed, + |qid, stamp| host.render_pass_started(qid, stamp)); let target_error = host.publish_recovery_targets(); host.transition(1, 2)?; let mut recovered = false; @@ -280,7 +285,7 @@ pub(super) fn service( host.recovery_mut().pending = None; let (listed, exact, fallback) = (entries.listed, entries.exact, entries.fallback); - let (cancelled, blame) = classify(host, entries, request.blamed, &mut sources); + let (cancelled, blame) = classify(host, entries, request.blamed, attribution); if listed != 0 { pr_warn!( "G17: GPU recovery: {} commands were in flight ({} matched by stamp, {} by queue); they fail instead of completing\n", @@ -335,10 +340,9 @@ fn read_entries(host: &mut H) -> Entries { /// Also returns whether the firmware's blamed QID is still guilty after attribution: a blamed /// render that had not started while others had (or with no culprit at all) is innocent, and its /// work is spared like that of every other innocent VM instead of failing with EIO. -fn classify(queues: &mut Q, entries: Entries, blamed: Option, sources: &mut Sources) -> (u128, bool) { +fn classify(queues: &mut Q, entries: Entries, blamed: Option, attribution: Attribution) -> (u128, bool) { let mut guilty_qids = entries.guilty | blamed.map_or(0, |qid| 1u128 << qid); let cancelled = entries.cancelled; - let attribution = sources.attribute(blamed, |qid, stamp| queues.render_pass_started(qid, stamp)); match attribution { Attribution::Firmware => {} Attribution::Queues(qids) => guilty_qids = qids, @@ -398,3 +402,102 @@ fn classify(queues: &mut Q, entries: Entries, blamed: Option, sou }); (cancelled & !(guilty_qids & !collateral) & spared, blame) } + + +#[kernel::macros::kunit_tests(asahi_g17_recovery_snapshot)] +mod tests { + use super::*; + use core::cell::Cell; + + struct Halted { + recovery: State, + state: Cell, + reads: Cell, + known_keys: bool, + blamed: bool, + } + + impl Halted { + fn new(known_keys: bool) -> Self { + let mut recovery = State::new(); + assert!(recovery.request(0, 38).unwrap()); + Self { recovery, state: Cell::new(1), reads: Cell::new(0), + known_keys, blamed: false } + } + } + + impl Memory for Halted { + fn recovery_state(&self) -> Result { Ok(self.state.get()) } + fn host_recovery(&self) -> Result { Ok(0) } + fn fault_sources(&self) -> Result { + assert_eq!(self.state.get(), 1); + let mut sources = Sources::sample(3, |slot| { + Some((if self.known_keys { ((38 + slot as u64) << 40) | 1 } else { 0 }, 0)) + }); + sources.reason = Some(2); + Ok(sources) + } + fn transition(&self, expected: u32, next: u32) -> Result { + assert_eq!(self.state.get(), expected); + self.state.set(next); + Ok(()) + } + fn clear_timestamps(&self) -> Result { panic!("active render scheduler") } + fn entry(&self, _index: usize) -> Result<(u32, u32)> { + assert_eq!(self.state.get(), 3); + Ok((0, 0)) + } + fn wait_tick(&self) { + assert_eq!(self.state.get(), 2); + self.state.set(3); + } + } + + impl Queues for Halted { + fn render_scheduler_active(&self) -> bool { true } + fn render_pass_started(&self, qid: u8, _stamp: u64) -> Option { + // This is the regression oracle: state 0/2/3 is too late. A + // resumed peer may have started after the fault we are attributing. + assert_eq!(self.state.get(), 1); + self.reads.set(self.reads.get() + 1); + Some(qid == 39) + } + fn publish_recovery_targets(&self) -> Option { None } + fn oldest_spared(&self, _qid: u8) -> Option { None } + fn visit_owners(&self, _qid: u8, _visit: &mut dyn FnMut(&Arc)) {} + fn classify_work(&mut self, _class: &dyn Fn(&Arc) -> Class) -> u128 { 0 } + fn note_interrupted(&mut self, _qid: u8, _stamp: u32) -> Interrupted { Interrupted::None } + fn settle_blamed_render(&mut self, qid: u8, _error: Error) { + assert_eq!(qid, 38); + self.blamed = true; + } + fn settle_cancelled_renders(&mut self, _qids: u128) {} + fn rescan_compute(&mut self) -> Result { Ok(()) } + fn release_retired_compute(&mut self) {} + fn announce_withheld(&mut self) {} + } + + impl Host for Halted { + fn recovery(&self) -> &State { &self.recovery } + fn recovery_mut(&mut self) -> &mut State { &mut self.recovery } + } + + #[test] + fn pass_evidence_is_frozen_before_the_first_acknowledgement() { + let mut host = Halted::new(true); + service(&mut host, || false).unwrap(); + assert_eq!(host.reads.get(), 2); + assert_eq!(host.state.get(), 0); + assert_eq!(host.recovery.generation(), 1); + assert!(!host.blamed); + } + + #[test] + fn unknown_25g83_keys_still_preserve_firmware_blame() { + let mut host = Halted::new(false); + service(&mut host, || false).unwrap(); + assert_eq!(host.reads.get(), 0); + assert!(host.blamed); + assert_eq!(host.state.get(), 0); + } +} From 0d753834db34dc82c6f4e7198cc72d48e8e87673 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Tue, 27 Oct 2026 18:37:11 +1100 Subject: [PATCH 123/184] drm/asahi: record halted tiler capacity alongside G17 recovery progress Record the declared TVB state, block inventory and high-water values, plus the blamed pair's bounded render geometry/GTP layout and pending parameter-buffer growth. All reads run in the existing state-1 snapshot; there is no allocation, MMIO access or high-water reset. These observations distinguish a full tiler, a delayed grow request and an unstamped unstarted pass in the large point/barrier CTS cases. They are diagnostics, not a timeout extension or a claim that reason 2 means TVB exhaustion. Keep raw captures in private qualification evidence. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/gpu/drm/asahi/g17/buffer.rs | 16 ++++++++++++++++ drivers/gpu/drm/asahi/g17/queue/render.rs | 6 ++++++ drivers/gpu/drm/asahi/g17/runtime/render/grow.rs | 4 ++-- .../gpu/drm/asahi/g17/runtime/render/recovery.rs | 2 ++ 4 files changed, 26 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/asahi/g17/buffer.rs b/drivers/gpu/drm/asahi/g17/buffer.rs index 8f3a03fbb8dba..fd6460db2d1fb 100644 --- a/drivers/gpu/drm/asahi/g17/buffer.rs +++ b/drivers/gpu/drm/asahi/g17/buffer.rs @@ -677,6 +677,22 @@ impl Manager { pub(crate) fn blocks(&self) -> usize { self.tvb.blocks() } + /// Read-only diagnostic while firmware is halted. In particular this + /// does not reset the high-water counter as automatic_target() does. + pub(crate) fn report_recovery(&self) { + let read = |offset| -> Result { + Ok(self.graph.word(offset)?.load(Ordering::Relaxed)) + }; + pr_warn!("G17: recovery tvb id={} blocks={} cursors={:?} pages={:?} capacity={:?} producer={:?} consumer={:?} max_pages={:?} reset={:?} initialized={}\n", + self.buffer_id, self.blocks(), self.cursors(), + read(core::mem::offset_of!(fw::State, pages)), + read(core::mem::offset_of!(fw::State, block_capacity)), + read(core::mem::offset_of!(fw::State, producer)), + read(core::mem::offset_of!(fw::State, consumer)), + read(fw::CONTROL + core::mem::offset_of!(fw::Control, max_pages)), + read(fw::CONTROL + core::mem::offset_of!(fw::Control, reset)), + self.initialized); + } pub(crate) fn state_va(&self) -> u64 { self.graph.gpu_va() } diff --git a/drivers/gpu/drm/asahi/g17/queue/render.rs b/drivers/gpu/drm/asahi/g17/queue/render.rs index 921f6eb3fb6eb..e6123d39e4957 100644 --- a/drivers/gpu/drm/asahi/g17/queue/render.rs +++ b/drivers/gpu/drm/asahi/g17/queue/render.rs @@ -1039,7 +1039,13 @@ impl Pair { pr_warn!("G17: recovery render slot={} qids={:?} active={} prepared={} quarantined={} stamps={:?} freelist={:?}\n", self.slot, self.qids(), self.active.len(), self.prepared.is_some(), self.quarantined, stamps, self.free_list.counters()); + self.memory.manager.report_recovery(); for active in self.active.iter().take(4) { + if let Validated::Render { pass, .. } = &active.packet.command { + pr_warn!("G17: recovery render geometry ordinal={} width={} height={} layers={} layout={:?}\n", + active.ticket.ordinal, pass.width, pass.height, pass.layers, + target::Layout::new(pass, self.clusters, self.variant.programs)); + } pr_warn!("G17: recovery render ordinal={} published={} ctx={} seq={} snapshot={:?}\n", active.ticket.ordinal, active.outer_published, active.packet.context.id(), active.packet.order.sequence, observation::snapshot(&self.memory, active.ticket.ordinal)); diff --git a/drivers/gpu/drm/asahi/g17/runtime/render/grow.rs b/drivers/gpu/drm/asahi/g17/runtime/render/grow.rs index 20569af84bc7b..277534328406e 100644 --- a/drivers/gpu/drm/asahi/g17/runtime/render/grow.rs +++ b/drivers/gpu/drm/asahi/g17/runtime/render/grow.rs @@ -14,8 +14,8 @@ use kernel::prelude::*; const RETRY_LIMIT: u16 = 1000; pub(super) struct Grow { - requests: KVec, - active: Option, + pub(super) requests: KVec, + pub(super) active: Option, pub(super) reply: Option<(TvbGrowRequest, bool, u16)>, release_attempts: u16, } diff --git a/drivers/gpu/drm/asahi/g17/runtime/render/recovery.rs b/drivers/gpu/drm/asahi/g17/runtime/render/recovery.rs index 20f537ab69598..7a6d987591c76 100644 --- a/drivers/gpu/drm/asahi/g17/runtime/render/recovery.rs +++ b/drivers/gpu/drm/asahi/g17/runtime/render/recovery.rs @@ -7,6 +7,8 @@ use kernel::prelude::*; impl Registry { pub(in crate::g17::runtime) fn report_render_recovery(&self, qid: u8, port: &crate::g17::port::Port) { + pr_warn!("G17: recovery grow pending={} active={:?} reply={:?}\n", + self.render.grow.requests.len(), self.render.grow.active, self.render.grow.reply); for entry in self.render.entries.iter().flatten().filter(|entry| { entry.reservation.ids.iter().any(|id| id.qid() == qid) }) { From 84f47efb93f38852c0686537e0e1ff298b7de8d4 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Tue, 27 Oct 2026 18:37:12 +1100 Subject: [PATCH 124/184] drm/asahi: trace G17 input conversion and scheduler hop boundaries Add static-key input-resolution and scheduler lifecycle traces. Join these to existing publish/complete and dma_fence/sched events using the engine/context/sequence and fence identity to separate conversion, inline setup, dependency/capacity waits, scheduler dispatch and firmware execution in multi-queue signal hops. Counters are read only when each tracepoint is enabled. The trace path does not poll a fence, allocate, request GPU power or alter scheduling. These are measurement boundaries; no unmeasured latency saving is claimed. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/gpu/drm/asahi/g17/job.rs | 16 ++++++-- drivers/gpu/drm/asahi/g17/queue/mod.rs | 6 +++ drivers/gpu/drm/asahi/g17/trace.c | 15 +++++++ drivers/gpu/drm/asahi/g17/trace.h | 56 ++++++++++++++++++++++++++ drivers/gpu/drm/asahi/g17/trace.rs | 30 ++++++++++++++ drivers/gpu/drm/asahi/g17/trace_api.h | 4 ++ 6 files changed, 124 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/asahi/g17/job.rs b/drivers/gpu/drm/asahi/g17/job.rs index 61033aa9e52b4..ad9adcecf0f81 100644 --- a/drivers/gpu/drm/asahi/g17/job.rs +++ b/drivers/gpu/drm/asahi/g17/job.rs @@ -363,10 +363,17 @@ impl sched::JobImpl for Job { } fn prepare(job: &mut Self) -> Option { - job.backend.prepare(&job.packet) + super::trace::schedule(&job.packet, 5, 0); + let wait = job.backend.prepare(&job.packet); + super::trace::schedule(&job.packet, 6, i32::from(wait.is_some())); + wait } fn run(job: &mut Self) -> Result> { - job.backend.publish(job.packet.clone()).map(Some) + super::trace::schedule(&job.packet, 3, 0); + let result = job.backend.publish(job.packet.clone()).map(Some); + super::trace::schedule(&job.packet, 4, + result.as_ref().err().map_or(0, |error| error.to_errno())); + result } fn timed_out(job: &mut Timeout, finished: bool) -> sched::Status { // The scheduler retains the detached job throughout this callback. Keep @@ -430,6 +437,7 @@ impl Scheduler { early: bool, publication: Option, ) -> Result { + super::trace::schedule(&packet, 0, dependencies.len() as i32); let completed = packet.completion.fence(); let host_timestamps = packet.completion.has_timestamps(); // Only commands which actually write timestamps become host-prefix @@ -462,7 +470,9 @@ impl Scheduler { }; // All fallible scheduler setup precedes early hardware publication. The // scheduler still owns watchdog, credits and cancellation after push. - if !(early && self.backend.try_early(&packet)) { + let inline = early && self.backend.try_early(&packet); + super::trace::schedule(&packet, if inline { 1 } else { 2 }, 0); + if !inline { if packet.completion.needs_source_ready() { packet.completion.set_source_ready(ready.clone()); } diff --git a/drivers/gpu/drm/asahi/g17/queue/mod.rs b/drivers/gpu/drm/asahi/g17/queue/mod.rs index 16891524e25a0..d22c0f617f374 100644 --- a/drivers/gpu/drm/asahi/g17/queue/mod.rs +++ b/drivers/gpu/drm/asahi/g17/queue/mod.rs @@ -212,6 +212,9 @@ impl Queue { } // Only convert inputs when no independent host-prefix wait could keep // the retained source bookkeeping queued behind an external signal. + if engine == Engine::Compute { + super::trace::inputs(self.context.id(), order.sequence, 0, inputs.len(), None); + } let sources = if engine == Engine::Compute && !inputs.is_empty() && !destinations.has_outputs() && self.frontiers[0].iter().all(|(sequence, fences)| @@ -225,6 +228,9 @@ impl Queue { failure.as_ref().is_none_or(|(sequence, _)| !Order::contains(order.wait_through[index], *sequence))) { self.backend.input_sources(inputs) } else { None }; + if engine == Engine::Compute { + super::trace::inputs(self.context.id(), order.sequence, 1, inputs.len(), sources.as_deref()); + } // Only earlier render ordinals covered by the explicit logical prefix // can delay host publication. These links never become GPU dependencies. diff --git a/drivers/gpu/drm/asahi/g17/trace.c b/drivers/gpu/drm/asahi/g17/trace.c index 59f0094d129e9..62307838bb36e 100644 --- a/drivers/gpu/drm/asahi/g17/trace.c +++ b/drivers/gpu/drm/asahi/g17/trace.c @@ -70,3 +70,18 @@ void asahi_g17_trace_report(u8 role, u8 ring, const u32 *words) { trace_asahi_g17_report(role, ring, words, __arch_counter_get_cntvct()); } + +void asahi_g17_trace_inputs(u32 ctx, u64 seq, u8 phase, u32 inputs, u32 sources, + u32 waits, bool converted) +{ + if (trace_asahi_g17_inputs_enabled()) + trace_asahi_g17_inputs(ctx, seq, phase, inputs, sources, waits, + converted, __arch_counter_get_cntvct()); +} + +void asahi_g17_trace_schedule(u8 engine, u32 ctx, u64 seq, u8 phase, s32 detail) +{ + if (trace_asahi_g17_schedule_enabled()) + trace_asahi_g17_schedule(engine, ctx, seq, phase, detail, + __arch_counter_get_cntvct()); +} diff --git a/drivers/gpu/drm/asahi/g17/trace.h b/drivers/gpu/drm/asahi/g17/trace.h index 36e1b445f1b77..c8cecbcc7f400 100644 --- a/drivers/gpu/drm/asahi/g17/trace.h +++ b/drivers/gpu/drm/asahi/g17/trace.h @@ -158,6 +158,62 @@ TRACE_EVENT(asahi_g17_event, TP_printk("phase=%u counter=%llu", __entry->phase, __entry->counter) ); +/* Input conversion: phase 0 entry, 1 result. converted=0 also covers no live input. */ +TRACE_EVENT(asahi_g17_inputs, + TP_PROTO(u32 ctx, u64 seq, u8 phase, u32 inputs, u32 sources, u32 waits, + bool converted, u64 counter), + TP_ARGS(ctx, seq, phase, inputs, sources, waits, converted, counter), + TP_STRUCT__entry( + __field(u32, ctx) + __field(u64, seq) + __field(u8, phase) + __field(u32, inputs) + __field(u32, sources) + __field(u32, waits) + __field(bool, converted) + __field(u64, counter) + ), + TP_fast_assign( + __entry->ctx = ctx; + __entry->seq = seq; + __entry->phase = phase; + __entry->inputs = inputs; + __entry->sources = sources; + __entry->waits = waits; + __entry->converted = converted; + __entry->counter = counter; + ), + TP_printk("ctx=%u seq=%llu phase=%u inputs=%u sources=%u waits=%u converted=%u counter=%llu", + __entry->ctx, __entry->seq, __entry->phase, __entry->inputs, + __entry->sources, __entry->waits, __entry->converted, __entry->counter) +); + +/* phase: 0 enqueue, 1 inline published, 2 scheduler fallback, 3 run begin, + * 4 run end, 5 prepare begin, 6 prepare end. detail: dependencies, error or wait. */ +TRACE_EVENT(asahi_g17_schedule, + TP_PROTO(u8 engine, u32 ctx, u64 seq, u8 phase, s32 detail, u64 counter), + TP_ARGS(engine, ctx, seq, phase, detail, counter), + TP_STRUCT__entry( + __field(u8, engine) + __field(u32, ctx) + __field(u64, seq) + __field(u8, phase) + __field(s32, detail) + __field(u64, counter) + ), + TP_fast_assign( + __entry->engine = engine; + __entry->ctx = ctx; + __entry->seq = seq; + __entry->phase = phase; + __entry->detail = detail; + __entry->counter = counter; + ), + TP_printk("engine=%u ctx=%u seq=%llu phase=%u detail=%d counter=%llu", + __entry->engine, __entry->ctx, __entry->seq, __entry->phase, + __entry->detail, __entry->counter) +); + /* Raw 18-word records: trace=0, statistics=1, event=2. No unit inference. */ TRACE_EVENT(asahi_g17_report, TP_PROTO(u8 role, u8 ring, const u32 *words, u64 counter), diff --git a/drivers/gpu/drm/asahi/g17/trace.rs b/drivers/gpu/drm/asahi/g17/trace.rs index cd38c95193585..72197f0eac0b7 100644 --- a/drivers/gpu/drm/asahi/g17/trace.rs +++ b/drivers/gpu/drm/asahi/g17/trace.rs @@ -105,3 +105,33 @@ pub(super) fn report(role: u8, ring: u8, words: &[u32; super::fw::channels::EVEN // SAFETY: C synchronously copies the complete initialized 18-word array. unsafe { asahi_g17_trace_report(role, ring, words.as_ptr()) }; } + + +unsafe extern "C" { + fn asahi_g17_trace_inputs(ctx: u32, seq: u64, phase: u8, inputs: u32, + sources: u32, waits: u32, converted: bool); + fn asahi_g17_trace_schedule(engine: u8, ctx: u32, seq: u64, phase: u8, detail: i32); +} + +/// Input conversion boundaries. No fence status is polled for tracing. +pub(super) fn inputs(ctx: u32, seq: u64, phase: u8, inputs: usize, + sources: Option<&super::sync_input::Sources>) +{ + let (count, waits) = sources.map_or((0, 0), + |s| (s.sources.len(), s.publication_waits().len())); + // SAFETY: Bounded UAPI counts and plain values only. C reads the timer only + // behind this event's static key; no GPU registers or pointers are exposed. + unsafe { asahi_g17_trace_inputs(ctx, seq, phase, inputs as u32, + count as u32, waits as u32, sources.is_some()) }; +} + +/// Host scheduling boundaries, joined to publish/complete by engine/context/sequence. +pub(super) fn schedule(packet: &super::job::Packet, phase: u8, detail: i32) { + let engine = match packet.command { + super::command::Validated::Render { .. } => RENDER, + super::command::Validated::Compute { .. } => COMPUTE, + }; + // SAFETY: Plain values only; counter sampling is static-key gated in C. + unsafe { asahi_g17_trace_schedule(engine, packet.context.id(), + packet.order.sequence, phase, detail) }; +} diff --git a/drivers/gpu/drm/asahi/g17/trace_api.h b/drivers/gpu/drm/asahi/g17/trace_api.h index be5eff4c8161b..cd4d360c55595 100644 --- a/drivers/gpu/drm/asahi/g17/trace_api.h +++ b/drivers/gpu/drm/asahi/g17/trace_api.h @@ -22,6 +22,10 @@ void asahi_g17_trace_message(u8 kind, u64 value); void asahi_g17_trace_event(u8 phase); void asahi_g17_trace_teardown(u8 kind, u64 a, u64 b, u64 c); +void asahi_g17_trace_inputs(u32 ctx, u64 seq, u8 phase, u32 inputs, u32 sources, + u32 waits, bool converted); +void asahi_g17_trace_schedule(u8 engine, u32 ctx, u64 seq, u8 phase, s32 detail); + bool asahi_g17_trace_report_enabled(void); void asahi_g17_trace_report(u8 role, u8 ring, const u32 *words); From d366d69bdefc079c745f87276f3496dc0a985821 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Tue, 27 Oct 2026 18:38:34 +1100 Subject: [PATCH 125/184] docs: distinguish G17 recovery ordering from unresolved T6040 first faults Record the state-1 attribution fix, bounded tiler evidence and host-hop trace contract. Keep the achieved-clock decoder and both deterministic CTS first faults open, with the exact missing firmware facts and measurement boundaries. Preserve the measured g3 policy and all gates. Assisted-by: LLM Signed-off-by: Ryan Murray --- GAPS.md | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/GAPS.md b/GAPS.md index ccc3dc4016d59..9debaafbfcb9b 100644 --- a/GAPS.md +++ b/GAPS.md @@ -107,6 +107,45 @@ and page-fault fallback. Diagnostic counters explicitly identify lifetime stampless totals; they are not a count of commands retired by one restart. +## g5 recovery evidence and host-hop timing + +The shared recovery service previously captured slot keys and monitors at +halted state 1, then called `render_pass_started()` during classification +after acknowledging state 3 to 0. A resumed render could change that pass +state. G5 computes attribution from the state-1 pass snapshot and carries +only that result through the handshake. KUnit drives the real service and +rejects any pass read after the first acknowledgement. Unknown 25G83 keys +still retain firmware blame. This is an independently demonstrated ordering +fix; it does not establish the cause of either deterministic reason-2 fault. + +G5 adds the existing declared TVB state (inventory, page capacity, producer, +consumer, high-water and reset) to the halted snapshot without resetting any +counter. The blamed pair's first four commands include render geometry and +the computed GTP layout, alongside the existing pass/cursor/stamp records. +Pending/active growth and its reply remain visible. The next isolated run +can distinguish exhausted inventory or delayed growth from a pass that never +started; no buffer enlargement or firmware timeout change is justified yet. + +Static-key `asahi_g17_inputs` records compute input conversion entry/result, +source count and publication waits. `asahi_g17_schedule` records enqueue, +inline/fallback, preparation and run boundaries. Together with the existing +publish/complete, DMA-fence and scheduler tracepoints these split host cost +per hop into input resolution, scheduler setup/wait/dispatch and firmware +execution. They make no timing claim until a matched trace is captured. + +The available macOS reference uses 26A428 on T6040 and reports a software +request P10 while hardware residency is 1578 MHz at raw state 14. Linux runs +the admitted 25G83 image. Thus neither a literal P10 conversion nor copying +another firmware's statistics layout proves achieved MHz. The reference +contains aggregate frequency/residency and public DT/calibration facts, +not a Linux 25G83 tag/field decoder. The inspected licensed public G17 source +at `jgoyvaerts/linux` `cc32473a09dd` also has no decoded statistics adapter. +Required input remains a fresh Linux raw report series under labeled idle, +ALU and fill load, and independently established field/timebase/achieved-state +semantics for that exact firmware. No captured raw payload has been promoted +to public implementation data. Achieved clock/power and the CTS cure remain +open; g3's measured memory/latency policy is preserved. + ## g4 shared runtime ports and observability The operator has selected g3 as the hardware baseline. This follow-on retains From aecb91d831daac37c5260c2a2931eb4ce2cb31ad Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Tue, 27 Oct 2026 18:52:18 +1100 Subject: [PATCH 126/184] drm/asahi: bound the G17 tail cache separately from 25G83 auxiliary storage 25G83 packs its existing GTP sub-buffers after the tail-pointer cache. One of them is 64 times the tile map. The inherited 32 MiB TPC limit was applied to the combined object, so the valid 10240-square target used by CTS point_size_10000 is rejected with ERANGE even though the cache and tile record individually fit their original limits. Apply TPC_MAX to the aligned cache, and retain TARGET_RECORD_MAX before allocating or growing the object. Both bounds cap the derived auxiliary extent. Keep the encoder's buffer sizing, address checks, allocation accounting, retained generations and memory-pressure handling unchanged. No firmware field or numeric resource limit increases. The same preflight serves fresh and grown targets; Neo has no GTP tail and retains its policy. KUnit covers the CTS geometry for both utile shapes, oversized core and layered tile-record rejection, and unchanged Neo cache capacity. Hardware must confirm the point case; this does not diagnose the separate 256-square 1 MiB vertex/barrier fault or decode firmware reason 2. Link: https://github.com/KhronosGroup/VK-GL-CTS/blob/vulkan-cts-1.4.6.2/external/vulkancts/modules/vulkan/rasterization/vktRasterizationTests.cpp#L9384 Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/gpu/drm/asahi/g17/buffer/target.rs | 71 ++++++++++++++++++++-- 1 file changed, 67 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/asahi/g17/buffer/target.rs b/drivers/gpu/drm/asahi/g17/buffer/target.rs index 64c88da8eca14..42a2c04b40112 100644 --- a/drivers/gpu/drm/asahi/g17/buffer/target.rs +++ b/drivers/gpu/drm/asahi/g17/buffer/target.rs @@ -139,6 +139,16 @@ impl Layout { }) } pub(crate) fn tpc_capacity(&self) -> Result { + // TPC_MAX bounds the tail-pointer cache itself. On 25G83 this object + // also contains GTP sub-buffers, notably 64 times the tile map. Applying + // the cache limit to their combined extent rejects valid large targets. + // Keep the original cache and tile-record limits; they also bound the + // derived auxiliary extent before any backing allocation or replacement. + if aligned(self.tpc_size, mmu::UAT_PGSZ)?.max(cfg::TPC_MIN) > cfg::TPC_MAX + || self.record_stride()? > cfg::TARGET_RECORD_MAX + { + return Err(ERANGE); + } Ok(aligned(self.tpc_end, mmu::UAT_PGSZ)?.max(cfg::TPC_MIN)) } pub(crate) fn record_stride(&self) -> Result { @@ -188,7 +198,7 @@ impl Target { pub(crate) fn new(dev: &AsahiDrmDevice, vm: &mmu::Vm, layout: &Layout) -> Result> { let tpc = layout.tpc_capacity()?; let stride = layout.record_stride()?; - if tpc > cfg::TPC_MAX || stride > cfg::TARGET_RECORD_MAX { + if stride > cfg::TARGET_RECORD_MAX { return Err(ERANGE); } let records = Object::new(dev, vm, stride * DEPTH)?; @@ -272,9 +282,6 @@ impl Target { let stride = layout.record_stride()?; let mut grew = false; if tpc > self.tpc.backing.size() { - if tpc > cfg::TPC_MAX { - return Err(ERANGE); - } Self::replace(&mut self.retired, &mut self.tpc, dev, vm, tpc, owners)?; grew = true; } @@ -350,3 +357,59 @@ impl Target { } } } + + +#[kernel::macros::kunit_tests(asahi_g17_target_capacity)] +mod tests { + use super::*; + + fn pass(size: u32, utile: u8) -> RenderPass { + // SAFETY: RenderPass and its nested surfaces/programs contain only + // integers and bools, for which the all-zero representation is valid. + let mut pass: RenderPass = unsafe { core::mem::zeroed() }; + pass.width = size; + pass.height = size; + pass.layers = 1; + pass.utile_width = utile; + pass.utile_height = utile; + pass + } + + #[test] + fn t6040_auxiliary_tiler_storage_is_not_tail_pointer_cache() { + // CTS point_size_10000 renders to 10240 squared. Its GTP sub-buffer + // is required by the existing 25G83 program, not a larger cache. + for utile in [16, 32] { + let layout = Layout::new(&pass(10240, utile), 2, RegisterPrograms::V25G83).unwrap(); + assert!(layout.tpc_size < cfg::TPC_MAX); + assert!(layout.record_stride().unwrap() < cfg::TARGET_RECORD_MAX); + assert!(layout.tpc_end > cfg::TPC_MAX); + assert!(layout.tpc_capacity().unwrap() >= layout.tpc_end); + assert_eq!(layout.gtp[0], Some(0)); + assert!(layout.gtp[1].unwrap() >= layout.tpc_size); + } + } + + #[test] + fn core_and_tile_record_limits_still_reject_oversized_targets() { + let mut layout = Layout::new(&pass(10240, 32), 2, RegisterPrograms::V25G83).unwrap(); + layout.tpc_size = cfg::TPC_MAX + 1; + assert_eq!(layout.tpc_capacity(), Err(ERANGE)); + let mut layered = pass(16384, 16); + layered.layers = 2; + let layout = Layout::new(&layered, 2, RegisterPrograms::V25G83).unwrap(); + assert!(layout.record_stride().unwrap() > cfg::TARGET_RECORD_MAX); + assert_eq!(layout.tpc_capacity(), Err(ERANGE)); + } + + #[test] + fn neo_keeps_the_same_cache_extent_and_limit() { + let mut layout = Layout::new(&pass(10240, 32), 1, RegisterPrograms::G17P).unwrap(); + assert!(layout.gtp.iter().all(Option::is_none)); + assert_eq!(layout.tpc_capacity().unwrap(), + aligned(layout.tpc_size, mmu::UAT_PGSZ).unwrap().max(cfg::TPC_MIN)); + layout.tpc_size = cfg::TPC_MAX + 1; + layout.tpc_end = layout.tpc_size; + assert_eq!(layout.tpc_capacity(), Err(ERANGE)); + } +} From 1214a925dbee556c0c4305777308f9938f262eb8 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Tue, 27 Oct 2026 18:52:18 +1100 Subject: [PATCH 127/184] docs: identify the T6040 large-target guard separately from the barrier fault Record the production layout reproducer, unchanged numeric bounds and mandatory hardware/memory acceptance for the point-target correction. Keep the 256-square large-vertex barrier fault unresolved and separate from framebuffer size and unqualified firmware reason codes. Assisted-by: LLM Signed-off-by: Ryan Murray --- GAPS.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/GAPS.md b/GAPS.md index 9debaafbfcb9b..c8d2c7c753261 100644 --- a/GAPS.md +++ b/GAPS.md @@ -107,6 +107,35 @@ and page-fault fallback. Diagnostic counters explicitly identify lifetime stampless totals; they are not a count of commands retired by one restart. +## g5 large-target capacity correction + +Public CTS `vulkan-cts-1.4.6.2` uses a 10240-square framebuffer for +`rasterization.primitive_size.points.point_size_10000`. The existing 25G83 +GTP layout appends a sub-buffer of 64 times the tile map after the TPC. +Applying the inherited 32 MiB cache bound to this combined allocation rejects +that valid target with ERANGE, despite its cache and tile record each fitting +their original limits. This is reproducible in the production layout code +without GPU execution. The normalized hardware summary contains ERANGE and +reason-2 recoveries but cannot establish their order or one-to-one association. + +G5 applies the original TPC_MAX to the aligned cache, preserving the original +tile-record bound before allocation or growth. The tile-map bound also caps +the derived auxiliary extent. It changes no firmware buffer-sizing formula, +numeric limit, VA window, generation/retention rule or allocation accounting. +Neo has no auxiliary tail and keeps the same policy. KUnit covers both utile +shapes at the CTS target size, oversized cache/layered-record rejection and +Neo capacity. Fresh and growing targets use the same preflight. Large targets +need their full required metadata through the existing memory/VA mechanisms; +allocation failure still propagates. Hardware execution/readback and post-close +memory/capacity baselines are mandatory acceptance gates. + +The separate 1 MiB vertex/barrier case uses a 256-square target, not a large +framebuffer. Its point count is actual vertex-buffer bytes divided by stride. +It cannot be explained by the large-target capacity guard. Preserve its first +operation/exception, generated sequence and the halted-state TVB/growth/pass +snapshots before choosing a coherence, VM-bind or firmware-timeout fix. The +point-target correction is not a claim that firmware reason 2 is decoded. + ## g5 recovery evidence and host-hop timing The shared recovery service previously captured slot keys and monitors at From 92f563f1b5409aafa16abd037d119ed57d57d70c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franti=C5=A1ek=20Bal=C3=A1zsy?= Date: Sun, 25 Oct 2026 16:28:36 +0100 Subject: [PATCH 128/184] drm/apple: route the T6041 Type-C displays through the display fabric MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The dcpext of a T6041 listed the DP PHY and crossbar of every Type-C port and picked the port whose PHY had entered DP alt mode at connect time. The display fabric already routes the T602x, T6030 and J700 Type-C ports: a typec-routes node under the DCP, one mode switch per port, driven by the port controller. Use it for desktop 26.x as well and drop the private route (apple,dptx-phys, dp-phyN/dp-xbarN, apple_t6040_phy_dp_alt()). Three things differ from the other fabric users: - The T6040 DP PHY must be powered when the display connects, not when a route is claimed. The port controller calls the PHY's and the route's mode switch in connector order, and the PHY has to have seen DP alt mode and the plug orientation before its lanes are programmed. - The compact DPTX of desktop 26.x takes the crossbar link down and up around a link configuration through dcp->xbar, while the fabric's own direct-link helper is T6030 only. The route sets dcp->xbar for the time it is active; without it the link never trains. - There is a single Type-C pipeline. A port in DP alt mode without a sink (a dock, or a pin assignment the PHY cannot drive) must not hold it against the port that has a display: claim it on HPD only. Tested on a J616s (Mac16,7) with a USB-C display on one port: attach at boot, rebind of the port controller (replug) and a device suspend test (pm_test=devices). The other ports and a second display are not tested. Signed-off-by: František Balázsy --- drivers/gpu/drm/apple/dcp-fabric.c | 78 +++++++++++- drivers/gpu/drm/apple/dcp-fabric.h | 3 + drivers/gpu/drm/apple/dcp-internal.h | 11 -- drivers/gpu/drm/apple/dcp.c | 181 ++++----------------------- drivers/phy/apple/t6040-usb2.c | 9 -- include/linux/soc/apple/dp-tunnel.h | 15 --- 6 files changed, 106 insertions(+), 191 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp-fabric.c b/drivers/gpu/drm/apple/dcp-fabric.c index b5e2eafb8c090..629b33ddace2c 100644 --- a/drivers/gpu/drm/apple/dcp-fabric.c +++ b/drivers/gpu/drm/apple/dcp-fabric.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -306,6 +307,54 @@ static int dcp_dpxbar_tunnel_select_source(struct mux_control *mux, int state) return ret; } +/* + * Desktop 26.x firmware does not power the DP PHY of a Type-C route itself: + * the route does, once its crossbar points at the pipeline, and gives it + * back before the crossbar is released. + */ +static int dcp_typec_route_phy_on(struct apple_dcp_typec_route *route) +{ + int ret; + + if (route->dcp->fw_compat != DCP_FIRMWARE_V_26_6_DESKTOP || + route->phy_powered) + return 0; + ret = phy_init(route->phy); + if (!ret) { + ret = phy_power_on(route->phy); + if (ret) + phy_exit(route->phy); + } + if (ret) { + dev_warn(route->dcp->dev, "Type-C DP PHY init/power: %d\n", ret); + return ret; + } + route->phy_powered = true; + msleep(25); + return 0; +} + +/* + * The PHY is powered when the display connects, not when the route is + * claimed: the port controller's mode switches run in connector order, and + * the PHY must have seen DP alt mode and the plug orientation first. + */ +int dcp_typec_phy_on(struct apple_dcp *dcp) +{ + struct apple_dcp_typec_route *route = READ_ONCE(dcp->active_typec_route); + + return route ? dcp_typec_route_phy_on(route) : 0; +} + +static void dcp_typec_route_phy_off(struct apple_dcp_typec_route *route) +{ + if (!route->phy_powered) + return; + route->phy_powered = false; + phy_power_off(route->phy); + phy_exit(route->phy); +} + static int dcp_typec_route_activate(struct apple_dcp_typec_route *route, struct mux_control *xbar) { @@ -370,6 +419,12 @@ static int dcp_typec_route_activate(struct apple_dcp_typec_route *route, dcp_modes_begin_attachment(dcp); apple_connector_edid_set_live(dcp->connector, false); dcp->phy = route->phy; + /* + * Desktop 26.x compact DPTX takes the crossbar link down and up around + * a link configuration (dptxport_compact_xbar_link()) through dcp->xbar. + */ + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) + dcp->xbar = route->xbar; dcp->dptx_phy = route->dptx_phy; dcp->connector_type = DRM_MODE_CONNECTOR_USB; WRITE_ONCE(dcp->ext_backlight, false); @@ -444,6 +499,7 @@ static int dcp_typec_route_deactivate(struct apple_dcp_typec_route *route) if (route->deactivate_error) return route->deactivate_error; if (!route->deactivate_xbar_done) { + dcp_typec_route_phy_off(route); if (!route->tunnel || route->xbar_up) ret = mux_control_deselect(route->active_xbar ?: route->xbar); else @@ -546,8 +602,15 @@ static int dcp_typec_route_deactivate(struct apple_dcp_typec_route *route) dcp->dptx_phy = dcp->typec_routes[0].dptx_phy; } } else { - /* Keep DPTX endpoint discovery working before a cable is attached. */ - dcp->phy = dcp->typec_routes[0].phy; + /* + * Keep DPTX endpoint discovery working before a cable is + * attached. Desktop 26.x powers the PHY with the route, so an + * idle pipeline must not point at one. + */ + dcp->phy = dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP ? + NULL : dcp->typec_routes[0].phy; + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) + dcp->xbar = NULL; dcp->dptx_phy = dcp->typec_routes[0].dptx_phy; dcp->connector_type = DRM_MODE_CONNECTOR_USB; } @@ -1521,6 +1584,17 @@ static int dcp_typec_route_set(struct typec_mux_dev *mux, } } + /* + * Desktop 26.x has one Type-C pipeline: only a port with a sink (HPD) + * takes it. A dock or cable in DP alt mode without a display behind it + * would otherwise hold it against the port that has one. + */ + if (!port->owner && !hpd && + route->dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) { + port->applied_valid = true; + return 0; + } + if (!port->owner) { best = dcp_typec_free_route(port); /* Every pipeline is driving another port's display. */ diff --git a/drivers/gpu/drm/apple/dcp-fabric.h b/drivers/gpu/drm/apple/dcp-fabric.h index 869a2c3716cfe..57bffb02d715f 100644 --- a/drivers/gpu/drm/apple/dcp-fabric.h +++ b/drivers/gpu/drm/apple/dcp-fabric.h @@ -39,6 +39,8 @@ struct apple_dcp_typec_route { u32 dptx_phy; u32 mux_index; bool selected; + /* desktop 26.x: the DP PHY is initialised and powered for this route */ + bool phy_powered; /* crossbar output actually selected: xbar (dpphy) or a Thunderbolt dpin */ unsigned int tunnel_dpin; u64 tunnel_generation; @@ -51,6 +53,7 @@ struct apple_dcp_typec_route { int deactivate_error; }; +int dcp_typec_phy_on(struct apple_dcp *dcp); bool dcp_is_typec_output(struct apple_dcp *dcp); bool dcp_is_usb4_output(struct apple_dcp *dcp); bool dcp_uses_t6020_tunnel_flow(struct apple_dcp *dcp); diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index b720cd80c11d6..7920bd86af102 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -294,17 +294,6 @@ struct dcp_desktop_state { * display (unplug); the next swap needs a power-on and a mode set. */ bool ext_pipe_off; - /* - * Dynamic Type-C DP route (T6041 dcpext1): the DP PHYs and crossbars - * of every Type-C port, the one in use (or -1), the DPTX target PHY - * of each and the crossbar state selecting this processor. - */ - unsigned int ndyn; - int dyn_active; - struct phy *dyn_phy[3]; - struct mux_control *dyn_xbar[3]; - u32 dyn_dptx_phy[3]; - u32 dyn_mux_index; }; /* TODO: move IOMFB members to its own struct */ diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 628c1b7e05660..69a5a55ae63eb 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -2133,119 +2133,30 @@ static int dcp_dptx_connect_tile(struct apple_dcp *dcp) /* * Desktop 26.x external processors (T6041 dcpext0 HDMI, dcpext1 USB-C). - * They keep the classic external model of the driver this was ported from: - * no Type-C display fabric, the DPTX link set up on HPD (HDMI) or on the - * DP alt mode hotplug of the port (USB-C), and the Type-C port chosen - * dynamically from the PHY that entered DP alt mode. + * The DPTX link is set up on HPD (HDMI) or when the Type-C display fabric + * routes a port's DP alt mode to the processor (USB-C). */ -/* The caller holds hpd_mutex, which also orders dcp->phy against a connect. */ -static int dcp_desktop_dyn_route_select_locked(struct apple_dcp *dcp) -{ - struct dcp_desktop_state *d = dcp->desktop; - typeof(&apple_t6040_phy_dp_alt) dp_alt; - unsigned int i; - int ret; - - lockdep_assert_held(&dcp->hpd_mutex); - if (!d->ndyn || d->dyn_active >= 0) - return 0; - /* Looked up at runtime so appledrm does not require the T6040 PHY driver. */ -#if IS_ENABLED(CONFIG_PHY_APPLE_T6040_USB2) - dp_alt = symbol_get(apple_t6040_phy_dp_alt); -#else - dp_alt = NULL; -#endif - if (!dp_alt) { - dev_dbg(dcp->dev, "T6040 PHY driver not loaded\n"); - return -ENODEV; - } - for (i = 0; i < d->ndyn; i++) - if (dp_alt(d->dyn_phy[i])) - break; -#if IS_ENABLED(CONFIG_PHY_APPLE_T6040_USB2) - symbol_put(apple_t6040_phy_dp_alt); -#endif - if (i == d->ndyn) { - dev_dbg(dcp->dev, "no Type-C port in DP alt mode\n"); - return -ENODEV; - } - ret = mux_control_select(d->dyn_xbar[i], d->dyn_mux_index); - if (ret) { - dev_warn(dcp->dev, "Type-C port %u: crossbar select: %d\n", i, ret); - return ret; - } - ret = phy_init(d->dyn_phy[i]); - if (!ret) { - ret = phy_power_on(d->dyn_phy[i]); - if (ret) - phy_exit(d->dyn_phy[i]); - } - if (ret) { - dev_warn(dcp->dev, "Type-C port %u: DP PHY init/power: %d\n", i, ret); - mux_control_deselect(d->dyn_xbar[i]); - return ret; - } - msleep(25); - dcp->phy = d->dyn_phy[i]; - dcp->xbar = d->dyn_xbar[i]; - dcp->dptx_phy = d->dyn_dptx_phy[i]; - d->dyn_active = i; - dev_dbg(dcp->dev, "Type-C port %u selected (DPTX target PHY %u, crossbar state %u)\n", - i, dcp->dptx_phy, d->dyn_mux_index); - return 0; -} - -static void dcp_desktop_dyn_route_release_locked(struct apple_dcp *dcp) -{ - struct dcp_desktop_state *d = dcp->desktop; - int i = d->dyn_active; - - lockdep_assert_held(&dcp->hpd_mutex); - if (!d->ndyn || i < 0) - return; - d->dyn_active = -1; - dcp->phy = NULL; - dcp->xbar = NULL; - phy_power_off(d->dyn_phy[i]); - phy_exit(d->dyn_phy[i]); - mux_control_deselect(d->dyn_xbar[i]); - dev_dbg(dcp->dev, "Type-C port %u released\n", i); -} - -/* Unbind: a selected route holds its crossbar and its powered PHY. */ -static void dcp_desktop_dyn_route_release(struct apple_dcp *dcp) -{ - if (!dcp->desktop) - return; - guard(mutex)(&dcp->hpd_mutex); - dcp_desktop_dyn_route_release_locked(dcp); -} - static int dcp_desktop_dptx_connect(struct apple_dcp *dcp, u32 port) { struct dptx_port *dptx = &dcp->dptxport[port]; unsigned long left; - bool selected; int ret = 0; mutex_lock(&dcp->hpd_mutex); - /* A route selected here is released again if the connect fails. */ - selected = !dcp->phy && dcp->desktop->ndyn; - if (selected) { - ret = dcp_desktop_dyn_route_select_locked(dcp); - if (ret) - goto out_unlock; - } if (!dcp->phy) { dev_warn(dcp->dev, "dcp_dptx_connect: missing phy\n"); ret = -ENODEV; goto out_unlock; } + /* A fabric route powers its PHY here (a no-op for the other routes). */ + ret = dcp_typec_phy_on(dcp); + if (ret) + goto out_unlock; if (!dptx_service_available(dptx)) { dev_warn(dcp->dev, "dcp_dptx_connect: dptx service for port %u not enabled\n", port); ret = -ENODEV; - goto out_release; + goto out_unlock; } if (dptx->connected) goto out_unlock; @@ -2255,13 +2166,13 @@ static int dcp_desktop_dptx_connect(struct apple_dcp *dcp, u32 port) ret = dptxport_connect(dptx->service, 0, dcp->dptx_phy, dcp->dptx_die, true); if (ret) { dev_warn(dcp->dev, "DPTX port %u: connect failed: %d\n", port, ret); - goto out_release; + goto out_unlock; } ret = dptxport_request_display(dptx->service); if (ret) { dev_warn(dcp->dev, "DPTX port %u: display request failed: %d\n", port, ret); - goto out_release; + goto out_unlock; } dptx->connected = true; @@ -2290,9 +2201,6 @@ static int dcp_desktop_dptx_connect(struct apple_dcp *dcp, u32 port) av_service_connect(dcp); return 0; -out_release: - if (selected) - dcp_desktop_dyn_route_release_locked(dcp); out_unlock: mutex_unlock(&dcp->hpd_mutex); return ret; @@ -2310,8 +2218,6 @@ static int dcp_desktop_dptx_disconnect_oob(struct apple_dcp *dcp, u32 port) dptxport_set_hpd(dcp->dptxport[port].service, false); } ret = dcp_dptx_disconnect(dcp, port); - scoped_guard(mutex, &dcp->hpd_mutex) - dcp_desktop_dyn_route_release_locked(dcp); if (!dcp->main_display) dcp->desktop->ext_pipe_off = true; return ret; @@ -3382,7 +3288,7 @@ static int dcp_start_desktop_external(struct apple_dcp *dcp, u32 map) if (ret) return ret; - if (!dcp->phy && !d->ndyn) { + if (!dcp->phy) { dev_warn(dcp->dev, "no DP PHY and no Type-C route: no display link\n"); return 0; } @@ -5284,8 +5190,6 @@ VISIBLE_IF_KUNIT void dcp_comp_unbind(struct device *dev, struct device *main, v dcp->systemep = NULL; } - dcp_desktop_dyn_route_release(dcp); - /* * The auxiliary endpoints a desktop 26.x start brought up, still * routed while their shutdown is acknowledged. @@ -5396,62 +5300,31 @@ static void dcp_desktop_phy_off(void *data) } /* - * The outputs of a desktop 26.x external processor (T6041 dcpext): the - * fixed HDMI route's DP PHY is powered here, as the boot loader's working - * sequence does; a dynamic Type-C route lists the DP PHY and crossbar of - * every port (dp-phyN, dp-xbarN, apple,dptx-phys, apple,dptx-mux-index), - * and the one in DP alt mode is claimed at connect. + * The fixed HDMI output of a desktop 26.x external processor (T6041 dcpext0): + * its DP PHY is powered here, as the boot loader's working sequence does. A + * Type-C output (dcpext1) has no fixed PHY; the display fabric powers the + * PHY of the route a port claims (see dcp_typec_phy_on()). */ static int dcp_desktop_probe_routes(struct apple_dcp *dcp) { - struct dcp_desktop_state *d = dcp->desktop; struct device *dev = dcp->dev; - unsigned int i; - int nd, ret; + int ret; - d->dyn_active = -1; - if (!dcp_desktop_external(dcp)) + if (!dcp_desktop_external(dcp) || !dcp->phy) return 0; - if (dcp->phy) { - ret = phy_init(dcp->phy); - if (ret) - return dev_err_probe(dev, ret, "Failed to initialize dp-phy\n"); - ret = phy_power_on(dcp->phy); - if (ret) { - phy_exit(dcp->phy); - return dev_err_probe(dev, ret, "Failed to power on dp-phy\n"); - } - ret = devm_add_action_or_reset(dev, dcp_desktop_phy_off, dcp->phy); - if (ret) - return ret; - msleep(25); + ret = phy_init(dcp->phy); + if (ret) + return dev_err_probe(dev, ret, "Failed to initialize dp-phy\n"); + ret = phy_power_on(dcp->phy); + if (ret) { + phy_exit(dcp->phy); + return dev_err_probe(dev, ret, "Failed to power on dp-phy\n"); } - - nd = of_property_count_u32_elems(dev->of_node, "apple,dptx-phys"); - if (nd <= 0) - return 0; - if (nd > ARRAY_SIZE(d->dyn_phy)) - return dev_err_probe(dev, -EINVAL, "too many Type-C routes: %d\n", nd); - - for (i = 0; i < nd; i++) { - char name[16]; - - snprintf(name, sizeof(name), "dp-phy%u", i); - d->dyn_phy[i] = devm_phy_get(dev, name); - if (IS_ERR(d->dyn_phy[i])) - return dev_err_probe(dev, PTR_ERR(d->dyn_phy[i]), "%s\n", name); - snprintf(name, sizeof(name), "dp-xbar%u", i); - d->dyn_xbar[i] = devm_mux_control_get(dev, name); - if (IS_ERR(d->dyn_xbar[i])) - return dev_err_probe(dev, PTR_ERR(d->dyn_xbar[i]), "%s\n", name); - of_property_read_u32_index(dev->of_node, "apple,dptx-phys", i, - &d->dyn_dptx_phy[i]); - } - of_property_read_u32(dev->of_node, "apple,dptx-mux-index", &d->dyn_mux_index); - d->ndyn = nd; - dev_info(dev, "%u Type-C DP routes, crossbar state %u\n", d->ndyn, - d->dyn_mux_index); + ret = devm_add_action_or_reset(dev, dcp_desktop_phy_off, dcp->phy); + if (ret) + return ret; + msleep(25); return 0; } diff --git a/drivers/phy/apple/t6040-usb2.c b/drivers/phy/apple/t6040-usb2.c index 868d4304b2395..c15c004d94c83 100644 --- a/drivers/phy/apple/t6040-usb2.c +++ b/drivers/phy/apple/t6040-usb2.c @@ -1509,15 +1509,6 @@ static int t6040_dp_mux_set(struct typec_mux_dev *mux, struct typec_mux_state *s return 0; } -/* For the DCP's Type-C route: is this PHY's port in DP alt mode (C/E)? */ -bool apple_t6040_phy_dp_alt(struct phy *phy) -{ - struct t6040_usb2_phy *tphy = phy_get_drvdata(phy); - - return tphy && tphy->dp_alt; -} -EXPORT_SYMBOL_GPL(apple_t6040_phy_dp_alt); - static void t6040_dp_mux_unregister(void *mux) { typec_mux_unregister(mux); diff --git a/include/linux/soc/apple/dp-tunnel.h b/include/linux/soc/apple/dp-tunnel.h index 868de62db1156..540d48fe8e0dc 100644 --- a/include/linux/soc/apple/dp-tunnel.h +++ b/include/linux/soc/apple/dp-tunnel.h @@ -61,21 +61,6 @@ int apple_atc_dp_tunnel_rate_source(struct phy *phy, unsigned int source, */ int apple_atc_dp_tunnel_open(struct phy *phy); -/* - * T6040 Type-C PHY: true while the Type-C controller holds the PHY's port in - * DisplayPort alt mode with pin assignment C or E (DP on all four lanes), so - * appledrm can route a DCP that lists several Type-C DP PHYs to the one that - * has a DisplayPort sink plugged. - */ -#if IS_ENABLED(CONFIG_PHY_APPLE_T6040_USB2) -bool apple_t6040_phy_dp_alt(struct phy *phy); -#else -static inline bool apple_t6040_phy_dp_alt(struct phy *phy) -{ - return false; -} -#endif - /* * Display crossbar: take the connection of an already selected output down or * bring it back up (clock gates and enables) around a link reconfiguration. From b7e5c080e92a7f45f6b836f765d8a6e754beb32f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franti=C5=A1ek=20Bal=C3=A1zsy?= Date: Sun, 25 Oct 2026 14:04:18 +0100 Subject: [PATCH 129/184] drm/apple: check the desktop 26.6 callback records against their handlers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The H17P callbacks are checked against the record sizes the firmware sends; the desktop 26.6 ones were not. The u16 offset keeps a record inside the shared memory, but a handler could still read past a short request or write past a short reply into the next record. Give every desktop 26.6 handler the request and reply size it reads and writes, and refuse a callback whose record is shorter, or whose lengths do not fit the message, the way H17P refuses a malformed one. Longer records stay accepted. Signed-off-by: František Balázsy --- drivers/gpu/drm/apple/iomfb.c | 9 ++ drivers/gpu/drm/apple/iomfb.h | 2 + drivers/gpu/drm/apple/iomfb_v26_6_desktop.c | 91 +++++++++++++++++++++ 3 files changed, 102 insertions(+) diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 27c7ba4b39b66..4b9ccafa0338c 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -1428,6 +1428,15 @@ static void dcpep_handle_cb(struct apple_dcp *dcp, enum dcp_context_id context, handled = tag >= 0 && tag < IOMFB_MAX_CB && dcp->cb_handlers && dcp->cb_handlers[tag]; + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && handled && + (length < sizeof(*hdr) || + (u64)sizeof(*hdr) + hdr->in_len + hdr->out_len > length || + !iomfb_check_callback_v26_6_desktop(tag, hdr->in_len, hdr->out_len))) { + dev_err(dev, "short IOMFB callback %d (%u, %u, length %u)\n", + tag, hdr->in_len, hdr->out_len, length); + dcp->crashed = true; + return; + } if (dcp->fw_compat == DCP_FIRMWARE_H17P && (tag < 0 || tag >= IOMFB_MAX_CB || (u64)sizeof(*hdr) + hdr->in_len + hdr->out_len != length || diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h index 7e6e16f1dddf7..7c60a81207ad7 100644 --- a/drivers/gpu/drm/apple/iomfb.h +++ b/drivers/gpu/drm/apple/iomfb.h @@ -212,6 +212,8 @@ bool iomfb_validate_callback_h17p(int tag, u32 in_len, u32 out_len); struct apple_dcp; bool iomfb_check_callback_h17p(struct apple_dcp *dcp, int tag, u32 in_len, u32 out_len); +/* Desktop 26.6: does the record hold what the tag's handler reads and writes? */ +bool iomfb_check_callback_v26_6_desktop(int tag, u32 in_len, u32 out_len); typedef bool (*iomfb_cb_handler)(struct apple_dcp *, int, void *, void *); diff --git a/drivers/gpu/drm/apple/iomfb_v26_6_desktop.c b/drivers/gpu/drm/apple/iomfb_v26_6_desktop.c index de8783f3a3694..1e5f431a88137 100644 --- a/drivers/gpu/drm/apple/iomfb_v26_6_desktop.c +++ b/drivers/gpu/drm/apple/iomfb_v26_6_desktop.c @@ -266,6 +266,97 @@ static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = { [599] = trampoline_nop, }; +/* + * What each handler above reads from the request and writes to the reply. + * The firmware may send longer records; a shorter one is refused. + */ +struct dcp_cb_min_v26_6_desktop { + u32 in_len; + u32 out_len; + bool valid; +}; + +#define CB_MIN(in, out) { .in_len = (in), .out_len = (out), .valid = true } + +static const struct dcp_cb_min_v26_6_desktop cb_min[IOMFB_MAX_CB] = { + [0] = CB_MIN(0, sizeof(u8)), + [1] = CB_MIN(0, sizeof(u8)), + [2] = CB_MIN(0, 0), + [3] = CB_MIN(0, sizeof(struct dcp_rt_bandwidth)), + [6] = CB_MIN(sizeof(struct dcp_set_frame_sync_props_req_v26_6_desktop), + sizeof(struct dcp_set_frame_sync_props_resp_v26_6_desktop)), + [100] = CB_MIN(0, 0), + [101] = CB_MIN(0, sizeof(u32)), + [102] = CB_MIN(0, 0), + [104] = CB_MIN(0, 0), + [107] = CB_MIN(0, 0), + [108] = CB_MIN(0, sizeof(u8)), + [109] = CB_MIN(0, sizeof(u8)), + [110] = CB_MIN(0, sizeof(u8)), + [111] = CB_MIN(0, sizeof(u8)), + [112] = CB_MIN(0, sizeof(u8)), + [113] = CB_MIN(0, sizeof(u8)), + [114] = CB_MIN(sizeof(struct dcp_dict_passthrough_req_v26_6), + sizeof(struct dcp_dict_passthrough_resp_v26_6)), + [115] = CB_MIN(sizeof(struct dcpep_get_tiling_state_req), + sizeof(struct dcpep_get_tiling_state_resp)), + [121] = CB_MIN(0, 0), + [122] = CB_MIN(0, sizeof(u8)), + [123] = CB_MIN(0, sizeof(u8)), + [125] = CB_MIN(sizeof(struct dcp_read_edt_data_req), + sizeof(dcp_read_edt_data_resp_t)), + [127] = CB_MIN(sizeof(u32), sizeof(u8)), + [128] = CB_MIN(sizeof(struct dcp_set_dcpav_prop_chunk_req), sizeof(u8)), + [129] = CB_MIN(sizeof(struct dcp_set_dcpav_prop_end_req), sizeof(u8)), + [134] = CB_MIN(0, sizeof(u32)), + [201] = CB_MIN(sizeof(struct dcp_map_buf_req), + sizeof(struct DCP_FW_NAME(dcp_map_buf_resp))), + [206] = CB_MIN(0, sizeof(u8)), + [207] = CB_MIN(0, sizeof(u8)), + [208] = CB_MIN(0, 0), + [209] = CB_MIN(0, sizeof(u64)), + [300] = CB_MIN(sizeof(struct iomfb_property), 0), + [400] = CB_MIN(0, 0), + [401] = CB_MIN(sizeof(struct dcp_get_uint_prop_req), + sizeof(struct dcp_get_uint_prop_resp)), + [406] = CB_MIN(0, 0), + [408] = CB_MIN(0, sizeof(u64)), + [411] = CB_MIN(sizeof(struct DCP_FW_NAME(dcp_map_reg_req)), + sizeof(struct DCP_FW_NAME(dcp_map_reg_resp))), + [413] = CB_MIN(0, sizeof(u8)), + [414] = CB_MIN(sizeof(struct iomfb_sr_set_property_int_req), sizeof(u8)), + [415] = CB_MIN(0, sizeof(u8)), + [451] = CB_MIN(sizeof(struct dcp_allocate_buffer_req), + sizeof(struct dcp_allocate_buffer_resp)), + [552] = CB_MIN(0, sizeof(u8)), + [561] = CB_MIN(0, sizeof(u8)), + [563] = CB_MIN(0, sizeof(u8)), + [565] = CB_MIN(0, sizeof(u8)), + [567] = CB_MIN(0, sizeof(u8)), + [572] = CB_MIN(0, sizeof(u32)), + [575] = CB_MIN(sizeof(struct dcp_hotplug_req_v26_6), + sizeof(struct dcp_hotplug_resp_v26_6)), + [576] = CB_MIN(0, 0), + [581] = CB_MIN(0, 0), + [582] = CB_MIN(0, sizeof(u8)), + [590] = CB_MIN(sizeof(struct DCP_FW_NAME(dc_swap_complete_resp)), 0), + [592] = CB_MIN(sizeof(dcp_swap_intent_t), 0), + [593] = CB_MIN(sizeof(u32), 0), + [594] = CB_MIN(sizeof(u8), 0), + [595] = CB_MIN(0, 0), + [598] = CB_MIN(0, 0), + [599] = CB_MIN(0, 0), +}; + +#undef CB_MIN + +bool iomfb_check_callback_v26_6_desktop(int tag, u32 in_len, u32 out_len) +{ + if (tag < 0 || tag >= IOMFB_MAX_CB || !cb_min[tag].valid) + return false; + return in_len >= cb_min[tag].in_len && out_len >= cb_min[tag].out_len; +} + void DCP_FW_NAME(iomfb_start)(struct apple_dcp *dcp) { dcp->cb_handlers = cb_handlers; From d7f2aa6ead929fb17f5a43e161fc2af3e0a22890 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franti=C5=A1ek=20Bal=C3=A1zsy?= Date: Sun, 25 Oct 2026 14:04:43 +0100 Subject: [PATCH 130/184] phy: apple: t6040: give the Type-C lanes back to USB3 after DP MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit t6040_dp_power_on() shuts the USB3 side down, but t6040_dp_power_off() only cleared dp_up, and t6040_usb3_power_on() could re-initialise the PHY under a live DP mode. Once a Type-C DP session ended with the plug still in, USB3 on that port stayed down until dwc3 power cycled the PHY. Track whether dwc3 holds the USB3 phy on and asked for the PIPE switch. USB3 bring-up now waits while DP mode is programmed, and when DP mode ends and the port has left DP alt mode, from the DP power-off or the mode switch, whichever comes last, USB3 is brought up again and the PIPE goes back to the PHY. On an unplug or a DP mode change tipd drops the USB role first, dwc3 powers the USB3 phy off, and nothing changes there. Signed-off-by: František Balázsy --- drivers/phy/apple/t6040-usb2.c | 62 ++++++++++++++++++++++++++++++---- 1 file changed, 55 insertions(+), 7 deletions(-) diff --git a/drivers/phy/apple/t6040-usb2.c b/drivers/phy/apple/t6040-usb2.c index c15c004d94c83..20fb5e9da5cb0 100644 --- a/drivers/phy/apple/t6040-usb2.c +++ b/drivers/phy/apple/t6040-usb2.c @@ -237,6 +237,9 @@ struct t6040_usb2_phy { struct completion app_reply; enum typec_orientation orientation; bool usb3_up; /* tuned by the coprocessor this power cycle */ + bool usb3_on; /* dwc3 holds the USB3 phy powered on */ + bool usb3_pipe; /* dwc3 asked for the PIPE switch this power cycle */ + bool usb3_lent; /* DP mode took the lanes of this Type-C port */ bool pipe_switched; /* the controller's PIPE runs on this PHY */ /* DisplayPort (PHY mode 3); dp_ok is false when the DT lacks a DP route */ @@ -882,15 +885,19 @@ static void t6040_usb3_pipe_park(struct t6040_usb2_phy *tphy) * its PIPE on the dummy PHY. A failure here leaves the port at USB2, so it is * logged and not returned: the USB2 PHY and the core must still come up. */ -static int t6040_usb3_power_on(struct phy *phy) +static void t6040_usb3_up_locked(struct t6040_usb2_phy *tphy) { - struct t6040_usb2_phy *tphy = phy_get_drvdata(phy); bool swap; int ret; - guard(mutex)(&tphy->lock); + lockdep_assert_held(&tphy->lock); if (!tphy->usb3_ok || tphy->usb3_up) - return 0; + return; + /* DP mode owns all four lanes; USB3 comes back in t6040_usb3_reclaim_locked(). */ + if (tphy->dp_up) { + dev_dbg(tphy->dev, "USB3: lanes held by DP, USB2 only for now\n"); + return; + } switch (tphy->orientation) { case TYPEC_ORIENTATION_NORMAL: @@ -901,7 +908,7 @@ static int t6040_usb3_power_on(struct phy *phy) break; default: dev_info(tphy->dev, "USB3: no plug orientation, USB2 only\n"); - return 0; + return; } /* PIPE_HANDLER_DEFAULT: the dummy PIPE clock the controller keeps using at USB2 */ @@ -918,7 +925,7 @@ static int t6040_usb3_power_on(struct phy *phy) tphy->usb3_up = true; dev_dbg(tphy->dev, "USB3 PHY up, lane %d\n", swap); - return 0; + return; usb2_only: /* power_off skips a PHY that never came up: undo here, so the next attach starts clean */ @@ -926,6 +933,15 @@ static int t6040_usb3_power_on(struct phy *phy) sleep: t6040_usb3_sleep(tphy); dev_warn(tphy->dev, "USB3 PHY bring-up failed (%d), USB2 only\n", ret); +} + +static int t6040_usb3_power_on(struct phy *phy) +{ + struct t6040_usb2_phy *tphy = phy_get_drvdata(phy); + + guard(mutex)(&tphy->lock); + tphy->usb3_on = true; + t6040_usb3_up_locked(tphy); return 0; } @@ -955,6 +971,8 @@ static int t6040_usb3_power_off(struct phy *phy) struct t6040_usb2_phy *tphy = phy_get_drvdata(phy); guard(mutex)(&tphy->lock); + tphy->usb3_on = false; + tphy->usb3_pipe = false; t6040_usb3_down_locked(tphy); return 0; } @@ -972,6 +990,7 @@ static int t6040_usb3_set_mode(struct phy *phy, enum phy_mode mode, int submode) return -EOPNOTSUPP; guard(mutex)(&tphy->lock); + tphy->usb3_pipe = true; if (!tphy->usb3_up || tphy->pipe_switched) return 0; t6040_usb3_pipe_switch(tphy); @@ -1353,6 +1372,31 @@ static void t6040_dp_drive(struct t6040_usb2_phy *tphy, unsigned int lane, u32 p t6040_usb2_mask32(base + (tx ? 0x100 : 0xa8), 0x7ffff, eq); } +/* + * Give the lanes back to USB3 once the DP session is over and the port has + * left DP alt mode while dwc3 still holds the USB3 phy on: the USB3 bring-up + * programs the PHY afresh, as it does after DP on the next attach, and the + * PIPE returns to the PHY if the controller had asked for it. Called when + * the DP phy powers off and when the mux leaves DP, as either can come last. + * tipd drops the USB role before it leaves DP alt mode, so on an unplug or a + * DP mode change dwc3 powers the USB3 phy off first and this does nothing. + */ +static void t6040_usb3_reclaim_locked(struct t6040_usb2_phy *tphy) +{ + lockdep_assert_held(&tphy->lock); + if (!tphy->usb3_lent || tphy->dp_up || tphy->dp_alt) + return; + tphy->usb3_lent = false; + if (!tphy->usb3_on || tphy->usb3_up) + return; + t6040_usb3_up_locked(tphy); + if (tphy->usb3_up && tphy->usb3_pipe && !tphy->pipe_switched) { + t6040_usb3_pipe_switch(tphy); + tphy->pipe_switched = true; + } + dev_info(tphy->dev, "USB3 %s after DP\n", tphy->usb3_up ? "back" : "not restored"); +} + /* ---- DP phy_ops ---- */ static int t6040_dp_power_on(struct phy *phy) @@ -1368,8 +1412,10 @@ static int t6040_dp_power_on(struct phy *phy) * lanes to DP: aciophy_phy_shutdown of the USB3 side first, then the * same mode-3 init as the fixed DP PHY. */ - if (!t6040_dp_only(tphy)) + if (!t6040_dp_only(tphy)) { t6040_usb3_down_locked(tphy); + tphy->usb3_lent = true; + } ret = t6040_dp_phy_init(tphy, tphy->dp_flip); if (ret) { dev_err(tphy->dev, "DP PHY init failed: %d\n", ret); @@ -1389,6 +1435,7 @@ static int t6040_dp_power_off(struct phy *phy) guard(mutex)(&tphy->lock); tphy->dp_up = false; + t6040_usb3_reclaim_locked(tphy); return 0; } @@ -1506,6 +1553,7 @@ static int t6040_dp_mux_set(struct typec_mux_dev *mux, struct typec_mux_state *s dp ? "DP" : "non-DP", state->mode, alt, tphy->dp_flip ? "reversed" : "normal"); if (alt && tphy->dp_up) t6040_dp_xbar_config(tphy, tphy->dp_flip); + t6040_usb3_reclaim_locked(tphy); return 0; } From bd2cdf39d822e8fc98ab26a5d4a03befa21d8d8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franti=C5=A1ek=20Bal=C3=A1zsy?= Date: Sun, 25 Oct 2026 18:11:12 +0100 Subject: [PATCH 131/184] drm/apple: say how desktop 26.x uses the Type-C fabric MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The comment in dcp_is_typec_output() still described the private route the previous commit removed. Desktop 26.x now takes its Type-C route from the fabric and only drives the link through its own compact DPTX connect. Signed-off-by: František Balázsy --- drivers/gpu/drm/apple/dcp-fabric.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/dcp-fabric.c b/drivers/gpu/drm/apple/dcp-fabric.c index 629b33ddace2c..f12bf78ec7fc7 100644 --- a/drivers/gpu/drm/apple/dcp-fabric.c +++ b/drivers/gpu/drm/apple/dcp-fabric.c @@ -103,7 +103,11 @@ static bool dcp_hdmi_borrowed(void *ctx); bool dcp_is_typec_output(struct apple_dcp *dcp) { - /* Desktop 26.x keeps its own Type-C route, outside this fabric. */ + /* + * Desktop 26.x takes its Type-C route from this fabric but drives the + * link through its own compact DPTX connect (dcp_desktop_dptx_connect()), + * not the Type-C output session below. + */ if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) return false; return dcp->active_typec_route || From 4299dc01bb2bab0b48a730f15bbad5d5f5b3ed08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franti=C5=A1ek=20Bal=C3=A1zsy?= Date: Sun, 25 Oct 2026 18:41:09 +0100 Subject: [PATCH 132/184] drm/apple: order desktop PHY power against route release, free the pipeline with the sink MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review of the Type-C fabric series found four holes on the T6041 path. dcp_typec_phy_on() looked at the active route under hpd_mutex only, while the deactivate powers the PHY off and releases the crossbar under tb_lock. A connect racing a release could power the PHY after the crossbar was deselected and leave phy_powered set. Take tb_lock around the check and the power-on, and refuse a route whose crossbar is already released. The start-up gate in dcp_start_desktop() is again satisfied by a Type-C route, so a restart after an unplug (dcp->phy is NULL then) still brings the DPTX services up, and the start-up link is only connected when a fixed PHY or an active route exists: an idle pipeline has no powered PHY and no crossbar selection to connect on. When HPD drops while the port stays in DP alt mode, the route, its crossbar and the PHY were kept. Release them with the sink, as a mode change does, so a display on another port is not refused as busy; the route is claimed again when HPD returns. Check that a desktop callback record lies inside the shared buffer before its header is read, and access dcp->xbar with READ_ONCE()/WRITE_ONCE() as it is read by the DPTX worker without a lock. Signed-off-by: František Balázsy --- drivers/gpu/drm/apple/dcp-fabric.c | 39 +++++++++++++++++++++++++++--- drivers/gpu/drm/apple/dcp.c | 10 ++++++-- drivers/gpu/drm/apple/dptxep.c | 5 ++-- drivers/gpu/drm/apple/iomfb.c | 11 +++++++-- 4 files changed, 55 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp-fabric.c b/drivers/gpu/drm/apple/dcp-fabric.c index f12bf78ec7fc7..4b0dcedd9773c 100644 --- a/drivers/gpu/drm/apple/dcp-fabric.c +++ b/drivers/gpu/drm/apple/dcp-fabric.c @@ -345,9 +345,23 @@ static int dcp_typec_route_phy_on(struct apple_dcp_typec_route *route) */ int dcp_typec_phy_on(struct apple_dcp *dcp) { - struct apple_dcp_typec_route *route = READ_ONCE(dcp->active_typec_route); + struct apple_dcp_typec_route *route; - return route ? dcp_typec_route_phy_on(route) : 0; + /* + * Ordered against dcp_typec_route_deactivate(), which powers the PHY + * off and releases the crossbar under tb_lock: a connect racing the + * release either powers the PHY first and has it taken down again, or + * finds the route released and powers nothing. A desktop connect does + * not take the fabric lock, so tb_lock is the common one. + */ + guard(mutex)(&dcp->tb_lock); + route = READ_ONCE(dcp->active_typec_route); + if (!route) + return 0; + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && + route->deactivate_xbar_done) + return -ENODEV; + return dcp_typec_route_phy_on(route); } static void dcp_typec_route_phy_off(struct apple_dcp_typec_route *route) @@ -428,7 +442,7 @@ static int dcp_typec_route_activate(struct apple_dcp_typec_route *route, * a link configuration (dptxport_compact_xbar_link()) through dcp->xbar. */ if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) - dcp->xbar = route->xbar; + WRITE_ONCE(dcp->xbar, route->xbar); dcp->dptx_phy = route->dptx_phy; dcp->connector_type = DRM_MODE_CONNECTOR_USB; WRITE_ONCE(dcp->ext_backlight, false); @@ -614,7 +628,7 @@ static int dcp_typec_route_deactivate(struct apple_dcp_typec_route *route) dcp->phy = dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP ? NULL : dcp->typec_routes[0].phy; if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) - dcp->xbar = NULL; + WRITE_ONCE(dcp->xbar, NULL); dcp->dptx_phy = dcp->typec_routes[0].dptx_phy; dcp->connector_type = DRM_MODE_CONNECTOR_USB; } @@ -1509,6 +1523,23 @@ static int dcp_typec_route_set(struct typec_mux_dev *mux, if (ret && !dcp_is_external_h17p(dcp)) return ret; } + /* + * Desktop 26.x has one Type-C pipeline and powers the PHY with + * the route. A sink that went away while the port stays in DP + * alt mode (a dock whose monitor was unplugged) must give back + * the pipeline, its crossbar and the PHY, or a display on + * another port is refused as busy. The route is taken again + * when HPD returns. + */ + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) { + port->preferred_route = port->owner; + port->hpd = false; + ret = dcp_typec_route_deactivate(port->owner); + if (ret) + return ret; + port->owner = NULL; + dcp_typec_pipeline_freed(); + } } /* did the pairing pass count this port's direct stream so far? */ diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 69a5a55ae63eb..f02b8ca5f6fd6 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -3288,7 +3288,8 @@ static int dcp_start_desktop_external(struct apple_dcp *dcp, u32 map) if (ret) return ret; - if (!dcp->phy) { + /* An idle Type-C pipeline has no PHY until a route is claimed. */ + if (!dcp->phy && !dcp->nr_typec_routes) { dev_warn(dcp->dev, "no DP PHY and no Type-C route: no display link\n"); return 0; } @@ -3316,7 +3317,12 @@ static int dcp_start_desktop_external(struct apple_dcp *dcp, u32 map) if (ret) return ret; - if (!dptx_ret && dptx_service_available(&dcp->dptxport[0])) { + /* + * A Type-C route powers the PHY and selects the crossbar when it is + * claimed; until then there is no link to bring up. + */ + if (!dptx_ret && dptx_service_available(&dcp->dptxport[0]) && + (dcp->fixed_phy || dcp->active_typec_route)) { ret = dcp_dptx_connect(dcp, 0); if (ret) dev_info(dcp->dev, "no DPTX link at startup: %d\n", ret); diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c index a7620457f1f46..a03efb93bdc91 100644 --- a/drivers/gpu/drm/apple/dptxep.c +++ b/drivers/gpu/drm/apple/dptxep.c @@ -490,11 +490,12 @@ static int dptxport_call_get_link_rate(struct apple_epic_service *service, static int dptxport_compact_xbar_link(struct apple_epic_service *service, bool up) { struct apple_dcp *dcp = service->ep->dcp; + struct mux_control *xbar = READ_ONCE(dcp->xbar); int ret; - if (!READ_ONCE(service->ep->compact) || !dcp->xbar) + if (!READ_ONCE(service->ep->compact) || !xbar) return 0; - ret = dcp_dpxbar_link(dcp->xbar, up); + ret = dcp_dpxbar_link(xbar, up); dev_dbg(dcp->dev, "DPTXPort: crossbar link %s: %d\n", up ? "up" : "down", ret); return ret; } diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 4b9ccafa0338c..a354580f70dd6 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -1415,6 +1415,14 @@ static void dcpep_handle_cb(struct apple_dcp *dcp, enum dcp_context_id context, return; } + /* The header is read below: check the record lies in the shared buffer first. */ + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && + (length < sizeof(*hdr) || offset >= 0x8000 || length > 0x8000 - offset)) { + dev_err(dev, "invalid IOMFB callback envelope\n"); + dcp->crashed = true; + return; + } + tag = dcp_parse_tag(hdr->tag); if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && @@ -1429,8 +1437,7 @@ static void dcpep_handle_cb(struct apple_dcp *dcp, enum dcp_context_id context, handled = tag >= 0 && tag < IOMFB_MAX_CB && dcp->cb_handlers && dcp->cb_handlers[tag]; if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && handled && - (length < sizeof(*hdr) || - (u64)sizeof(*hdr) + hdr->in_len + hdr->out_len > length || + ((u64)sizeof(*hdr) + hdr->in_len + hdr->out_len > length || !iomfb_check_callback_v26_6_desktop(tag, hdr->in_len, hdr->out_len))) { dev_err(dev, "short IOMFB callback %d (%u, %u, length %u)\n", tag, hdr->in_len, hdr->out_len, length); From 3c753216c4c56745aab9fa9951f91399f098d1e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franti=C5=A1ek=20Bal=C3=A1zsy?= Date: Sun, 25 Oct 2026 18:41:09 +0100 Subject: [PATCH 133/184] selftests: apple-dcp: follow the desktop 26.6 fabric and callback checks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The fabric extraction now reaches dcp_typec_route_phy_off() and the DCP_FIRMWARE_V_26_6_DESKTOP tests, and the dispatcher extraction the desktop callback check. Add the define and, as for the other desktop paths, a stub that asserts if a non-desktop scenario reaches the check; the PHY power-off is a no-op there as the real one returns for an unpowered PHY. Seven of the eight units that stopped compiling build and pass again; mux-release-stages needs -Wno-unused-but-set-global with clang 23. Signed-off-by: František Balázsy --- tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py | 3 +++ tools/testing/selftests/drivers/gpu/apple-dcp/quiesce.py | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py b/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py index 9c70ecf5aeda9..d306e52f80f0c 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py @@ -1879,6 +1879,9 @@ def dispatcher(root): static struct dcp_channel *dcp_get_channel(struct apple_dcp *d, enum dcp_context_id c) { (void)c; return &d->channel; } static int dcp_parse_tag(char *p) { (void)p; return case_tag; } +/* A desktop 26.6 callback is not under test here: no scenario selects that firmware. */ +static bool __attribute__((unused)) iomfb_check_callback_v26_6_desktop(int tag, u32 in_len, u32 out_len) +{ (void)tag; (void)in_len; (void)out_len; assert(!"desktop 26.6 callback in an H17P scenario"); return false; } static bool of_machine_is_compatible(const char *n) { (void)n; return machine; } static bool of_device_is_compatible(struct device_node *n, const char *s) { (void)s; return n->external; } diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/quiesce.py b/tools/testing/selftests/drivers/gpu/apple-dcp/quiesce.py index 48d7c3078092c..30eb0a183d6b7 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/quiesce.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/quiesce.py @@ -80,6 +80,10 @@ def make_unit(source, parent=False, reference=None): static struct drm_device *dcp_typec_drm(void) { return NULL; } static void dcp_typec_plan(struct drm_device *d,struct apple_dcp_typec_port *p,unsigned n) { (void)d;(void)p;(void)n; } static bool dcp_typec_tunnel_held(struct apple_dcp *d) { (void)d;return false; } +/* The desktop 26.6 (T6041) Type-C path is not under test: nothing here is on that firmware. */ +#define DCP_FIRMWARE_V_26_6_DESKTOP 100 +static void __attribute__((unused)) dcp_typec_route_phy_off(struct apple_dcp_typec_route *r) +{ (void)r; /* never powered: the real one returns at once */ } static int dcp_typec_route_deactivate(struct apple_dcp_typec_route *r) { deactivate_calls++;r->selected=false;return 0; } static void dcp_typec_port_attach(struct apple_dcp_typec_port *p) { (void)p; } static int gpiod_get_value_cansleep(void *p) { (void)p;return 0; } From c96ebf0c5bc7a7aa47a1b29d635cbfd16c2baf90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franti=C5=A1ek=20Bal=C3=A1zsy?= Date: Sun, 25 Oct 2026 19:39:47 +0100 Subject: [PATCH 134/184] drm/apple: connect desktop 26.x Type-C displays through the Type-C output session MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A desktop 26.x Type-C display took its route from the fabric but was connected by dcp_desktop_dptx_connect(), outside the session the other SoCs use: no attach and detach bookkeeping (cable flag, generation, firmware metadata, unplug count), no relink, no reconnect retry and none of the IRQ_HPD handling the fabric already dispatches to it. dcp_is_typec_output() is no longer false for the desktop firmware, and dcp_dptx_connect(), dcp_dptx_connect_oob() and dcp_dptx_disconnect_oob() take the common path for it. What compact DPTX does differently stays where it differs: - the connection validation call does not exist, so it is skipped; - the PHY is powered when the display connects, before the DPTX connect; - a link configuration that has not arrived when the wait ends is not an error, it comes once a sink arrives; - a disconnect marks the external pipe as shut down by the firmware. The fixed HDMI output (dcpext0) has no Type-C session and keeps dcp_desktop_dptx_connect(). Signed-off-by: František Balázsy --- drivers/gpu/drm/apple/dcp-fabric.c | 7 ------ drivers/gpu/drm/apple/dcp.c | 34 +++++++++++++++++++++++------- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp-fabric.c b/drivers/gpu/drm/apple/dcp-fabric.c index 4b0dcedd9773c..09b5ec9652a0c 100644 --- a/drivers/gpu/drm/apple/dcp-fabric.c +++ b/drivers/gpu/drm/apple/dcp-fabric.c @@ -103,13 +103,6 @@ static bool dcp_hdmi_borrowed(void *ctx); bool dcp_is_typec_output(struct apple_dcp *dcp) { - /* - * Desktop 26.x takes its Type-C route from this fabric but drives the - * link through its own compact DPTX connect (dcp_desktop_dptx_connect()), - * not the Type-C output session below. - */ - if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) - return false; return dcp->active_typec_route || dcp->fixed_connector_type == DRM_MODE_CONNECTOR_USB; } diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index f02b8ca5f6fd6..ef92b5fcad71c 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -1582,6 +1582,7 @@ static int dcp_dptx_connect_raw(struct apple_dcp *dcp, u32 port, bool *retryable { /* T8122 dcpext: HPD only after the link wait, see dcp_ext_dptx_607() */ bool late_hpd = dcp_ext_dptx_607(dcp); + bool compact; unsigned long timeout; u64 generation; u8 dfp_port; @@ -1607,6 +1608,8 @@ static int dcp_dptx_connect_raw(struct apple_dcp *dcp, u32 port, bool *retryable dcp->connector_type, dcp->dptxport[port].connected); mutex_lock(&dcp->hpd_mutex); + compact = dptx_service_available(&dcp->dptxport[port]) && + READ_ONCE(dcp->dptxport[port].service->ep->compact); if (!dcp_connect_session_valid_locked(dcp, session, cable, recovery)) { ret = -ESTALE; goto out_unlock; @@ -1635,9 +1638,14 @@ static int dcp_dptx_connect_raw(struct apple_dcp *dcp, u32 port, bool *retryable reinit_completion(&dcp->dptxport[port].linkcfg_completion); WRITE_ONCE(dcp->dptxport[port].tile_hint, false); dcp->dptxport[port].atcphy = dcp->phy; + /* A desktop 26.x route powers its PHY when the display connects. */ + ret = dcp_typec_phy_on(dcp); + if (ret) + goto out_unlock; /* a tiled display's second half comes in on the port's dpin1 */ dfp_port = port && READ_ONCE(dcp->split.active) ? 2 : dcp->dptx_dfp_port; - if (!dcp_ext_dptx_607(dcp)) { + /* Compact DPTX (desktop 26.x) has no connection validation call. */ + if (!dcp_ext_dptx_607(dcp) && !compact) { ret = dptxport_validate_connection(dcp->dptxport[port].service, dfp_port, dcp->dptx_phy, dcp->dptx_die); @@ -1712,7 +1720,12 @@ static int dcp_dptx_connect_raw(struct apple_dcp *dcp, u32 port, bool *retryable /* A revoked wait must not disconnect or publish readiness for a new session. */ if (!dcp_connect_session_valid(dcp, session, cable, recovery)) return -ESTALE; - if (!ret && late_hpd) { + if (!ret && compact) { + /* Without a sink the link is configured once one arrives. */ + dev_dbg(dcp->dev, + "dcp_dptx_connect: no link configuration on port %u yet\n", port); + ret = 0; + } else if (!ret && late_hpd) { /* Without HPD this firmware does not train: assert it now. */ dev_dbg(dcp->dev, "dcp_dptx_connect: no link configuration on port %u yet, asserting HPD\n", @@ -2265,7 +2278,8 @@ int dcp_dptx_connect(struct apple_dcp *dcp, u32 port) struct dcp_fabric_session session = {}; int ret; - if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) + /* The fixed HDMI output of a desktop 26.x processor has no Type-C session. */ + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && !dcp_is_typec_output(dcp)) return dcp_desktop_dptx_connect(dcp, port); if (!dcp_is_external_h17p(dcp)) { @@ -2852,7 +2866,7 @@ int dcp_dptx_connect_oob(struct platform_device *pdev, u32 port) { struct apple_dcp *dcp = platform_get_drvdata(pdev); - if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && !dcp_is_typec_output(dcp)) return dcp_desktop_dptx_connect(dcp, port); if (dcp_is_external_h17p(dcp)) { @@ -2895,8 +2909,9 @@ int dcp_dptx_connect_oob(struct platform_device *pdev, u32 port) int dcp_dptx_disconnect_oob(struct platform_device *pdev, u32 port) { struct apple_dcp *dcp = platform_get_drvdata(pdev); + int ret; - if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && !dcp_is_typec_output(dcp)) return dcp_desktop_dptx_disconnect_oob(dcp, port); if (dcp_is_external_h17p(dcp)) @@ -2924,13 +2939,16 @@ int dcp_dptx_disconnect_oob(struct platform_device *pdev, u32 port) av_service_disconnect(dcp); if (dptx_service_available(&dcp->dptxport[port])) { - int ret = dptxport_set_hpd(dcp->dptxport[port].service, false); - + ret = dptxport_set_hpd(dcp->dptxport[port].service, false); if (ret) return ret; } - return dcp_dptx_disconnect(dcp, port); + ret = dcp_dptx_disconnect(dcp, port); + /* The firmware shuts a desktop 26.x external pipe down with its display. */ + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && !dcp->main_display) + dcp->desktop->ext_pipe_off = true; + return ret; } /* From d9ffff98f55cc1aad847ffc271f4683c72dee371 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franti=C5=A1ek=20Bal=C3=A1zsy?= Date: Sun, 25 Oct 2026 19:45:14 +0100 Subject: [PATCH 135/184] selftests: apple-dcp: the fast-replug scenario is not compact DPTX MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dcp_dptx_connect_raw() now asks the DPTX endpoint whether it is compact and has the route power the PHY. This scenario's endpoint is the classic one and its PHY needs no route: report not compact and stub the PHY power-on. Signed-off-by: František Balázsy --- tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py b/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py index d306e52f80f0c..518cf23473d61 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py @@ -490,6 +490,8 @@ def bounce(root): m = re.search(r'^static int dcp_dptx_connect_raw\(', dcp, re.M) assert m, 'dcp_dptx_connect_raw' raw = block(dcp, m.start()) + # Compact DPTX is the desktop 26.x wire; this scenario's endpoint is not. + raw = raw.replace('READ_ONCE(dcp->dptxport[port].service->ep->compact)', 'false') def swap(old, new): nonlocal unit @@ -510,6 +512,8 @@ def swap(old, new): swap(old_raw, 'static int dcp_dptx_connect_raw(struct apple_dcp *, u32, bool *' + (', const struct dcp_fabric_session *, bool, bool' if sessions else '') + ');') swap('static int dptxport_set_hpd(void *p, bool on)\n{ (void)p; assert(!on); return hpd_error; }', + '/* The route powers a desktop 26.x PHY at connect: nothing to do for this scenario. */\n' + 'static int __attribute__((unused)) dcp_typec_phy_on(struct apple_dcp *d) { (void)d; return 0; }\n' 'static bool fw_hpd, fw_display, link_trains;\n' 'static int hpd_drops, connects, release_error_once, assert_error;\n' '/* A request that times out may still be carried out; a refused one is not. */\n' From fa0966306684bf116181edf58707f488b3fe3c8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franti=C5=A1ek=20Bal=C3=A1zsy?= Date: Sun, 25 Oct 2026 17:13:26 +0100 Subject: [PATCH 136/184] dt-bindings: power: apple: add apple,irq-safe to the PMP report entries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit apple,irq-safe on a PMP report entry marks an entry that parents the IRQ-safe PMGR power states of a coprocessor: genpd requires the parent of an IRQ-safe subdomain to be IRQ safe as well. Signed-off-by: František Balázsy --- .../devicetree/bindings/power/apple,t8140-pmp-report.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Documentation/devicetree/bindings/power/apple,t8140-pmp-report.yaml b/Documentation/devicetree/bindings/power/apple,t8140-pmp-report.yaml index e8eac9e5411e3..e79ea331865b5 100644 --- a/Documentation/devicetree/bindings/power/apple,t8140-pmp-report.yaml +++ b/Documentation/devicetree/bindings/power/apple,t8140-pmp-report.yaml @@ -93,6 +93,13 @@ patternProperties: holds this entry's device ID, instead of by reg. Only valid on a report whose PMP the host starts. + apple,irq-safe: + type: boolean + description: + The entry is the parent of IRQ-safe PMGR power states and is + switched with interrupts disabled. Setting the state takes a + spinlock and polls the acknowledgment atomically. + required: - compatible - reg From 540f52fa22b1b343f582fe128848d7c0a2ae6c08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franti=C5=A1ek=20Bal=C3=A1zsy?= Date: Sun, 25 Oct 2026 01:25:45 +0200 Subject: [PATCH 137/184] pmdomain: apple: pmp-report: IRQ-safe entries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A PMP device-state entry that parents PMGR power states (which are IRQ safe) must be IRQ safe too, or genpd refuses the subdomain. Setting the state takes a spinlock and polls the acknowledgment atomically, so the entry may run with interrupts disabled. Opt in with apple,irq-safe. Tested on a MacBook Pro M4 Pro (J616s): the DISPEXT0 and DISPEXT1 device-state entries parent the dispext0/1 PMGR chains that power the HDMI and USB-C DisplayPort controllers. Signed-off-by: František Balázsy --- drivers/pmdomain/apple/pmp-report.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/pmdomain/apple/pmp-report.c b/drivers/pmdomain/apple/pmp-report.c index 823669e27ad74..aaa38c06daaaf 100644 --- a/drivers/pmdomain/apple/pmp-report.c +++ b/drivers/pmdomain/apple/pmp-report.c @@ -1473,6 +1473,13 @@ static int apple_pmp_report_entry_probe(struct platform_device *pdev) ent->genpd.power_on = apple_pmp_report_entry_power_on; ent->genpd.power_off = apple_pmp_report_entry_power_off; + /* + * An entry that is the parent of PMGR power states (which are IRQ safe) + * must be IRQ safe too. Setting the state takes a spinlock and polls + * the acknowledgment atomically, so it may be called with IRQs off. + */ + if (of_property_read_bool(node, "apple,irq-safe")) + ent->genpd.flags |= GENPD_FLAG_IRQ_SAFE; ret = pm_genpd_init(&ent->genpd, NULL, true); if (ret) { From 0e553f35ebba58b41b088027e2d3911d93219b44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franti=C5=A1ek=20Bal=C3=A1zsy?= Date: Mon, 26 Oct 2026 15:55:27 +0100 Subject: [PATCH 138/184] dt-bindings: display: apple,dcp: add apple,t6041-dcpext MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The M4 Pro (T6041) external display processors use the generic dcpext fallback like the other dcpext variants; the driver already matches apple,t6041-dcpext. Signed-off-by: František Balázsy --- Documentation/devicetree/bindings/display/apple,dcp.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/display/apple,dcp.yaml b/Documentation/devicetree/bindings/display/apple,dcp.yaml index d79ad457f7703..3d691822c50b1 100644 --- a/Documentation/devicetree/bindings/display/apple,dcp.yaml +++ b/Documentation/devicetree/bindings/display/apple,dcp.yaml @@ -33,6 +33,7 @@ properties: - apple,t8112-dcpext - apple,t6000-dcpext - apple,t6020-dcpext + - apple,t6041-dcpext - const: apple,dcpext - items: - const: apple,t8140-dcpext From 204e304f1045936f0871517793a8c33eb156c132 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franti=C5=A1ek=20Bal=C3=A1zsy?= Date: Mon, 26 Oct 2026 15:52:02 +0100 Subject: [PATCH 139/184] arm64: dts: apple: t6040-j616s: external display chain MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Describe the J616s external display chain, all disabled: dcpext0 behind the HDMI port (atc3 display crossbar, atcphy3, the DP-to-HDMI converter), dcpext1 behind the three Type-C ports (display fabric routes, atc0..2 crossbars), their DISPEXT power domains with the PMP acknowledgement entries, the display DARTs and mailboxes, and the dcpext firmware memory as disabled placeholders that the boot loader fills from the ADT. Nothing is enabled by the device tree: powering the DISPEXT chain without the PMP acknowledgement hangs the machine before root, and a boot loader that does not provide the firmware memory leaves the placeholders disabled. A gate enables the chain (next commit). Signed-off-by: František Balázsy --- .../dts/apple/t6040-j616s-extdisp-hdmi.dtsi | 308 ++++++++++++++++ .../apple/t6040-j616s-extdisp-mem-loader.dtsi | 72 ++++ .../dts/apple/t6040-j616s-extdisp-usbc.dtsi | 183 ++++++++++ .../boot/dts/apple/t6040-j616s-extdisp.dtsi | 345 ++++++++++++++++++ arch/arm64/boot/dts/apple/t6040-j616s.dts | 6 + 5 files changed, 914 insertions(+) create mode 100644 arch/arm64/boot/dts/apple/t6040-j616s-extdisp-hdmi.dtsi create mode 100644 arch/arm64/boot/dts/apple/t6040-j616s-extdisp-mem-loader.dtsi create mode 100644 arch/arm64/boot/dts/apple/t6040-j616s-extdisp-usbc.dtsi create mode 100644 arch/arm64/boot/dts/apple/t6040-j616s-extdisp.dtsi diff --git a/arch/arm64/boot/dts/apple/t6040-j616s-extdisp-hdmi.dtsi b/arch/arm64/boot/dts/apple/t6040-j616s-extdisp-hdmi.dtsi new file mode 100644 index 0000000000000..b414a0bddee7d --- /dev/null +++ b/arch/arm64/boot/dts/apple/t6040-j616s-extdisp-hdmi.dtsi @@ -0,0 +1,308 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * The fixed HDMI path of t6040-j616s-extdisp.dtsi (dcpext0 -> atc3 crossbar + * -> atcphy3 -> DP-to-HDMI converter) with every node left DISABLED. The + * nodes must not be enabled at boot in the device tree: powering the DISPEXT + * chain without the PMP acknowledgement hangs the machine before root. The + * enable gate (drivers/soc/apple/t6041-display-gate.c) switches them on in + * order once the PMP is bound. + * + * dispext0's DART is not in the display node's iommus: the gate appends it + * when it enables the chain, so the internal display's DART domain stays as + * it is without the external displays. + */ + +/* + * DISPEXT0 may only be powered once the PMP has acknowledged it in its + * device-state table (SOC-DEV-PS-REQ/ACK, PTD entries 480/488 on T6041): the + * soc-device record of DISPEXT0 has ID 18 (ADT iop-pmp-nub soc-device, ack + * flag set), so its request bit is 17. Without it, enabling the DISPEXT0 PMGR + * chain hung the machine before root. + * Same contract as the T6030 PMP device-state provider of + * aurora-silicon/linux#358, through the existing pmp-report entries here. + */ +&pmp_report { + pmp_dispext0: report@11 { + compatible = "apple,t6041-pmp-v2-report-entry", + "apple,t6000-pmp-v2-report-entry"; + reg = <0x11>; + label = "pmp-dispext0"; + #power-domain-cells = <0>; + /* parent of the IRQ-safe PMGR chain */ + apple,irq-safe; + status = "disabled"; + }; +}; + +&ps_dispext0_sys { + power-domains = <&pmp_dispext0>; + status = "disabled"; +}; + +&ps_dispext0_fe { + status = "disabled"; +}; + +&ps_dispext0_cpu { + /* + * NEVER enable: the ADT marks DISPEXT0_CPU no_ps (no PMGR power-state + * register, like the internal DISP_CPU). Its probe reads offset 0x350, + * and that read resets the SoC. DCPEXT0's own power gate (ADT power-gates 431) is the + * virtual DISPEXT0-CPU-V; the last real register of the chain is FE. + */ + /delete-property/ apple,always-on; + status = "disabled"; +}; + +&ps_atc3_common { + status = "disabled"; +}; + +&ps_atc3_common_dp { + status = "disabled"; +}; + +&ps_atc3_usb_aon { + status = "disabled"; +}; + +&ps_atc3_phymxwrap { + status = "disabled"; +}; + +&pmgr_mini { + /* ADT ATC3_USB: +0x148, parents ATC3_COMMON and ATC3_USB_AON; the crossbar's domain as on T6030 */ + ps_atc3_usb: power-controller@148 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x148 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc3_usb"; + power-domains = <&ps_atc3_common>, <&ps_atc3_usb_aon>; + status = "disabled"; + }; +}; + +&pinctrl_aop { + status = "disabled"; +}; + +&dcpext0_dart { + /* in the DISPEXT0 block: powered (after the PMP request) before probing */ + power-domains = <&ps_dispext0_fe>; + status = "disabled"; +}; + +&dispext0_dart { + /* in the DISPEXT0 block: powered (after the PMP request) before probing */ + power-domains = <&ps_dispext0_fe>; + status = "disabled"; +}; + +&dcpext0_mbox { + power-domains = <&ps_dispext0_fe>; + status = "disabled"; +}; + +&dcpext0 { + power-domains = <&ps_dispext0_fe>; + /* + * The IOMFB surface layout of the internal DCP; + * without apple,iomfb-surfaces apple-dcp's probe fails with -EINVAL. + */ + apple,iomfb-surfaces = <1 1 0 0>; + status = "disabled"; +}; + +&atcphy3_mbox { + status = "disabled"; +}; + +&atcphy3_xbar { + power-domains = <&ps_atc3_usb>; + status = "disabled"; +}; + +/* + * atcphy3 apply_tunables tables (aciophy_phy_init mode 3), from this machine's ADT /arm-io/atc-phy3 + * (privateartifacts/omarchy-m4-unit/adt.bin): AXI2AF converted entry by entry; every other table is + * byte-identical to atc-phy0 in the ADT, so the atcphy0 values of t6040-j616s-usb3.dtsi are reused + * (AUS40CMN_SHM differs: 0x50). Only the first six (axi2af..lane1) are applied in DP mode. + */ +&atcphy3 { + /* ATC3 ADT tunable_ATC0AXI2AF (port-specific), entries */ + apple,tunable-axi2af = + <0x0 0x800000ff 0x80000025>, + <0x4 0x80ff00ff 0x80250003>, + <0x8 0x80ff00ff 0x80250003>, + <0xc 0x80ff00ff 0x80250005>, + <0x10 0x80ff00ff 0x80250003>, + <0x14 0x80ff00ff 0x80250003>, + <0x18 0x80ff00ff 0x80250003>, + <0x1c 0x80ff00ff 0x80250003>, + <0x20 0x800000ff 0x8000001e>, + <0x24 0x800000ff 0x8000004a>, + <0x28 0x80ff00ff 0x804a0006>, + <0x2c 0x80ff00ff 0x804a0006>, + <0x30 0x80ff00ff 0x804a0006>, + <0x34 0x80ff00ff 0x804a0006>, + <0x38 0x80ff00ff 0x804a0006>, + <0x3c 0x80ff00ff 0x804a0006>, + <0x40 0x800000ff 0x8000001e>, + <0x44 0x800000ff 0x8000001e>, + <0x48 0x800000ff 0x8000001e>, + <0x100 0x000000ff 0x000000ff>, + <0x108 0xffff0006 0x00100000>, + <0x304 0x3f3f0000 0x04040000>, + <0x308 0x000fffff 0x00001681>, + <0x30c 0x000fffff 0x00001680>, + <0x310 0x70000fff 0x000001e0>, + <0x314 0xf0000fff 0x800001e0>, + <0x318 0x70000fff 0x000001e0>, + <0x31c 0xf0000fff 0x800001e0>, + <0x320 0x70000fff 0x000005a0>, + <0x324 0xf0000fff 0x800005a0>, + <0x328 0x70000fff 0x000005a0>, + <0x32c 0xf0000fff 0x800005a0>, + <0x330 0x70000fff 0x000005a0>, + <0x334 0xf0000fff 0x800005a0>, + <0x338 0x70000fff 0x000005a0>, + <0x33c 0x70000fff 0x000005a0>, + <0x340 0x70000fff 0x000005a0>, + <0x344 0x70000fff 0x000005a0>, + <0x348 0x70000fff 0x000005a0>, + <0x34c 0x70000fff 0x000005a0>, + <0x350 0x70000fff 0x000005a0>, + <0x358 0xffffffff 0x0000ffff>, + <0x35c 0xffffffff 0x0000ffff>, + <0x408 0xffff0020 0x00210020>, + <0x40c 0x000fffff 0x00001681>, + <0x410 0x000fffff 0x00001680>, + <0x414 0x70000fff 0x000001e0>, + <0x418 0x70000fff 0x000001e0>, + <0x41c 0x70000fff 0x000001e0>, + <0x420 0x70000fff 0x000001e0>, + <0x424 0x70000fff 0x000005a0>, + <0x428 0x70000fff 0x000005a0>, + <0x42c 0x70000fff 0x000005a0>, + <0x430 0x70000fff 0x000005a0>, + <0x434 0x70000fff 0x000005a0>, + <0x438 0x70000fff 0x000005a0>, + <0x43c 0x70000fff 0x000005a0>, + <0x440 0x70000fff 0x000005a0>, + <0x444 0x70000fff 0x000005a0>, + <0x448 0x70000fff 0x000005a0>, + <0x44c 0x70000fff 0x000005a0>, + <0x450 0x70000fff 0x000005a0>, + <0x454 0x70000fff 0x000005a0>, + <0x948 0x07ff07ff 0x00280000>, + <0xa48 0x07ff07ff 0x00400040>, + <0xb48 0x07ff07ff 0x00b8010e>, + <0xc48 0x07ff07ff 0x00400020>, + <0xd48 0x07ff07ff 0x008a008a>, + <0x1000 0xffffffff 0x7fffffff>, + <0x4008 0x00000001 0x00000001>, + <0x400c 0x00000001 0x00000001>, + <0x4014 0x00000001 0x00000001>, + <0x4018 0x00000001 0x00000000>, + <0x401c 0x00000001 0x00000001>; + /* the remaining tables equal atc-phy0 in the ADT, except AUS40CMN_SHM (0x50) */ + apple,tunable-fabric = + <0x4 0x003f003f 0x00200020>, + <0xc 0x000000f1 0x000000a1>, + <0x10 0x0000801f 0x00000004>, + <0x18 0x003f003f 0x00200020>, + <0x20 0x000000f1 0x000000a1>, + <0x24 0x0000801f 0x00000004>, + <0x2c 0x00ff00ff 0x00960096>, + <0x34 0x000000f1 0x00000071>, + <0x38 0x0000801f 0x00000004>, + <0x40 0x0000003f 0x00000020>, + <0x48 0x0000801f 0x00000004>, + <0x50 0x007f007f 0x00450045>, + <0x58 0x0000801f 0x00000004>, + <0x60 0x0000003f 0x00000020>, + <0x68 0x0000801f 0x00000004>, + <0x70 0x00000001 0x00000001>, + <0x74 0x0000801f 0x00000004>, + <0x7c 0x007f007f 0x00400040>, + <0x80 0x000000f1 0x00000011>, + <0x84 0x00000001 0x00000001>, + <0x88 0x00000001 0x00000001>, + <0x8c 0x0000801f 0x00000004>, + <0x94 0x001f001f 0x00100010>, + <0x98 0x000000f1 0x00000011>, + <0x9c 0x0000801f 0x00000004>, + <0xa4 0x000000ff 0x000000e0>, + <0xa8 0x0000801f 0x00000004>, + <0xb0 0x0000003f 0x00000020>, + <0xb8 0x0000801f 0x00000004>, + <0xc0 0x0000003f 0x00000020>, + <0xc8 0x0000801f 0x00000004>, + <0xd0 0x001f001f 0x00100010>, + <0xd8 0x00000001 0x00000001>, + <0xdc 0x00000001 0x00000001>, + <0xe0 0x0000801f 0x00000004>, + <0xe8 0x0000000f 0x00000008>, + <0xec 0x0000801f 0x00000004>, + <0xf4 0xffffffff 0x01010101>, + <0xf8 0x0000801f 0x00000004>, + <0x100 0x00000001 0x00000001>, + <0x104 0x00000001 0x00000001>, + <0x108 0x0000801f 0x00000004>, + <0x110 0x0000801f 0x00000004>, + <0x118 0x0000801f 0x00000004>, + <0x120 0x00000001 0x00000001>, + <0x124 0x00000001 0x00000001>, + <0x128 0x0000801f 0x00000004>, + <0x130 0xffffffff 0x01010101>, + <0x134 0x0000801f 0x00000004>, + <0x13c 0x00000001 0x00000001>, + <0x140 0x00000001 0x00000001>, + <0x144 0x0000801f 0x00000004>, + <0x14c 0x00000001 0x00000001>, + <0x150 0x00000001 0x00000001>, + <0x154 0x0000801f 0x00000004>, + <0x15c 0x0000801f 0x00000004>, + <0x164 0xffffffff 0x01010101>, + <0x168 0x0000ffff 0x00000101>, + <0x16c 0x0000801f 0x00000004>, + <0x174 0x00000001 0x00000001>, + <0x178 0x00000001 0x00000001>, + <0x17c 0x0000801f 0x00000004>, + <0x184 0x00000001 0x00000001>, + <0x188 0x00000001 0x00000001>, + <0x18c 0x0000801f 0x00000004>; + apple,tunable-common-cfg = + <0x8 0x00000001 0x00000001>; + apple,tunable-axi2af-lioa = + <0x28 0x0000fe2f 0x00000000>, + <0x2c 0x00000007 0x00000000>, + <0x30 0x0000007f 0x00000000>, + <0x34 0x000000ff 0x00000000>, + <0x58 0x0000010b 0x00000000>; + apple,tunable-lane0 = + <0xd094 0x00080000 0x00080000>, + <0xd098 0x00080000 0x00080000>, + <0xd0a4 0x00800000 0x00800000>, + <0xd0a8 0x00200000 0x00200000>; + apple,tunable-lane1 = + <0x1d094 0x00080000 0x00080000>, + <0x1d098 0x00080000 0x00080000>, + <0x1d0a4 0x00800000 0x00800000>, + <0x1d0a8 0x00200000 0x00200000>; + apple,tunable-aus40cmn-shm = + <0xa04 0x0000007c 0x00000050>; + apple,tunable-auspll = + <0x3224 0x00000c00 0x00000800>, + <0x3230 0x0000001f 0x0000000f>, + <0x3240 0x07d8007f 0x04000059>, + <0x32b0 0x0b800000 0x01800000>, + <0x32e8 0x00060001 0x00040001>, + <0x3338 0x0f800000 0x07800000>; + apple,tunable-cio4pll = + <0x3a30 0x0000001f 0x0000000f>, + <0x3a4c 0x0000fe7f 0x0000501e>, + <0x3ab0 0x00700000 0x00300000>, + <0x3ae8 0x00060000 0x00020000>; +}; diff --git a/arch/arm64/boot/dts/apple/t6040-j616s-extdisp-mem-loader.dtsi b/arch/arm64/boot/dts/apple/t6040-j616s-extdisp-mem-loader.dtsi new file mode 100644 index 0000000000000..08d4768494634 --- /dev/null +++ b/arch/arm64/boot/dts/apple/t6040-j616s-extdisp-mem-loader.dtsi @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * The firmware memory of the external DCPs, for a boot loader that fills it + * (m4-loader m4_dcpext_fixup()). + * + * The loader reads /arm-io/dcpextN/iop-dcpextN-nub segment-ranges of the + * booting Mac (__TEXT, __DATA and the heap; __OS_LOG is left out as for the + * internal DCP), gives each placeholder below its reg, iommu-addresses and + * name, enables it, names the regions in the node's memory-region-names and + * sets apple,firmware-version/compat. A placeholder it does not fill is + * deleted together with the node's memory-region. A boot loader without this + * fix-up leaves them disabled, and the enable gate then keeps the external + * displays off. + * + * Nothing here enables dcpext0/1 or their DART/mailbox. + */ + +/ { + reserved-memory { + dcpext0_text: dcpext0-text { + compatible = "apple,asc-mem"; + reg = <0x0 0x0 0x0 0x4000>; + no-map; + status = "disabled"; + }; + + dcpext0_data: dcpext0-data { + compatible = "apple,asc-mem"; + reg = <0x0 0x0 0x0 0x4000>; + no-map; + status = "disabled"; + }; + + dcpext0_heap: dcpext0-heap { + compatible = "apple,asc-mem"; + reg = <0x0 0x0 0x0 0x4000>; + no-map; + status = "disabled"; + }; + + dcpext1_text: dcpext1-text { + compatible = "apple,asc-mem"; + reg = <0x0 0x0 0x0 0x4000>; + no-map; + status = "disabled"; + }; + + dcpext1_data: dcpext1-data { + compatible = "apple,asc-mem"; + reg = <0x0 0x0 0x0 0x4000>; + no-map; + status = "disabled"; + }; + + dcpext1_heap: dcpext1-heap { + compatible = "apple,asc-mem"; + reg = <0x0 0x0 0x0 0x4000>; + no-map; + status = "disabled"; + }; + }; +}; + +&dcpext0 { + memory-region = <&dcpext0_text>, <&dcpext0_data>, <&dcpext0_heap>; + memory-region-names = "text", "data", "heap"; +}; + +&dcpext1 { + memory-region = <&dcpext1_text>, <&dcpext1_data>, <&dcpext1_heap>; + memory-region-names = "text", "data", "heap"; +}; diff --git a/arch/arm64/boot/dts/apple/t6040-j616s-extdisp-usbc.dtsi b/arch/arm64/boot/dts/apple/t6040-j616s-extdisp-usbc.dtsi new file mode 100644 index 0000000000000..493dcbd3a28a0 --- /dev/null +++ b/arch/arm64/boot/dts/apple/t6040-j616s-extdisp-usbc.dtsi @@ -0,0 +1,183 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * USB-C DisplayPort: dcpext1 over any of the three Type-C ports. As for + * HDMI, the DISPEXT1 chain and dcpext1 stay DISABLED in the DTB and are + * enabled by the enable gate (drivers/soc/apple/t6041-display-gate.c). The + * routes go through the display fabric (typec-routes, end of this file): + * tipd's mode switch reaches dcpext1, which claims the port. Boot-time + * changes: mode-switch on atcphy0..2 (the PHY registers a Type-C mode + * switch; tipd drives it) and the port@3 route endpoints of the connectors. + */ + +/* PMP device-state acknowledgement for DISPEXT1 (soc-device 19, request bit 18), as DISPEXT0. */ +&pmp_report { + pmp_dispext1: report@12 { + compatible = "apple,t6041-pmp-v2-report-entry", + "apple,t6000-pmp-v2-report-entry"; + reg = <0x12>; + label = "pmp-dispext1"; + #power-domain-cells = <0>; + apple,irq-safe; + status = "disabled"; + }; +}; + +/* + * DISPEXT1_SYS sits on FAB7_SOC (ADT pmgr device id2 134, pmgr_aux 0x488, parent + * FAB6_SOC, off at boot), which t6040-pmgr.dtsi does not describe. m4.62 enabled + * DISPEXT1_SYS without it and the first DCPEXT1 register access (its DART) reset + * the SoC. DISPEXT0_SYS's parent FAB2_SOC is on anyway through ATC0..2_COMMON. + */ +&pmgr_aux { + ps_fab7_soc: power-controller@488 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x488 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "fab7_soc"; + power-domains = <&ps_fab6_soc>; + status = "disabled"; + }; +}; + +&ps_dispext1_sys { + power-domains = <&ps_fab7_soc>, <&pmp_dispext1>; + status = "disabled"; +}; + +&ps_dispext1_fe { + status = "disabled"; +}; + +&ps_dispext1_cpu { + /* NEVER enable: ADT no_ps, as DISPEXT0_CPU (its register read resets the SoC). */ + /delete-property/ apple,always-on; + status = "disabled"; +}; + +/* The per-ATC crossbars of the Type-C ports: the ATC USB domain, as atcphy3's. */ +&atcphy0_xbar { + power-domains = <&ps_atc0_usb>; + status = "disabled"; +}; + +&atcphy1_xbar { + power-domains = <&ps_atc1_usb>; + status = "disabled"; +}; + +&atcphy2_xbar { + power-domains = <&ps_atc2_usb>; + status = "disabled"; +}; + +/* tipd switches the PHY into DP alt mode through the connector graph. */ +&atcphy0 { + mode-switch; +}; + +&atcphy1 { + mode-switch; +}; + +&atcphy2 { + mode-switch; +}; + +/* + * Type-C display routes through the display fabric, as T6030 and J700: + * dcpext1 offers one route per port, each a mode switch the port controller + * drives over the connector graph (port@3). The gate enables the routes + * before devices exist, so the port controllers wait for dcpext1's mode + * switches, and probes the controllers again once dcpext1 is bound. A route + * without a registered mode switch would make the port wait for one forever. + */ +&typec0 { + ports { + port@3 { + reg = <3>; + + typec0_dcpext1: endpoint { + remote-endpoint = <&dcpext1_typec0>; + }; + }; + }; +}; + +&typec1 { + ports { + port@3 { + reg = <3>; + + typec1_dcpext1: endpoint { + remote-endpoint = <&dcpext1_typec1>; + }; + }; + }; +}; + +&typec2 { + ports { + port@3 { + reg = <3>; + + typec2_dcpext1: endpoint { + remote-endpoint = <&dcpext1_typec2>; + }; + }; + }; +}; + +&dcpext1 { + apple,connector-type = "USB-C"; + /* as dcpext0: without it apple-dcp's probe fails with -EINVAL (m4.63) */ + apple,iomfb-surfaces = <1 1 0 0>; + phys = <&atcphy0 PHY_TYPE_DP>, <&atcphy1 PHY_TYPE_DP>, <&atcphy2 PHY_TYPE_DP>; + phy-names = "typec0", "typec1", "typec2"; + mux-controls = <&atcphy0_xbar 0>, <&atcphy1_xbar 0>, <&atcphy2_xbar 0>; + mux-control-names = "typec0", "typec1", "typec2"; + /* ADT: atcN-dpphy dp-switch-dfp-endpoint N; dcpext1 ufp 1 port 0 = crossbar state 2 */ + apple,typec-dptx-phys = <0 1 2>; + apple,typec-mux-indices = <2 2 2>; + + typec-routes { + #address-cells = <1>; + #size-cells = <0>; + + route@0 { + reg = <0>; + mode-switch; + status = "disabled"; + + port { + dcpext1_typec0: endpoint { + remote-endpoint = <&typec0_dcpext1>; + }; + }; + }; + + route@1 { + reg = <1>; + mode-switch; + status = "disabled"; + + port { + dcpext1_typec1: endpoint { + remote-endpoint = <&typec1_dcpext1>; + }; + }; + }; + + route@2 { + reg = <2>; + mode-switch; + status = "disabled"; + + port { + dcpext1_typec2: endpoint { + remote-endpoint = <&typec2_dcpext1>; + }; + }; + }; + }; +}; diff --git a/arch/arm64/boot/dts/apple/t6040-j616s-extdisp.dtsi b/arch/arm64/boot/dts/apple/t6040-j616s-extdisp.dtsi new file mode 100644 index 0000000000000..affbad9ba51ad --- /dev/null +++ b/arch/arm64/boot/dts/apple/t6040-j616s-extdisp.dtsi @@ -0,0 +1,345 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * External display resources of the J616s. Everything here is disabled in + * the device tree: the enable gate (drivers/soc/apple/t6041-display-gate.c) + * switches the chain on once the boot loader has provided the firmware + * memory of the external display processors, so without that the booted + * hardware state is the same as without this file. + * + * Values come from this unit's ADT (25G83); CPU address = ADT address + + * 0x2_0000_0000. Path of the picture: + * HDMI: dcpext0 -> atc3 display crossbar -> atcphy3 (fixed DP PHY, no + * USB controller) -> on-board DP-to-HDMI converter + * USB-C: dcpext1 -> atcN display crossbar -> atcphyN (DP alt mode) + * + * Needs the DP mode of the T6040 Type-C PHY driver (phy-apple-t6040-usb2) + * with its probe path for a PHY without a USB controller (atcphy3), the + * atc-dpxbar,t604x display crossbar driver and the dcpext firmware memory + * (dcp-map style memory-region with iommu-addresses, t6040-j616s-extdisp- + * mem-loader.dtsi). + */ + +/ { + pinctrl_aop: pinctrl@510824000 { + compatible = "apple,t6041-pinctrl", "apple,t8103-pinctrl"; + reg = <0x5 0x10824000 0x0 0x4000>; + + gpio-controller; + #gpio-cells = <2>; + gpio-ranges = <&pinctrl_aop 0 0 85>; + apple,npins = <85>; + + interrupt-controller; + #interrupt-cells = <2>; + interrupt-parent = <&aic>; + interrupts = , + , + , + , + , + , + ; + status = "disabled"; + }; + + dispext0_dart: iommu@3a2300000 { + compatible = "apple,t6041-dart", "apple,t8110-dart"; + reg = <0x3 0xa2300000 0x0 0xc000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + status = "disabled"; + }; + + dcpext0_dart: iommu@3a2340000 { + compatible = "apple,t6041-dart", "apple,t8110-dart"; + reg = <0x3 0xa2340000 0x0 0x20000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + status = "disabled"; + }; + + dcpext0: dcp@3a2e00000 { + compatible = "apple,t6041-dcpext", "apple,dcpext"; + reg = <0x3 0xa2e00000 0x0 0x4000>, + <0x3 0xa0000000 0x0 0x4000000>, + <0x3 0xa2240000 0x0 0x10000>, + <0x3 0xa23a8000 0x0 0x4000>, + <0x3 0xa2800000 0x0 0x800000>; + reg-names = "coproc", "disp-0", "disp-1", "disp-2", "disp-3"; + mboxes = <&dcpext0_mbox>; + mbox-names = "mbox"; + iommus = <&dcpext0_dart 23>; + /* guessed: the internal DCP uses 0x20000, J700 DISPEXT0 is +0x8000 */ + apple,bw-scratch = <&pmgr_dcp 0 4 0x28000>; + status = "disabled"; + + dispext0_piodma: piodma { + iommus = <&dispext0_dart 16>; + }; + }; + + dcpext0_mbox: mailbox@3a2e08000 { + compatible = "apple,t6041-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x3 0xa2e08000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + status = "disabled"; + }; + + dispext1_dart: iommu@602300000 { + compatible = "apple,t6041-dart", "apple,t8110-dart"; + reg = <0x6 0x02300000 0x0 0xc000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + status = "disabled"; + }; + + dcpext1_dart: iommu@602340000 { + compatible = "apple,t6041-dart", "apple,t8110-dart"; + reg = <0x6 0x02340000 0x0 0xc000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + status = "disabled"; + }; + + dcpext1: dcp@602e00000 { + compatible = "apple,t6041-dcpext", "apple,dcpext"; + reg = <0x6 0x02e00000 0x0 0x4000>, + <0x6 0x00000000 0x0 0x4000000>, + <0x6 0x02240000 0x0 0x10000>, + <0x6 0x023a8000 0x0 0x4000>, + <0x6 0x02800000 0x0 0x800000>; + reg-names = "coproc", "disp-0", "disp-1", "disp-2", "disp-3"; + mboxes = <&dcpext1_mbox>; + mbox-names = "mbox"; + iommus = <&dcpext1_dart 23>; + /* guessed, as dcpext0 */ + apple,bw-scratch = <&pmgr_dcp 0 4 0x30000>; + status = "disabled"; + + dispext1_piodma: piodma { + iommus = <&dispext1_dart 16>; + }; + }; + + dcpext1_mbox: mailbox@602e08000 { + compatible = "apple,t6041-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x6 0x02e08000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + status = "disabled"; + }; + + /* ATC3: the HDMI converter's fixed DP PHY; no USB controller, no pipehandler. */ + atcphy3: phy@39aa90000 { + compatible = "apple,t6040-atcphy"; + reg = <0x3 0x9aa90000 0x0 0x4000>, + <0x3 0x9a800000 0x0 0x4000>, + <0x3 0x9b000000 0x0 0x30000>, + <0x3 0x9b200000 0x0 0x4000>, + <0x3 0x9b140000 0x0 0x4000>, + <0x3 0x9b0c0000 0x0 0x10000>, + <0x3 0x98000000 0x0 0x8000>, + <0x3 0x98020000 0x0 0x4000>, + <0x3 0x9b244000 0x0 0x4000>, + <0x3 0x9b250000 0x0 0x4000>; + reg-names = "bank0", "bank1", "core", "cfg", "cpu", "cpu-data", + "axi2af", "axi2af-lioa", "fabric", "common-cfg"; + #phy-cells = <1>; + power-domains = <&ps_atc3_common_dp>; + mboxes = <&atcphy3_mbox>; + mbox-names = "phy-cpu"; + nonposted-mmio; + status = "disabled"; + }; + + atcphy3_mbox: mailbox@39b148000 { + compatible = "apple,t6041-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x3 0x9b148000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + power-domains = <&ps_atc3_phymxwrap>; + status = "disabled"; + }; + + /* Display crossbars (ADT atcN-dpxbar, atc-dpxbar,t604x): no driver yet. */ + atcphy0_xbar: mux@38324c000 { + compatible = "apple,t6041-display-crossbar"; + reg = <0x3 0x8324c000 0x0 0x4000>; + #mux-control-cells = <1>; + status = "disabled"; + }; + + atcphy1_xbar: mux@38b24c000 { + compatible = "apple,t6041-display-crossbar"; + reg = <0x3 0x8b24c000 0x0 0x4000>; + #mux-control-cells = <1>; + status = "disabled"; + }; + + atcphy2_xbar: mux@39324c000 { + compatible = "apple,t6041-display-crossbar"; + reg = <0x3 0x9324c000 0x0 0x4000>; + #mux-control-cells = <1>; + status = "disabled"; + }; + + atcphy3_xbar: mux@39b24c000 { + compatible = "apple,t6041-display-crossbar"; + reg = <0x3 0x9b24c000 0x0 0x4000>; + #mux-control-cells = <1>; + status = "disabled"; + }; +}; + +/* + * PMGR domains from the ADT device table (+16 offset, +19 block; block 0 = + * pmgr_aux, block 3 = pmgr_mini; checked against the ATC0 nodes). Disabled + * so the pwrstate driver does not touch them yet: DISPEXT0/1 run the + * firmware iBoot loaded, and an always-on domain that happens to be off + * would be powered on by the driver. + */ +&pmgr_aux { + ps_dispext0_sys: power-controller@340 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x340 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dispext0_sys"; + status = "disabled"; + }; + + ps_dispext0_fe: power-controller@348 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x348 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dispext0_fe"; + power-domains = <&ps_dispext0_sys>; + status = "disabled"; + }; + + ps_dispext0_cpu: power-controller@350 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x350 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dispext0_cpu"; + power-domains = <&ps_dispext0_fe>; + apple,always-on; + status = "disabled"; + }; + + ps_atc3_common: power-controller@408 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x408 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc3_common"; + apple,always-on; + status = "disabled"; + }; + + ps_dispext1_sys: power-controller@490 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x490 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dispext1_sys"; + status = "disabled"; + }; + + ps_dispext1_fe: power-controller@498 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x498 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dispext1_fe"; + power-domains = <&ps_dispext1_sys>; + status = "disabled"; + }; + + ps_dispext1_cpu: power-controller@4a0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x4a0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dispext1_cpu"; + power-domains = <&ps_dispext1_fe>; + apple,always-on; + status = "disabled"; + }; +}; + +&pmgr_mini { + ps_atc3_common_dp: power-controller@b8 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0xb8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc3_common_dp"; + power-domains = <&ps_atc3_phymxwrap>; + status = "disabled"; + }; + + ps_atc3_usb_aon: power-controller@e0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0xe0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc3_usb_aon"; + apple,always-on; + status = "disabled"; + }; + + ps_atc3_phymxwrap: power-controller@168 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x168 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc3_phymxwrap"; + power-domains = <&ps_atc3_common>, <&ps_atc3_usb_aon>; + status = "disabled"; + }; +}; + +/* + * HDMI port (disabled with dcpext0): converter power SMC gP0f/gP1a (as on + * the M3 Pro J514s), HPD on AOP GPIO 0x24. The converter's reset (gP10), + * force-dpfu (AOP GPIO 0x2a) and its I2C side channel (i2c8 0x08) are left + * undescribed, as on the J514s. + */ +&dcpext0 { + apple,connector-type = "HDMI-A"; + hdmi-hpd-gpios = <&pinctrl_aop 36 GPIO_ACTIVE_HIGH>; + hdmi-pwren-gpios = <&smc_gpio 15 GPIO_ACTIVE_HIGH>; + dp2hdmi-pwren-gpios = <&smc_gpio 26 GPIO_ACTIVE_HIGH>; + phys = <&atcphy3 PHY_TYPE_DP>; + phy-names = "dp-phy"; + mux-controls = <&atcphy3_xbar 0>; + mux-control-names = "dp-xbar"; + mux-index = <0>; + apple,dptx-phy = <3>; +}; diff --git a/arch/arm64/boot/dts/apple/t6040-j616s.dts b/arch/arm64/boot/dts/apple/t6040-j616s.dts index c47e71ed1e258..aa17241c57dd2 100644 --- a/arch/arm64/boot/dts/apple/t6040-j616s.dts +++ b/arch/arm64/boot/dts/apple/t6040-j616s.dts @@ -4331,3 +4331,9 @@ }; #include "t6040-j616s-usb3.dtsi" + +/* External display chain, all disabled: the gate in drivers/soc/apple/t6041-display-gate.c enables it. */ +#include "t6040-j616s-extdisp.dtsi" +#include "t6040-j616s-extdisp-mem-loader.dtsi" +#include "t6040-j616s-extdisp-hdmi.dtsi" +#include "t6040-j616s-extdisp-usbc.dtsi" From d962c07a66b18281e4f135a96230187d4ff4c967 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franti=C5=A1ek=20Bal=C3=A1zsy?= Date: Mon, 26 Oct 2026 15:52:41 +0100 Subject: [PATCH 140/184] soc: apple: t6041-display-gate: power up the J616s external displays MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extend the T6041 display gate to the external displays. When both external display processors have their firmware memory (the memory-region nodes of the dcpext are available: the boot loader filled the placeholders), enable the chain after the PMP firmware is bound: the Type-C routes before any device exists (so the port controllers wait for dcpext1's mode switches) with the display subsystem held back, then the HDMI chain, the Type-C chain, and last both processors and the display subsystem together, so the display master is created once with every DART in its iommus. The port controllers are probed again once dcpext1 is bound, as the Type-C core treats a mode switch below a disabled node as absent at their first probe. If the PMP does not come within 20 s, or a step fails, the routes are withdrawn and only the internal panel stays. The processors are enabled only once the infrastructure devices have their drivers (the crossbar and PHY are modules), as a processor that finds one missing is given up for good. Without the firmware memory, with nomodeset, or on another machine, nothing changes. Tested on a J616s with m4-loader rev19-j616s-1 (which fills the memory from the ADT): HDMI and USB-C DisplayPort from boot, eDP unchanged. Signed-off-by: František Balázsy --- drivers/soc/apple/Kconfig | 6 +- drivers/soc/apple/t6041-display-gate.c | 454 +++++++++++++++++++++++++ 2 files changed, 458 insertions(+), 2 deletions(-) diff --git a/drivers/soc/apple/Kconfig b/drivers/soc/apple/Kconfig index 9033a75140946..cbd8a0f0d430a 100644 --- a/drivers/soc/apple/Kconfig +++ b/drivers/soc/apple/Kconfig @@ -269,8 +269,10 @@ config APPLE_T6041_DISPLAY_GATE boot loader has handed the running display coprocessor over. This option enables those nodes before devices are created, only when the boot loader has published a complete T6041 handoff for the - 26.6.2 display firmware. Without it, or with nomodeset, it does - nothing. + 26.6.2 display firmware. It also powers up the J616s external + display chain (HDMI, Type-C DisplayPort) once the boot loader has + given the external display processors their firmware memory. + Without these, or with nomodeset, it does nothing. Say 'y' if you want the native display on M4 Pro MacBook Pros. diff --git a/drivers/soc/apple/t6041-display-gate.c b/drivers/soc/apple/t6041-display-gate.c index aa088ddb957de..eaa711247399c 100644 --- a/drivers/soc/apple/t6041-display-gate.c +++ b/drivers/soc/apple/t6041-display-gate.c @@ -156,3 +156,457 @@ static int __init apple_t6041_display_gate(void) /* Runs before of_platform_default_populate_init() at arch_initcall_sync. */ arch_initcall(apple_t6041_display_gate); + +/* + * External displays (HDMI behind dcpext0, Type-C behind dcpext1). The + * device tree describes the whole chain disabled; it is enabled here once the + * boot loader has given each external display processor its firmware memory + * (the memory-region nodes of the dcpext are available, or they stay disabled + * placeholders). Before any device exists only the Type-C routes are enabled + * (the port controllers then wait for dcpext1's mode switches) and the + * display subsystem is held back. The power-up waits for the PMP firmware, + * whose acknowledgement the DISPEXT domains need: HDMI chain, Type-C chain, + * then both processors and the display subsystem together, so the display + * master is created once with every DART in its iommus. + */ +#include +#include +#include +#include +#include +#include +#include + +/* + * Two stages, each with its own display DART (a DART still disabled in the + * display subsystem's iommus would make of_iommu drop its whole fwspec): the + * HDMI chain, then the Type-C chain. Run at the same time the two processors + * crashed (dcpext0: axi_rd_err), as they never did one after the other. + * Parents first; the genpd links in the tree do the real ordering. + */ +struct gate_stage { + const char *dart; /* display DART of this stage */ + const char *const *nodes; + unsigned int nr; +}; + +static const char *const gate_hdmi_nodes[] = { + /* ATC3 power, PMP DISPEXT0 request, DISPEXT0, AOP pins, PHY, crossbar, DARTs, mailbox, dcpext0 */ + "/power-management@502800000/power-controller@408", + "/power-management@508300000/power-controller@e0", + "/power-management@508300000/power-controller@168", + "/power-management@508300000/power-controller@b8", + "/power-management@508300000/power-controller@148", + "/pmp-report@5003c0000/report@11", + "/power-management@502800000/power-controller@340", + "/power-management@502800000/power-controller@348", + "/pinctrl@510824000", + "/mailbox@39b148000", + "/phy@39aa90000", + "/mux@39b24c000", + "/iommu@3a2340000", + "/iommu@3a2300000", + "/mailbox@3a2e08000", + "/dcp@3a2e00000", +}; + +static const char *const gate_usbc_nodes[] = { + /* PMP DISPEXT1 request, FAB7_SOC, DISPEXT1, crossbars, DARTs, mailbox, routes, dcpext1 */ + "/pmp-report@5003c0000/report@12", + "/power-management@502800000/power-controller@488", + "/power-management@502800000/power-controller@490", + "/power-management@502800000/power-controller@498", + "/mux@38324c000", + "/mux@38b24c000", + "/mux@39324c000", + "/iommu@602340000", + "/iommu@602300000", + "/mailbox@602e08000", + "/dcp@602e00000/typec-routes/route@0", + "/dcp@602e00000/typec-routes/route@1", + "/dcp@602e00000/typec-routes/route@2", + "/dcp@602e00000", +}; + +static const struct gate_stage gate_stages[] = { + { "/iommu@3a2300000", gate_hdmi_nodes, ARRAY_SIZE(gate_hdmi_nodes) }, + { "/iommu@602300000", gate_usbc_nodes, ARRAY_SIZE(gate_usbc_nodes) }, +}; + +/* A display processor (a direct child of the root) and a Type-C display route. */ +static bool gate_is_dcp(const char *path) +{ + return !strncmp(path, "/dcp@", 5) && !strchr(path + 5, '/'); +} + +static bool gate_is_route(const char *path) +{ + return strstr(path, "/typec-routes/"); +} + +static int gate_display_add_dart(struct of_changeset *cs, struct device_node *display, + struct device_node *dart) +{ + struct property *old, *prop; + __be32 *value; + + old = of_find_property(display, "iommus", NULL); + if (!old || !dart->phandle || old->length % (2 * sizeof(*value))) + return -EINVAL; + prop = kzalloc(sizeof(*prop), GFP_KERNEL); + value = kmalloc(old->length + 2 * sizeof(*value), GFP_KERNEL); + if (!prop || !value) { + kfree(prop); + kfree(value); + return -ENOMEM; + } + memcpy(value, old->value, old->length); + value[old->length / sizeof(*value)] = cpu_to_be32(dart->phandle); + value[old->length / sizeof(*value) + 1] = cpu_to_be32(0); + prop->name = kstrdup("iommus", GFP_KERNEL); + prop->length = old->length + 2 * sizeof(*value); + prop->value = value; + if (!prop->name) { + kfree(value); + kfree(prop); + return -ENOMEM; + } + return of_changeset_update_property(cs, display, prop); +} + +/* + * The processor's firmware memory must be described: every memory-region of + * the dcpext node (the stage's last node) must be an available reserved-memory + * node. A loader that fills the regions from the ADT (m4-loader + * m4_dcpext_fixup) leaves disabled placeholders when it refuses, and an older + * loader leaves them all disabled; enabling the processor then would only + * make its driver fail. /chosen m4,dcpext is that loader's record (0 filled, + * 2 static memory), printed when the check fails. + */ +static bool gate_firmware_memory(const char *dcp) +{ + struct device_node *np, *region; + int i, n; + u32 record[2]; + + np = of_find_node_by_path(dcp); + if (!np) + return false; + n = of_count_phandle_with_args(np, "memory-region", NULL); + for (i = 0; i < n; i++) { + region = of_parse_phandle(np, "memory-region", i); + if (region && of_device_is_available(region) && + of_device_is_compatible(region, "apple,asc-mem")) { + of_node_put(region); + continue; + } + of_node_put(region); + n = -ENODEV; + break; + } + of_node_put(np); + if (n > 0) + return true; + np = of_find_node_by_path("/chosen"); + if (np && !of_property_read_u32_array(np, "m4,dcpext", record, 2)) + pr_err("%s: no firmware memory (loader record <%u %u>), nothing enabled\n", dcp, + record[0], record[1]); + else + pr_err("%s: no firmware memory, nothing enabled\n", dcp); + of_node_put(np); + return false; +} + +/* Every node of @st is there and still disabled, and its processor has firmware memory. */ +static bool gate_stage_ready(const struct gate_stage *st) +{ + struct device_node *np; + unsigned int i; + + if (!gate_firmware_memory(st->nodes[st->nr - 1])) + return false; + for (i = 0; i < st->nr; i++) { + np = of_find_node_by_path(st->nodes[i]); + if (!np) { + pr_err("%s: no such node, nothing enabled\n", st->nodes[i]); + return false; + } + if (of_device_is_available(np)) { + pr_err("%s: already enabled, nothing changed\n", st->nodes[i]); + of_node_put(np); + return false; + } + of_node_put(np); + } + return true; +} + +/* Enable @st's nodes and put its display DART into the display subsystem's iommus. */ +static int gate_stage_apply(const struct gate_stage *st, struct device_node *display) +{ + struct device_node *np; + struct of_changeset *cs; + unsigned int i; + int ret; + + cs = kzalloc(sizeof(*cs), GFP_KERNEL); + if (!cs) + return -ENOMEM; + of_changeset_init(cs); + + np = of_find_node_by_path(st->dart); + ret = np ? gate_display_add_dart(cs, display, np) : -ENODEV; + of_node_put(np); + for (i = 0; !ret && i < st->nr; i++) { + if (gate_is_dcp(st->nodes[i]) || gate_is_route(st->nodes[i])) + continue; + np = of_find_node_by_path(st->nodes[i]); + ret = np ? of_changeset_update_prop_string(cs, np, "status", "okay") : -ENODEV; + of_node_put(np); + } + if (!ret) + ret = of_changeset_apply(cs); + if (ret) { + of_changeset_destroy(cs); + kfree(cs); + } + /* Applied, it stays: the live tree refers to it. */ + return ret; +} + +#define GATE_PMP_WAIT_MS 20000 + +/* Set the status of the processors (externals) or the routes, and the display subsystem, in one changeset. */ +static int gate_set(struct device_node *display, bool dcps, const char *dcp_status, bool routes, + const char *route_status, const char *display_status) +{ + struct device_node *np; + struct of_changeset *cs; + unsigned int i, j; + int ret = 0; + + cs = kzalloc(sizeof(*cs), GFP_KERNEL); + if (!cs) + return -ENOMEM; + of_changeset_init(cs); + for (i = 0; !ret && i < ARRAY_SIZE(gate_stages); i++) + for (j = 0; !ret && j < gate_stages[i].nr; j++) { + const char *path = gate_stages[i].nodes[j]; + + if (!(dcps && gate_is_dcp(path)) && !(routes && gate_is_route(path))) + continue; + np = of_find_node_by_path(path); + ret = np ? of_changeset_update_prop_string(cs, np, "status", + gate_is_dcp(path) ? dcp_status : + route_status) : -ENODEV; + of_node_put(np); + } + if (!ret && display_status) + ret = of_changeset_update_prop_string(cs, display, "status", display_status); + if (!ret) + ret = of_changeset_apply(cs); + if (ret) { + of_changeset_destroy(cs); + kfree(cs); + } + return ret; +} + +static struct device_node *gate_display; +static struct device_node *gate_pmp; +static atomic_t gate_started = ATOMIC_INIT(0); + +/* Internal panel only: the routes go again, so the port controllers probe without mode switches. */ +static void gate_fallback(const char *why) +{ + int ret = gate_set(gate_display, false, NULL, true, "disabled", "okay"); + + pr_err("%s: external displays stay off, internal panel only (%d)\n", why, ret); +} + +/* + * The Type-C core treats a mode switch below a disabled node as absent, so the + * port controllers, which probe before dcpext1 is enabled, only took the USB + * muxes. Once dcpext1 is bound (its route mode switches are registered), let + * each controller wired to a route probe again, so that it takes them too. + */ +static void gate_reprobe_port_controllers(void) +{ + struct device *done[ARRAY_SIZE(gate_usbc_nodes)] = {}; + struct device_node *dcp, *np, *ep, *connector, *ctrl; + struct device *dev = NULL; + unsigned int i, n = 0, tries; + + dcp = of_find_node_by_path("/dcp@602e00000"); + for (tries = 0; dcp && tries < 150; tries++) { + dev = dcp->fwnode.dev; + if (dev && READ_ONCE(dev->driver)) + break; + msleep(200); + } + of_node_put(dcp); + if (!dev || !READ_ONCE(dev->driver)) { + pr_err("dcpext1 not bound, port controllers left as they are\n"); + return; + } + for (i = 0; i < ARRAY_SIZE(gate_usbc_nodes); i++) { + unsigned int k; + + if (!gate_is_route(gate_usbc_nodes[i])) + continue; + np = of_find_node_by_path(gate_usbc_nodes[i]); + ep = np ? of_graph_get_next_endpoint(np, NULL) : NULL; + connector = ep ? of_graph_get_remote_port_parent(ep) : NULL; + ctrl = connector ? of_get_parent(connector) : NULL; + dev = ctrl ? get_device(ctrl->fwnode.dev) : NULL; + of_node_put(ctrl); + of_node_put(connector); + of_node_put(ep); + of_node_put(np); + if (!dev) + continue; + for (k = 0; k < n && done[k] != dev; k++) + ; + if (k < n || !READ_ONCE(dev->driver)) { + put_device(dev); + continue; + } + done[n++] = dev; + if (device_reprobe(dev)) + dev_err(dev, "probe again failed\n"); + else + dev_info(dev, "probed again for the display routes\n"); + } + while (n) + put_device(done[--n]); +} + +/* + * A processor that finds its crossbar or DP PHY without a driver is given up + * for good (the deferred probe timeout is 0 here), and those drivers are + * modules that load only once the stage has created their devices. Wait until + * every such device of the enabled infrastructure has its driver. Returns the + * first path still without one, or NULL. + */ +static const char *gate_unbound_infra(void) +{ + unsigned int i, j; + + for (i = 0; i < ARRAY_SIZE(gate_stages); i++) + for (j = 0; j < gate_stages[i].nr; j++) { + const char *path = gate_stages[i].nodes[j]; + struct device_node *np; + struct device *dev; + + /* Power domains are the genpd core's to wait for; the modules are the point here. */ + if (gate_is_dcp(path) || gate_is_route(path) || + !strncmp(path, "/power-management@", 18)) + continue; + np = of_find_node_by_path(path); + dev = np ? READ_ONCE(np->fwnode.dev) : NULL; + of_node_put(np); + if (dev && !READ_ONCE(dev->driver)) + return path; + } + return NULL; +} + +#define GATE_DRIVER_WAIT_MS 10000 + +static void gate_wait_for_drivers(void) +{ + const char *path = NULL; + unsigned int tries; + + for (tries = 0; tries < GATE_DRIVER_WAIT_MS / 100; tries++) { + path = gate_unbound_infra(); + if (!path) + return; + msleep(100); + } + pr_warn("%s has no driver after " __stringify(GATE_DRIVER_WAIT_MS) " ms, enabling the processors anyway\n", + path); +} + +static void gate_power_up(struct work_struct *work) +{ + unsigned int i; + int ret = 0; + + for (i = 0; !ret && i < ARRAY_SIZE(gate_stages); i++) { + ret = gate_stage_apply(&gate_stages[i], gate_display); + if (!ret) + pr_info("stage %u enabled\n", i); + } + if (!ret) + gate_wait_for_drivers(); + /* Both processors and the display subsystem together: the master binds once, with all DCPs. */ + if (!ret) + ret = gate_set(gate_display, true, "okay", false, NULL, "okay"); + if (ret) { + gate_fallback("could not enable the external displays"); + return; + } + pr_info("processors and display subsystem enabled\n"); + gate_reprobe_port_controllers(); +} +static DECLARE_WORK(gate_power_up_work, gate_power_up); + +static void gate_timeout(struct work_struct *work) +{ + if (!atomic_xchg(&gate_started, 1)) + gate_fallback("no PMP within " __stringify(GATE_PMP_WAIT_MS) " ms"); +} +static DECLARE_DELAYED_WORK(gate_timeout_work, gate_timeout); + +/* The PMP's driver is bound: DISPEXT can be acknowledged now. */ +static int gate_pmp_bound(struct notifier_block *nb, unsigned long action, void *data) +{ + struct device *dev = data; + + if (action != BUS_NOTIFY_BOUND_DRIVER || dev->of_node != gate_pmp || atomic_xchg(&gate_started, 1)) + return NOTIFY_DONE; + cancel_delayed_work(&gate_timeout_work); + queue_work(system_long_wq, &gate_power_up_work); + return NOTIFY_OK; +} +static struct notifier_block gate_pmp_nb = { .notifier_call = gate_pmp_bound }; + +static int __init apple_t6041_extdisp_gate(void) +{ + unsigned int i; + int ret; + + if (!of_machine_is_compatible("apple,j616s")) + return 0; + if (IS_ENABLED(CONFIG_VIDEO) && video_firmware_drivers_only()) + return 0; + + /* Everything must be there and disabled, with firmware memory, before anything is changed. */ + for (i = 0; i < ARRAY_SIZE(gate_stages); i++) + if (!gate_stage_ready(&gate_stages[i])) + return 0; + gate_display = of_find_node_by_path("/display-subsystem"); + gate_pmp = of_find_node_by_path("/pmp@500700000"); + if (!gate_display || !gate_pmp || !of_device_is_available(gate_display) || + !of_device_is_available(gate_pmp)) { + pr_err("no display subsystem or PMP, nothing enabled\n"); + return 0; + } + + /* No device exists yet: routes on, display subsystem held back until the power-up. */ + ret = gate_set(gate_display, false, NULL, true, "okay", "disabled"); + if (ret) { + pr_err("could not prepare the tree: %d, nothing enabled\n", ret); + return 0; + } + ret = bus_register_notifier(&platform_bus_type, &gate_pmp_nb); + if (ret) { + gate_fallback("no bus notifier"); + return 0; + } + schedule_delayed_work(&gate_timeout_work, msecs_to_jiffies(GATE_PMP_WAIT_MS)); + pr_info("routes enabled, waiting for the PMP\n"); + return 0; +} +arch_initcall(apple_t6041_extdisp_gate); From d42eadabb9c4e4547768c427aae2e46a1a5a1443 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franti=C5=A1ek=20Bal=C3=A1zsy?= Date: Mon, 26 Oct 2026 17:22:40 +0100 Subject: [PATCH 141/184] drm/asahi: admit a third loaded T6040 25G83 image MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A J616s with 26.6.2 boot firmware (25G83, iBoot mBoot-18000.161.10) can load a third normalized T6040 TEXT identity: TEXT 0x60000, DATA 0x138000, the same entropy offsets, sha256 d725264d4831b95a5b8a375abf2fb5cdfbd6d7cbc7f1baa2cc8b37ee5d6e5e98. Without it the GPU probe stops with "GPU firmware image not admitted" and the device has no render node. Add the digest to the 26.6.2 T6040 image list next to the two existing identities and to the KUnit test that exercises every listed image through the lookup used by identify(). Signed-off-by: František Balázsy --- drivers/gpu/drm/asahi/fwid.rs | 7 ++++++- drivers/gpu/drm/asahi/hw/t6040.rs | 11 +++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/asahi/fwid.rs b/drivers/gpu/drm/asahi/fwid.rs index bb1fd3c36f623..be81eac5dfb74 100644 --- a/drivers/gpu/drm/asahi/fwid.rs +++ b/drivers/gpu/drm/asahi/fwid.rs @@ -213,7 +213,7 @@ mod tests { } #[test] - fn t6040_accepts_both_loaded_images() { + fn t6040_accepts_all_loaded_images() { let crate::g17::fw::variant::Firmware::Identified(builds) = crate::hw::t6040::CONFIG.firmware else { @@ -231,6 +231,11 @@ mod tests { 0x31, 0xc3, 0xb2, 0x8d, 0x05, 0x09, 0xa1, 0x7e, 0xb7, 0x61, 0x08, 0xb7, 0xc9, 0xa5, 0x2a, 0xcc, 0xd9, 0xac, ], + [ + 0xd7, 0x25, 0x26, 0x4d, 0x48, 0x31, 0xb9, 0x5a, 0x5b, 0x8a, 0x37, 0x5a, 0xbf, 0x2f, + 0xb5, 0xcd, 0xfb, 0xd6, 0xd7, 0xcb, 0xc7, 0xf1, 0xba, 0xa2, 0xcc, 0x8b, 0x37, 0xee, + 0x5d, 0x6e, 0x5e, 0x98, + ], ]; for (i, digest) in digests.into_iter().enumerate() { let found = measured(build.images, 0x138000, [0x59839, 0x5991c], digest).unwrap(); diff --git a/drivers/gpu/drm/asahi/hw/t6040.rs b/drivers/gpu/drm/asahi/hw/t6040.rs index fa3e51efacadc..11ed37a7c1ef8 100644 --- a/drivers/gpu/drm/asahi/hw/t6040.rs +++ b/drivers/gpu/drm/asahi/hw/t6040.rs @@ -85,6 +85,17 @@ pub(crate) const CONFIG: Config = Config { 0xc9, 0xa5, 0x2a, 0xcc, 0xd9, 0xac, ], }, + crate::fwid::FirmwareBuild { + name: "Apple GFX 25G83 for T6040 (normalized loaded image, second alternate J616s fill)", + text_size: 0x60000, + data_size: 0x138000, + entropy: [0x59839, 0x5991c], + text_sha256: [ + 0xd7, 0x25, 0x26, 0x4d, 0x48, 0x31, 0xb9, 0x5a, 0x5b, 0x8a, 0x37, 0x5a, 0xbf, + 0x2f, 0xb5, 0xcd, 0xfb, 0xd6, 0xd7, 0xcb, 0xc7, 0xf1, 0xba, 0xa2, 0xcc, 0x8b, + 0x37, 0xee, 0x5d, 0x6e, 0x5e, 0x98, + ], + }, ], variant: &fw::variant::V25G83, }]), From 579de5efa9a20ddeefe9a5cd5ad09f32b6d7f731 Mon Sep 17 00:00:00 2001 From: Ryan Murray <81541646+RyanTheTide@users.noreply.github.com> Date: Tue, 27 Oct 2026 19:53:42 +1100 Subject: [PATCH 142/184] drm/apple: carry external VRR through the J616s g5 desktop policy The g5 line already has the internal-panel adaptive-sync policy and failure fallback that overlap the last two commits of #451. Keep that panel path, admit VRR advertised by external sink modes, and program the desktop sync parameter when an external pipe enters or leaves VRR. Ported from the external-display intent of fbalazsy's #451 while preserving its preceding commits. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/gpu/drm/apple/iomfb.c | 12 +++++++++--- drivers/gpu/drm/apple/iomfb_template.c | 10 +++++++--- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index a354580f70dd6..1d583cac75100 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -204,9 +204,15 @@ static inline u64 dcpep_ack(enum dcp_context_id id) bool dcp_desktop_vrr_supported(struct apple_dcp *dcp) { - return dcp->panel.has_vrr && dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && - dcp_has_panel(dcp) && dcp->main_display && - !READ_ONCE(dcp->desktop->vrr_failed); + if (dcp->fw_compat != DCP_FIRMWARE_V_26_6_DESKTOP || + READ_ONCE(dcp->desktop->vrr_failed)) + return false; + + /* External processors advertise VRR on their sink modes, not a panel. */ + if (!dcp->main_display) + return !dcp_has_panel(dcp); + + return dcp_has_panel(dcp) && dcp->panel.has_vrr; } int dcp_desktop_vrr_check(struct apple_dcp *dcp, struct drm_crtc_state *state) diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index b1145f733c9bb..ee86743626bc5 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -3086,9 +3086,13 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, return -ENOMEM; cookie = &modeset->wait; modeset->actual = dcp->mode; - modeset->program_sync = dcp_has_panel(dcp) && dcp->main_display && - (dcp_desktop_vrr_supported(dcp) || dcp->vrr_enabled || - READ_ONCE(dcp->desktop->vrr_failed)); + modeset->program_sync = + (dcp_has_panel(dcp) && dcp->main_display && + (dcp_desktop_vrr_supported(dcp) || dcp->vrr_enabled || + READ_ONCE(dcp->desktop->vrr_failed))) || + (!dcp->main_display && + (mode->vrr || dcp->vrr_enabled || + READ_ONCE(dcp->desktop->vrr_failed))); modeset->vrr = modeset->program_sync && mode->vrr && crtc_state->vrr_enabled && dcp_desktop_vrr_supported(dcp); modeset->min_vrr = mode->min_vrr; From 90cd05465e28617293d193714e4f38fcfb31a25a Mon Sep 17 00:00:00 2001 From: Ryan Murray <81541646+RyanTheTide@users.noreply.github.com> Date: Tue, 27 Oct 2026 20:07:08 +1100 Subject: [PATCH 143/184] selftests: apple-dcp: keep the g5 desktop callback fixture once MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The g5 lifecycle fixture already extracts the real desktop 26.6 callback checker. The #451 fixture follow-up adds an assert stub for its older base, which makes three g5 host scenarios fail to compile with a duplicate symbol. Keep the extracted checker and remove only that redundant stub. The original #451 commits remain attributed to František Balázsy. This is a g5 integration fixup. Assisted-by: LLM Signed-off-by: Ryan Murray --- tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py b/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py index 518cf23473d61..f17525db99a6b 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py @@ -1883,9 +1883,6 @@ def dispatcher(root): static struct dcp_channel *dcp_get_channel(struct apple_dcp *d, enum dcp_context_id c) { (void)c; return &d->channel; } static int dcp_parse_tag(char *p) { (void)p; return case_tag; } -/* A desktop 26.6 callback is not under test here: no scenario selects that firmware. */ -static bool __attribute__((unused)) iomfb_check_callback_v26_6_desktop(int tag, u32 in_len, u32 out_len) -{ (void)tag; (void)in_len; (void)out_len; assert(!"desktop 26.6 callback in an H17P scenario"); return false; } static bool of_machine_is_compatible(const char *n) { (void)n; return machine; } static bool of_device_is_compatible(struct device_node *n, const char *s) { (void)s; return n->external; } From 48fe30da9842007954558d215edae084c90ee1ff Mon Sep 17 00:00:00 2001 From: Ryan Murray <81541646+RyanTheTide@users.noreply.github.com> Date: Tue, 27 Oct 2026 20:31:38 +1100 Subject: [PATCH 144/184] drm/apple: combine g5 exact desktop callbacks with the #451 bounds table MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The g5 line already validates exact record lengths for its desktop 26.6 callbacks. #451 adds a minimum-length table for the full handler set, so a straight replay defines the checker twice and fails the WERROR build. Keep the g5 exact checks as a helper, fill the four g5 handlers absent from the new table, then require both checks. The table now covers every registered desktop handler. Point the synthetic desktop fixtures at the exact helper; production dispatch uses the combined checker. The original #451 commits remain attributed to František Balázsy. This is a g5 integration fixup. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/gpu/drm/apple/iomfb_v26_6_desktop.c | 10 ++++++++-- .../testing/selftests/drivers/gpu/apple-dcp/desktop.py | 3 ++- .../selftests/drivers/gpu/apple-dcp/lifecycle.py | 5 +++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb_v26_6_desktop.c b/drivers/gpu/drm/apple/iomfb_v26_6_desktop.c index 1e5f431a88137..d6f649391ced3 100644 --- a/drivers/gpu/drm/apple/iomfb_v26_6_desktop.c +++ b/drivers/gpu/drm/apple/iomfb_v26_6_desktop.c @@ -132,7 +132,7 @@ static bool trampoline_panel_dictionary_v26_6(struct apple_dcp *dcp, int tag, return true; } -bool iomfb_check_callback_v26_6_desktop(int tag, u32 in_len, u32 out_len) +static bool iomfb_check_callback_exact_v26_6_desktop(int tag, u32 in_len, u32 out_len) { switch (tag) { case 201: @@ -311,6 +311,7 @@ static const struct dcp_cb_min_v26_6_desktop cb_min[IOMFB_MAX_CB] = { [134] = CB_MIN(0, sizeof(u32)), [201] = CB_MIN(sizeof(struct dcp_map_buf_req), sizeof(struct DCP_FW_NAME(dcp_map_buf_resp))), + [202] = CB_MIN(sizeof(struct dcp_unmap_buf_req_v26_6), 0), [206] = CB_MIN(0, sizeof(u8)), [207] = CB_MIN(0, sizeof(u8)), [208] = CB_MIN(0, 0), @@ -328,6 +329,9 @@ static const struct dcp_cb_min_v26_6_desktop cb_min[IOMFB_MAX_CB] = { [415] = CB_MIN(0, sizeof(u8)), [451] = CB_MIN(sizeof(struct dcp_allocate_buffer_req), sizeof(struct dcp_allocate_buffer_resp)), + [452] = CB_MIN(sizeof(struct dcp_map_physical_req), + sizeof(struct dcp_map_physical_resp)), + [454] = CB_MIN(sizeof(u32), sizeof(u32)), [552] = CB_MIN(0, sizeof(u8)), [561] = CB_MIN(0, sizeof(u8)), [563] = CB_MIN(0, sizeof(u8)), @@ -339,6 +343,7 @@ static const struct dcp_cb_min_v26_6_desktop cb_min[IOMFB_MAX_CB] = { [576] = CB_MIN(0, 0), [581] = CB_MIN(0, 0), [582] = CB_MIN(0, sizeof(u8)), + [588] = CB_MIN(0, 0), [590] = CB_MIN(sizeof(struct DCP_FW_NAME(dc_swap_complete_resp)), 0), [592] = CB_MIN(sizeof(dcp_swap_intent_t), 0), [593] = CB_MIN(sizeof(u32), 0), @@ -354,7 +359,8 @@ bool iomfb_check_callback_v26_6_desktop(int tag, u32 in_len, u32 out_len) { if (tag < 0 || tag >= IOMFB_MAX_CB || !cb_min[tag].valid) return false; - return in_len >= cb_min[tag].in_len && out_len >= cb_min[tag].out_len; + return in_len >= cb_min[tag].in_len && out_len >= cb_min[tag].out_len && + iomfb_check_callback_exact_v26_6_desktop(tag, in_len, out_len); } void DCP_FW_NAME(iomfb_start)(struct apple_dcp *dcp) diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py index 60b903f85d22f..0b00a6a81d33f 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py @@ -389,7 +389,8 @@ def panel_identity(root, output=None): dictionary += block((root/'drivers/gpu/drm/apple/parser.c').read_text(), 'int parse_panel_edid_data(') dictionary += block(backend, 'struct dcp_dict_passthrough_req_v26_6 {')+';\n' dictionary += block(backend, 'static bool trampoline_panel_dictionary_v26_6(') - dictionary += block(backend, 'bool iomfb_check_callback_v26_6_desktop(') + dictionary += block(backend, 'static bool iomfb_check_callback_exact_v26_6_desktop(') + dictionary += '\n#define iomfb_check_callback_v26_6_desktop iomfb_check_callback_exact_v26_6_desktop\n' bindings = '\n'.join(line for line in block(backend, 'static const iomfb_cb_handler cb_handlers[').splitlines() if '[413]' in line or '[552]' in line or '[561]' in line) unit = code + r''' diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py b/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py index f17525db99a6b..440b5202f670a 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py @@ -1936,5 +1936,6 @@ def dispatcher(root): """ desktop = (root / "drivers/gpu/drm/apple/iomfb_v26_6_desktop.c").read_text() - checker = block(desktop, 'bool iomfb_check_callback_v26_6_desktop(') - return prefix + checker + source[start:end] + suffix + checker = block(desktop, 'static bool iomfb_check_callback_exact_v26_6_desktop(') + alias = '\n#define iomfb_check_callback_v26_6_desktop iomfb_check_callback_exact_v26_6_desktop\n' + return prefix + checker + alias + source[start:end] + suffix From 900841552fa6e5f7637adad4a0c15bbc694f3036 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 23:30:09 +1100 Subject: [PATCH 145/184] dt-bindings: spi: apple: Add the T6041 SPI controller The M4 Pro (T6040/T6041) SPI controllers are register compatible with T8103, like the T6020 and T8140 ones. Assisted-by: LLM Signed-off-by: Ryan Murray (cherry picked from commit fb63f95afe46ad66c35cf75a968ff201cd5de008) --- Documentation/devicetree/bindings/spi/apple,spi.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/spi/apple,spi.yaml b/Documentation/devicetree/bindings/spi/apple,spi.yaml index 4b30265686fa1..be47c5f9ff075 100644 --- a/Documentation/devicetree/bindings/spi/apple,spi.yaml +++ b/Documentation/devicetree/bindings/spi/apple,spi.yaml @@ -18,6 +18,7 @@ properties: - items: - enum: - apple,t6020-spi + - apple,t6041-spi - apple,t8140-spi - const: apple,t8103-spi - items: From b1ba68156118760e01f13cf26a2db65814b9800f Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 23:30:09 +1100 Subject: [PATCH 146/184] dt-bindings: input: apple,mesa-fingerprint: Name the signed root key Sensors whose enclave session needs a signed root key (the J700 and J616s part, 0x3356) take it as per-device factory data, like the calibration. Let firmware-name carry it as an optional second entry so the board description names it. Assisted-by: LLM Signed-off-by: Ryan Murray (cherry picked from commit ceca3fd9960240011d2609198a864d3a549d42cc) --- .../bindings/input/apple,mesa-fingerprint.yaml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Documentation/devicetree/bindings/input/apple,mesa-fingerprint.yaml b/Documentation/devicetree/bindings/input/apple,mesa-fingerprint.yaml index 53b304bcec7ff..631ba47a5a3ca 100644 --- a/Documentation/devicetree/bindings/input/apple,mesa-fingerprint.yaml +++ b/Documentation/devicetree/bindings/input/apple,mesa-fingerprint.yaml @@ -57,11 +57,17 @@ properties: description: Rail selector words, in power-on order. firmware-name: - maxItems: 1 + minItems: 1 + items: + - description: + Calibration blob for this sensor. + - description: + Signed root-key container, for sensors whose enclave session needs + one. description: - Calibration blob for this sensor. It is per-device factory data written - at manufacture, so it is neither redistributable nor derivable from the - compatible; the board description has to name it. + Per-device factory data written at manufacture, so neither + redistributable nor derivable from the compatible; the board + description has to name it. spi-cpha: true From cd2cdc5e1ed01fab13c75960ba2dc1a437d038c6 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 23:30:10 +1100 Subject: [PATCH 147/184] soc: apple: sep: Take the sensor root key name from the device tree The signed root-key container is per-device data, but its name was fixed to J700's. Read it from the sensor node's second firmware-name entry and keep J700's name when the board gives only the calibration, so the J700 device tree and its installed file keep working unchanged. Name the file in the error when it cannot be loaded. Assisted-by: LLM Signed-off-by: Ryan Murray (cherry picked from commit 1bcccae53e5bfa901859a4e29f85cdbd9278a47c) --- drivers/soc/apple/sbio.rs | 9 +++++---- drivers/soc/apple/sensor.rs | 15 +++++++++++++-- drivers/soc/apple/sensor_shim.c | 24 ++++++++++++++---------- drivers/soc/apple/sep.rs | 3 +++ drivers/soc/apple/shim.h | 2 +- 5 files changed, 36 insertions(+), 17 deletions(-) diff --git a/drivers/soc/apple/sbio.rs b/drivers/soc/apple/sbio.rs index 88be672817b1c..5a81a370dda38 100644 --- a/drivers/soc/apple/sbio.rs +++ b/drivers/soc/apple/sbio.rs @@ -2183,14 +2183,15 @@ impl SepData { self.sensor_trace(c"SENSOR_NONCE"); if !self.sensor_key_loaded.load(Relaxed) { - let firmware = match kernel::firmware::Firmware::request( - c"apple/msrk-j700.bin", &self.dev, - ) { + // Per-device data, so the board names it; J700 predates that. + let name = sensor::root_key_firmware_name().unwrap_or(ROOT_KEY_FIRMWARE); + let firmware = match kernel::firmware::Firmware::request(name, &self.dev) { Ok(firmware) => firmware, Err(error) => { dev_err!( self.dev, - "sensor: signed root-key container unavailable: {:?}\n", + "sensor: signed root-key container {} unavailable: {:?}\n", + name, error ); return false; diff --git a/drivers/soc/apple/sensor.rs b/drivers/soc/apple/sensor.rs index 63a4ae28fbc6d..13bb470c89b3b 100644 --- a/drivers/soc/apple/sensor.rs +++ b/drivers/soc/apple/sensor.rs @@ -25,15 +25,26 @@ extern "C" { fn sep_sensor_irq_available() -> c_int; fn sep_sensor_irq_arm(); fn sep_sensor_irq_wait(timeout_ms: c_uint) -> c_int; - fn sep_sensor_firmware_name() -> *const c_char; + fn sep_sensor_firmware_name(index: c_uint) -> *const c_char; } /// The calibration blob's name from the board description, or `None` when the /// sensor node has no `firmware-name` property (the caller then uses a default). pub(crate) fn firmware_name() -> Option<&'static CStr> { + firmware_name_at(0) +} + +/// The signed root-key container's name from the board description: the +/// second `firmware-name` entry, or `None` when the board gives only the +/// calibration (the caller then uses a default). +pub(crate) fn root_key_firmware_name() -> Option<&'static CStr> { + firmware_name_at(1) +} + +fn firmware_name_at(index: c_uint) -> Option<&'static CStr> { // SAFETY: the shim returns NULL or a pointer to a NUL-terminated string // owned by the device property and valid for the device's lifetime. - let ptr = unsafe { sep_sensor_firmware_name() }; + let ptr = unsafe { sep_sensor_firmware_name(index) }; if ptr.is_null() { return None; } diff --git a/drivers/soc/apple/sensor_shim.c b/drivers/soc/apple/sensor_shim.c index 68405e0a74507..bed228b4d1ca3 100644 --- a/drivers/soc/apple/sensor_shim.c +++ b/drivers/soc/apple/sensor_shim.c @@ -568,19 +568,23 @@ static int sep_sensor_power_line_locked(void) } /* - * The per-device calibration blob name, as the board description gives it in - * the sensor node's "firmware-name" property. NULL when absent, so the caller - * can fall back to a default. + * A per-device blob name, as the board description gives it in the sensor + * node's "firmware-name" list: index 0 is the calibration, index 1 the signed + * root key. NULL when that entry is absent, so the caller can fall back to a + * default. */ -static const char *sep_sensor_firmware_name_locked(void) +static const char *sep_sensor_firmware_name_locked(unsigned int index) { - const char *name = NULL; + const char *names[2] = { NULL, NULL }; + int count; - if (!sep_spi) + if (!sep_spi || index >= ARRAY_SIZE(names)) return NULL; - if (device_property_read_string(&sep_spi->dev, "firmware-name", &name)) + count = device_property_read_string_array(&sep_spi->dev, "firmware-name", + names, ARRAY_SIZE(names)); + if (count <= (int)index) return NULL; - return name; + return names[index]; } /* Powers the sensor on/off, holding the hardware settling delay. */ @@ -871,14 +875,14 @@ int sep_sensor_power_line(void) return result; } -const char *sep_sensor_firmware_name(void) +const char *sep_sensor_firmware_name(unsigned int index) { struct sep_sensor_lifetime *sensor = sep_lock_sensor(); const char *result; if (!sensor) return NULL; - result = sep_sensor_firmware_name_locked(); + result = sep_sensor_firmware_name_locked(index); mutex_unlock(&sensor->lock); return result; } diff --git a/drivers/soc/apple/sep.rs b/drivers/soc/apple/sep.rs index df71496a564fe..7d9f4f2af1869 100644 --- a/drivers/soc/apple/sep.rs +++ b/drivers/soc/apple/sep.rs @@ -449,6 +449,9 @@ const PATCH_POLL_MS: u32 = 20; const PATCH_POLL_ATTEMPTS: u32 = 250; const CALIBRATION_FIRMWARE: &CStr = c"apple/mesa_calibration.bin"; +/// The signed root-key container on boards whose sensor node names only the +/// calibration (J700). +const ROOT_KEY_FIRMWARE: &CStr = c"apple/msrk-j700.bin"; const SBIO_PROBE_USER_ID: i32 = 1000; static_assert!(SBIO_PROBE_USER_ID >= crate::sks::SKS_DESIGNATE_USER_MIN); diff --git a/drivers/soc/apple/shim.h b/drivers/soc/apple/shim.h index 0425bf2cdaa6e..99d2661e2cb1c 100644 --- a/drivers/soc/apple/shim.h +++ b/drivers/soc/apple/shim.h @@ -132,7 +132,7 @@ int sep_sensor_cs_timing_mode(void); int sep_sensor_power_cycle(void); int sep_sensor_power_source(void); int sep_sensor_power(int on); -const char *sep_sensor_firmware_name(void); +const char *sep_sensor_firmware_name(unsigned int index); int sep_sensor_xfer(const void *tx, void *rx, size_t len); int sep_sensor_xfer_tx(const void *tx, size_t len); int sep_sensor_xfer2(const void *tx, size_t tx_len, void *rx, size_t rx_len); From 0dda06f92f06d5e405692cc6a7ad00823972c181 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 23:30:10 +1100 Subject: [PATCH 148/184] soc: apple: sep: Add the T6040 (J616s) platform profile The J616s ADT (26.6.2) describes a SEP that iBoot boots before the AP OS, an iop-sep,ascwrap-v6 whose DART window starts at the BIT(40) alias, and the Mesa sensor J700 carries (0x3356) on spi2 at 0x429108000, 8 MHz, chip select 0. So this is a warm-registration platform with T8140's authenticated sensor session, and the xART guard on from the first boot because macOS shares the enclave. Only J616s is matched; the other T6040 Macs' sensor wiring is unchecked. Assisted-by: LLM Signed-off-by: Ryan Murray (cherry picked from commit 718202c86b1ff9f11fcc9aa957f017243c2d18b1) --- drivers/soc/apple/profile.rs | 40 ++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/drivers/soc/apple/profile.rs b/drivers/soc/apple/profile.rs index 0ba0eb510122b..0e632c65da81f 100644 --- a/drivers/soc/apple/profile.rs +++ b/drivers/soc/apple/profile.rs @@ -9,7 +9,7 @@ //! here once. The driver reads addresses from the device tree, never scans for //! them, and never patches properties at runtime. //! -//! Five bring-up targets are modelled: +//! These bring-up targets are modelled: //! //! * `T8103` / J313 (MacBook Air, M1): the host boots the SEP with the boot //! endpoint handshake over a 0x30000 shared-memory window. @@ -24,6 +24,8 @@ //! a powered sensor, so the driver uses the warm registration path. //! * `T8122` / J613, J615 (MacBook Air, M3): the T8112 warm registration with //! the T8140 SEP DART aperture above 4 GiB. +//! * `T6040` / J616s (MacBook Pro 16", M4 Pro): iBoot boots sepOS, so the +//! driver uses the warm registration path; the sensor is the J700 part. // The boot handshake, identity source, sensor and DART fields are consumed by // the boot-endpoint, identity and sensor-transport paths. @@ -426,6 +428,28 @@ pub(crate) const T8122: PlatformProfile = PlatformProfile { ..T8112 }; +/// MacBook Pro 16" M4 Pro (J616s). The J616s ADT (26.6.2, 25G83) records a +/// pre-booted SEP (`sepfw-load-at-boot`), an `iop-sep,ascwrap-v6` SEP whose +/// DART aperture starts at the BIT(40) alias (`vm-base` 0x10000004000), and +/// the Mesa sensor J700 carries (`sensor-id` 0x3356) on spi2 at +/// `0x429108000`, 8 MHz, chip select 0. Its power is NUB GPIO 53 and its +/// data-ready line AP GPIO 116. `/defaults` carries `cpx-encryption-mode = 2`. +/// +/// A warm-registration platform like T8140, with T8140's authenticated +/// sensor session: the enclave serves this sensor only after the nonce and +/// the signed root key. Device-tree nodes remain authoritative for addresses. +pub(crate) const T6040: PlatformProfile = PlatformProfile { + name: "T6040/J616s", + sensor: SensorProfile { + controller_base: 0x4_2910_8000, + ..T8140.sensor + }, + // macOS shares this enclave and Linux has no records here yet: keep + // Linux to its own xART records from the first boot. + xart_guard: 1, + ..T8140 +}; + static_assert!(T8103.shmem_capacity == 0x30000); static_assert!(T6020.shmem_capacity == 0x40000); static_assert!(T8140.shmem_capacity == 0x40000); @@ -441,6 +465,8 @@ static_assert!(T8112.sensor.controller_base == T8103.sensor.controller_base); static_assert!(T8122.shmem_capacity == T8112.shmem_capacity); static_assert!(T8122.sensor.controller_base == 0x2a1108000); static_assert!(T8122.wide_dma_mask); +static_assert!(T6040.sensor.controller_base == 0x429108000); +static_assert!(T6040.sensor.expected_id == T8140.sensor.expected_id); /// Whether the machine root declares `compatible`. /// @@ -587,6 +613,11 @@ pub(crate) fn detect() -> Result<&'static PlatformProfile> { if machine_has(b"apple,t8122") && (machine_has(b"apple,j613") || machine_has(b"apple,j615")) { return Ok(&T8122); } + // Only the board whose ADT was read: J616s. The other T6040 Macs share + // the SoC but their sensor wiring is unchecked. + if machine_has(b"apple,t6040") && machine_has(b"apple,j616s") { + return Ok(&T6040); + } Err(ENODEV) } @@ -637,6 +668,11 @@ mod tests { assert!(T8122.wide_dma_mask); assert!(T8122.shmem_capacity == T8112.shmem_capacity); assert!(T8122.sensor.controller_base == 0x2_a110_8000); + assert!(T6040.nonce_session); + assert!(T6040.wide_dma_mask); + assert!(T6040.bootstrap == Bootstrap::WarmRegister); + assert!(T6040.identity == IdentitySource::Chosen); + assert!(T6040.xart_guard == 1); } } @@ -667,7 +703,7 @@ mod dart_window_tests { #[test] fn the_other_profiles_keep_the_bit40_window() { - for profile in [&T8103, &T6000, &T6020, &T8140, &T6020_SEPOS13, &T8122] { + for profile in [&T8103, &T6000, &T6020, &T8140, &T6020_SEPOS13, &T8122, &T6040] { assert_eq!(offset(profile.dart_window), 1 << 40); assert_eq!(range(profile.dart_window), (0, 1 << 32)); } From 20aeee2bd4da085e19ae3af5a90249ed54e144af Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 23:30:10 +1100 Subject: [PATCH 149/184] arm64: dts: apple: t6040: Add the SEP, its DART and mailbox, and spi2 From the J616s ADT: the SEP at 0x406600000 with its mailbox at +0x8000 (AIC 666-669), its DART at 0x404c80000 (AIC 676) with the window iBoot programmed (vm-base 0x10000004000, vm-size 0x3ff00000), and spi2 at 0x429108000 (AIC 1569) with the Mesa sensor child. Add the sep, spi_p and spi2 power states from the ADT pmgr devices. The SEP and its DART stay disabled; apple_sep enables them when it loads. Assisted-by: LLM Signed-off-by: Ryan Murray (cherry picked from commit ccbc6f976c8ee65c2947cd5f6eb9d85413fffd46) --- arch/arm64/boot/dts/apple/t6040-pmgr.dtsi | 28 ++++++++++ arch/arm64/boot/dts/apple/t6040.dtsi | 66 +++++++++++++++++++++++ 2 files changed, 94 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t6040-pmgr.dtsi b/arch/arm64/boot/dts/apple/t6040-pmgr.dtsi index 8c05fb4d4d5d3..bf357745d581d 100644 --- a/arch/arm64/boot/dts/apple/t6040-pmgr.dtsi +++ b/arch/arm64/boot/dts/apple/t6040-pmgr.dtsi @@ -140,6 +140,24 @@ power-domains = <&ps_sio>; }; + ps_spi_p: power-controller@198 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x198 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "spi_p"; + power-domains = <&ps_sio>; + }; + + ps_spi2: power-controller@1c8 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1c8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "spi2"; + power-domains = <&ps_spi_p>; + }; + ps_ans: power-controller@370 { compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; reg = <0x370 4>; @@ -178,6 +196,16 @@ power-domains = <&ps_ans>, <&ps_apcie_st1>; apple,always-on; }; + + ps_sep: power-controller@c00 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0xc00 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "sep"; + power-domains = <&ps_fab3_soc>; + apple,always-on; + }; }; /* diff --git a/arch/arm64/boot/dts/apple/t6040.dtsi b/arch/arm64/boot/dts/apple/t6040.dtsi index 01486e0f5ac65..c054f5accf531 100644 --- a/arch/arm64/boot/dts/apple/t6040.dtsi +++ b/arch/arm64/boot/dts/apple/t6040.dtsi @@ -702,6 +702,48 @@ status = "disabled"; }; + /* + * iBoot hands over a running SEP. Keep Linux off its DART at boot; + * apple_sep enables it when the driver binds. The SEP only accepts + * IOVAs in the window iBoot programmed (ADT vm-base 0x10000004000, + * vm-size 0x3ff00000), which carries the BIT(40) alias the DART + * translates. + */ + sep_dart: iommu@404c80000 { + compatible = "apple,t6041-dart", "apple,t8110-dart"; + reg = <0x4 0x04c80000 0x0 0x20000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + apple,dma-range = /bits/ 64 <0x10000004000 0x3ff00000>; + apple,dma-offset = /bits/ 64 <0x0>; + status = "disabled"; + }; + + sep: sep@406600000 { + compatible = "apple,sep"; + reg = <0x4 0x06600000 0x0 0x88000>; + mboxes = <&sep_mbox>; + mbox-names = "mbox"; + iommus = <&sep_dart 0>; + power-domains = <&ps_sep>; + status = "disabled"; + }; + + sep_mbox: mailbox@406608000 { + compatible = "apple,t6041-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x4 0x06608000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + status = "disabled"; + }; + ans_mbox: mailbox@409608000 { compatible = "apple,t6041-asc-mailbox", "apple,asc-mailbox-v4"; reg = <0x4 0x09608000 0x0 0x4000>; @@ -827,6 +869,30 @@ status = "disabled"; }; + /* + * Touch ID. iBoot leaves the pads muxed and the controller uses its + * native chip select, so there is no pinctrl here. + */ + spi2: spi@429108000 { + compatible = "apple,t6041-spi", "apple,t8103-spi"; + reg = <0x4 0x29108000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + clocks = <&clkref>; + power-domains = <&ps_spi2>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + + mesa: fingerprint@0 { + compatible = "apple,mesa-fingerprint"; + reg = <0>; + spi-max-frequency = <8000000>; + spi-cpol; + status = "disabled"; + }; + }; + nvme: nvme@44dcc0000 { compatible = "apple,t6041-nvme-ans2"; reg = <0x4 0x4dcc0000 0x0 0x10000>, From 1c85b4d66fdf6fbbe5f3a5fe300127f2b067caa3 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 26 Oct 2026 23:30:10 +1100 Subject: [PATCH 150/184] arm64: dts: apple: t6040-j616s: Enable Touch ID Give the boot loader the sep alias it reserves the SEP firmware through, enable the SEP mailbox and spi2, and describe the sensor: NUB GPIO 53 powers it, AP GPIO 116 is its data-ready line, and its sealed calibration and root key come from this Mac's own iBoot system container. Assisted-by: LLM Signed-off-by: Ryan Murray (cherry picked from commit a4b962a20add7dd09ff167f8cfc7cc4bd53ad2e9) --- arch/arm64/boot/dts/apple/t6040-j616s.dts | 29 +++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t6040-j616s.dts b/arch/arm64/boot/dts/apple/t6040-j616s.dts index aa17241c57dd2..6a85dda0804ac 100644 --- a/arch/arm64/boot/dts/apple/t6040-j616s.dts +++ b/arch/arm64/boot/dts/apple/t6040-j616s.dts @@ -29,6 +29,8 @@ disp0-piodma = &disp0_piodma; /* the boot loader finds the GPU node through this alias */ gpu = &gpu; + /* the boot loader reserves the SEP firmware through this alias */ + sep = &sep; }; chosen { @@ -1350,6 +1352,33 @@ status = "okay"; }; +/* apple_sep enables the SEP and its DART when it loads. */ +&sep_mbox { + status = "okay"; +}; + +/* + * Touch ID sensor (Mesa 0x3356). Its power is NUB GPIO 53 and its + * data-ready line is AP GPIO 116, as the J616s ADT describes them. + */ +&spi2 { + status = "okay"; +}; + +&mesa { + status = "okay"; + enable-gpios = <&pinctrl_nub 53 GPIO_ACTIVE_HIGH>; + interrupt-parent = <&pinctrl>; + interrupts = <116 IRQ_TYPE_EDGE_RISING>; + spi-cs-setup-delay-ns = <20>; + spi-cs-hold-delay-ns = <20>; + /* + * Per-device factory data from this Mac's own iBoot system container: + * the sealed calibration and the sealed root key. + */ + firmware-name = "apple/mesacal-j616s.bin", "apple/msrk-j616s.bin"; +}; + /* The Type-C retimers (ADT atcrt at 0x18/0x19/0x1a) sit on this bus; no driver yet. */ &i2c6 { status = "okay"; From 781883b8c9ef47a1dc7b42f6ce5f4bf1cef614d3 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Tue, 27 Oct 2026 01:08:00 -0400 Subject: [PATCH 151/184] drm/asahi: kill G17 contexts left with only executed render work A closed context whose failed compute command was retired on the firmware's fault record has a clean compute side, yet the render work it queued behind the failure ran without storing its stamp and stays linked in the context's work list. Such a pair is released only after a kill of its context, and that kill was only ever published through one of the context's compute queues, of which none is left to carry it. The context, its pairs and its memory therefore stayed until the GPU firmware stopped: 4 contexts after ten minutes of ray query stress beside heavy geometry. Publish the kill for such a context on its render pairs' behalf. A context qualifies once it has been closed for 250 ms, every pair of it is idle or has executed all of its commands, at least one has, and no compute queue still runs it, switches away from it or has published its kill. The kill names one of the context's own render queues that its scheduler page lists and is refused if the page names a queue of another context; the pairs it holds must have a single admission owner. The rules of a failed context's kill apply unchanged: the pairs are held for the kill, their memory is released only once the scheduler page reports it, the kill waits for a moment without other contexts' compute unless GPU memory is pressed, and the held pairs become killed pairs, reused last, once the context's scheduler release is consumed. The teardown entry keeps the kill's cookie, so only that exact cookie acknowledges it, and its doorbell is retried without duplicating the record. debugfs reports render-only kills, their acknowledgements, the contexts refused by first reason and the kills waiting. On Neo, ten minutes of two ray query stress loops beside heavy geometry had 53 faults, 53 kills through compute queues and 4 render-only kills, all acknowledged, with 0 GPU recoveries and nothing left pending afterwards (4 teardowns before). Ten minutes of the five-lane stress load had 48 faults, 8 kills through compute queues and 2 render-only kills, all acknowledged, with 0 GPU recoveries and nothing left pending. No render-only kill was refused in either run. The asahi_g17_teardown tracepoint records render-only kills as kind 7 and their acknowledgements with queue 0x100. Signed-off-by: Ryan Murray (cherry picked from commit 72771fbd90f04f17fef4a8bdd41cc29611c2e42f) Assisted-by: LLM --- .../gpu/drm/asahi/g17/queue/compute/exit.rs | 4 + drivers/gpu/drm/asahi/g17/runtime.rs | 2 + .../gpu/drm/asahi/g17/runtime/compute_exit.rs | 201 +++++++++++++++++- .../gpu/drm/asahi/g17/runtime/render/exit.rs | 8 + drivers/gpu/drm/asahi/g17/teardown.rs | 148 +++++++++++++ drivers/gpu/drm/asahi/g17/trace.rs | 5 +- 6 files changed, 360 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/asahi/g17/queue/compute/exit.rs b/drivers/gpu/drm/asahi/g17/queue/compute/exit.rs index ae812dd41fc12..719a74787ca70 100644 --- a/drivers/gpu/drm/asahi/g17/queue/compute/exit.rs +++ b/drivers/gpu/drm/asahi/g17/queue/compute/exit.rs @@ -111,6 +111,10 @@ impl Queue { CANCELLED_DELAY_NS } } + /// A kill of `context` was published through this queue. + pub(crate) fn exit_published(&self, context: &Arc) -> bool { + self.exit.is_some() && self.context().is_some_and(|bound| Arc::ptr_eq(bound, context)) + } pub(crate) fn exit_owner(&self, context: &Arc) -> Option { if !self .active diff --git a/drivers/gpu/drm/asahi/g17/runtime.rs b/drivers/gpu/drm/asahi/g17/runtime.rs index 1e721a51f726e..2ff40e4c8924b 100644 --- a/drivers/gpu/drm/asahi/g17/runtime.rs +++ b/drivers/gpu/drm/asahi/g17/runtime.rs @@ -2380,6 +2380,8 @@ impl Registry { let waits = || self.compute.iter().flatten().filter_map(|entry| entry.queue.as_deref()) .filter_map(|queue| queue.kill_waiting(now)); compute_exit::write_kill_waits(f, waits().count(), waits().max().unwrap_or(0))?; + compute_exit::write_render_kills(f, self.teardown.kill_waits(now).count(), + self.teardown.kill_waits(now).max().unwrap_or(0))?; writeln!(f, "compute_slots used={} total={} reusable={} building={}", self.compute.iter().flatten().count(), self.compute.len(), self.compute.iter().flatten().filter_map(|entry| entry.queue.as_deref()).filter(|queue| queue.reusable()).count(), diff --git a/drivers/gpu/drm/asahi/g17/runtime/compute_exit.rs b/drivers/gpu/drm/asahi/g17/runtime/compute_exit.rs index 791c1fccae6ae..c8e6de387d91c 100644 --- a/drivers/gpu/drm/asahi/g17/runtime/compute_exit.rs +++ b/drivers/gpu/drm/asahi/g17/runtime/compute_exit.rs @@ -31,6 +31,47 @@ static PRESSED_KILLS: AtomicU32 = AtomicU32::new(0); static PRESSED_KILL_NS: AtomicU64 = AtomicU64::new(0); const PRESSED_KILL_INTERVAL_NS: u64 = 250_000_000; +/// Render-only kills (see `Firmware::publish_render_kill`): published, acknowledged, and +/// contexts refused (each counted once, by its first reason) because the scheduler page names +/// a queue outside the context, because the held pairs have no single owner, or because +/// admission could not hold them. +static RENDER_KILLS: [AtomicU32; 5] = [const { AtomicU32::new(0) }; 5]; +const RENDER_KILL_PUBLISHED: usize = 0; +const RENDER_KILL_ACKNOWLEDGED: usize = 1; +const RENDER_KILL_SCOPE: usize = 2; +const RENDER_KILL_OWNER: usize = 3; +const RENDER_KILL_HOLD: usize = 4; +/// A render-only candidate is left this long after close, as a quarantined compute queue is, +/// so that ordinary retirement goes first. +const RENDER_KILL_DELAY_NS: u64 = 250_000_000; + +/// The render queue a render-only kill names: one of the context's own render queues that +/// its scheduler page lists, never the reserved queues 0 and 1. +fn render_kill_target(listed: u128, own: u128) -> Option { + let candidates = listed & own & !0b11; + (candidates != 0).then(|| candidates.trailing_zeros() as u8) +} + +/// The one admission owner of every pair a kill holds, if there is one. +fn hold_owner(owners: impl Iterator>) -> Option { + let mut owner = None; + for slot_owner in owners { + match (owner, slot_owner) { + (_, None) | (_, Some(0)) => return None, + (None, Some(next)) => owner = Some(next), + (Some(current), Some(next)) if current != next => return None, + _ => {} + } + } + owner +} + +fn refuse_render_kill(teardown: &mut crate::g17::teardown::Pending, index: usize, reason: usize) { + if teardown.refuse_render_kill(index) { + RENDER_KILLS[reason].fetch_add(1, Ordering::Relaxed); + } +} + /// GPU memory is within an eighth of the device limit. A failed context holds its memory /// until its kill is acknowledged, so kills waiting for other contexts' compute to pause can /// push the device to its limit, and then every client's allocations fail: on Neo, @@ -63,6 +104,15 @@ pub(in crate::g17) fn write_kill_waits(f: &mut kernel::fmt::Formatter<'_>, waiti writeln!(f, "failed_kills waiting={} longest_wait_ms={}", waiting, longest / 1_000_000) } +pub(in crate::g17) fn write_render_kills(f: &mut kernel::fmt::Formatter<'_>, waiting: usize, longest: u64) + -> kernel::fmt::Result +{ + let count = |index: usize| RENDER_KILLS[index].load(Ordering::Relaxed); + writeln!(f, "render_kills published={} acknowledged={} refused_scope={} refused_owner={} refused_hold={} waiting={} longest_wait_ms={}", + count(RENDER_KILL_PUBLISHED), count(RENDER_KILL_ACKNOWLEDGED), count(RENDER_KILL_SCOPE), + count(RENDER_KILL_OWNER), count(RENDER_KILL_HOLD), waiting, longest / 1_000_000) +} + impl super::Registry { /// ACK storage belongs to the already-retained transaction, so a full event /// batch cannot overflow a separate cache. No allocation or wait is needed. @@ -74,6 +124,10 @@ impl super::Registry { } } } + if self.teardown.acknowledge_render_kill(cookie) { + RENDER_KILLS[RENDER_KILL_ACKNOWLEDGED].fetch_add(1, Ordering::Relaxed); + crate::g17::trace::teardown(4, cookie, 0x100, 0); + } } } @@ -101,6 +155,8 @@ impl Firmware { let _ = queue.announce_exit(now, || self.primary.notify(MSG_CONTROL_NOTIFY)); } } + let primary = &mut self.primary; + self.queues.teardown.announce_render_kills(|| primary.notify(MSG_CONTROL_NOTIFY)); // Under memory pressure one kill skips the wait, at most one per interval. let last = PRESSED_KILL_NS.load(Ordering::Relaxed); let mut pressed = @@ -109,14 +165,24 @@ impl Firmware { let Some((context, age)) = self.queues.teardown.closed(index, now) else { continue; }; - // A failed context's executed pairs keep their pool leases until the kill is - // acknowledged (`Pair::firmware_drained`), so its kill cannot wait for them. - if !self.queues.render.teardown_idle(&context)? - || (!self.compute_failed(&context) && !self.queues.render.teardown_pools_released(&context)) - { + if !self.queues.render.teardown_idle(&context)? { + self.queues.teardown.clear_kill_wait(index); continue; } - if let Err(error) = self.publish_compute_exit(&context, age, now, &mut pressed) { + // A failed context's executed pairs keep their pool leases until the kill is + // acknowledged (`Pair::firmware_drained`), so its kill cannot wait for them. + let result = if self.compute_failed(&context) || self.queues.render.teardown_pools_released(&context) { + // Only a render-only kill's wait lives on the entry; a compute kill keeps its + // own on the queue. + self.queues.teardown.clear_kill_wait(index); + self.publish_compute_exit(&context, age, now, &mut pressed) + } else if self.render_kill_candidate(index, &context, age) { + self.publish_render_kill(index, &context, now, &mut pressed) + } else { + self.queues.teardown.clear_kill_wait(index); + Ok(()) + }; + if let Err(error) = result { dev_err!( self.primary.state.shared.dev.as_ref(), "Scheduler {} exit request failed: {:?}\n", @@ -243,6 +309,102 @@ impl Firmware { Ok(()) } + /// A closed context whose compute side is clean, with render pairs whose commands all ran + /// but were never stamped (`Pair::executed_for_kill`). Its failed compute command was + /// retired on the firmware's fault record, so no compute queue is left to carry a kill, + /// yet those pairs are released only after one: without this the context, its pairs and + /// its memory stay until the processors stop. + fn render_kill_candidate(&self, index: usize, context: &Arc, age: u64) -> bool { + age >= RENDER_KILL_DELAY_NS + && !self.queues.teardown.render_kill_published(index) + && self.queues.render.executed_slots(context) != 0 + && self.queues.compute.iter().flatten().filter_map(|entry| entry.queue.as_deref()).all(|queue| { + queue.idle_for_context(context) && !queue.switching_from(context) && !queue.exit_published(context) + }) + } + + /// Kill a render-only candidate (`render_kill_candidate`). The rules of a failed + /// context's kill apply: the pairs are held for the kill and their memory is released only + /// once the scheduler page reports it, the kill waits for a moment without other contexts' + /// compute unless GPU memory is pressed, and it names only queues of this context. + fn publish_render_kill(&mut self, index: usize, context: &Arc, now: u64, pressed: &mut bool) -> Result { + if self.render_control_backpressured() { + return Ok(()); + } + let beside = self.foreign_compute_running(context, now); + if beside && !*pressed { + let (waited, crossed) = self.queues.teardown.note_kill_wait(index, now, SLOW_KILL_WAIT_NS); + let last = SLOW_KILL_WARNED_NS.load(Ordering::Relaxed); + if crossed + && (last == 0 || now.saturating_sub(last) >= SLOW_KILL_WAIT_NS) + && SLOW_KILL_WARNED_NS + .compare_exchange(last, now.max(1), Ordering::Relaxed, Ordering::Relaxed) + .is_ok() + { + dev_warn!( + self.primary.state.shared.dev.as_ref(), + "Scheduler {} kill has waited {} ms for other contexts' compute to pause\n", + context.id(), + waited / 1_000_000 + ); + } + return Ok(()); + } + let (mut own, mut foreign, slots) = self.queues.render.exit_scope(context); + for entry in self.queues.compute.iter().flatten() { + if let Some(queue) = entry.queue.as_deref() { + let bit = 1u128 << queue.qid(); + if queue.context().is_some_and(|bound| Arc::ptr_eq(bound, context)) { + own |= bit; + } else { + foreign |= bit; + } + } + } + let state = exit::State::read(context)?; + let Some(qids) = render_kill_target(state.qids, own).and_then(|target| state.scope(target, own, foreign)) else { + refuse_render_kill(&mut self.queues.teardown, index, RENDER_KILL_SCOPE); + return Ok(()); + }; + let held = |slot: u8| slots & (1u64 << slot) != 0 && self.queues.render.qids_for_slot(slot) & qids != 0; + let range = 0..crate::hw::t8140::queues::RENDER_SLOTS as u8; + let hold = range.clone().filter(|slot| held(*slot)).fold(0u64, |hold, slot| hold | 1u64 << slot); + let owner = hold_owner(range.filter(|slot| held(*slot)).map(|slot| self.queues.render.slot_owner(slot))); + let Some(owner) = owner.filter(|_| hold != 0) else { + refuse_render_kill(&mut self.queues.teardown, index, RENDER_KILL_OWNER); + return Ok(()); + }; + if !self + .queues + .admission + .hold_render_slots_for_exit_kill(owner, hold, self.queues.render.executed_slots(context) & hold) + { + refuse_render_kill(&mut self.queues.teardown, index, RENDER_KILL_HOLD); + return Ok(()); + } + let cookie = exit::next_cookie()?; + let record = ControlRecord::ContextKill(ContextKill::new(context.scheduler_va(), cookie)); + if let Err(error) = Rings::publish_control(&self.init, &record) { + self.queues.admission.release_exit_kill_hold(hold); + return if error == EAGAIN { Ok(()) } else { Err(error) }; + } + // Publication is irrevocable. The held pairs stay held until this context's + // scheduler release is consumed (`release_killed_slots` at detach). + self.queues.teardown.record_render_kill(index, cookie, now)?; + record_kill_wait(self.queues.teardown.take_kill_wait(index, now)); + if beside { + *pressed = false; + PRESSED_KILLS.fetch_add(1, Ordering::Relaxed); + PRESSED_KILL_NS.store(now.max(1), Ordering::Relaxed); + } + RENDER_KILLS[RENDER_KILL_PUBLISHED].fetch_add(1, Ordering::Relaxed); + crate::g17::trace::teardown(7, cookie, u64::from(context.id()), hold | u64::from(beside)); + fence(Ordering::SeqCst); + let primary = &mut self.primary; + self.queues.teardown.announce_render_kills(|| primary.notify(MSG_CONTROL_NOTIFY)); + Ok(()) + } + /// Another context's compute command is in flight. On Neo, kills of failed contexts beside /// other clients' compute occasionally made the firmware recover the GPU (twice in ten /// minutes of a five-lane load with 67 faults), even with nothing freed before the kill's @@ -304,6 +466,9 @@ impl Firmware { { return true; } + if self.queues.teardown.render_kill_polling(now, exit::POLL_NS) { + return true; + } (0..self.queues.teardown.len()).any(|index| { self.queues .teardown @@ -321,3 +486,27 @@ impl Firmware { }) } } + +#[kernel::macros::kunit_tests(asahi_g17_render_kill_target)] +mod render_kill_target_tests { + use super::{hold_owner, render_kill_target}; + + #[test] + fn a_render_kill_holds_pairs_of_one_owner_only() { + assert_eq!(hold_owner([Some(5), Some(5)].into_iter()), Some(5)); + assert_eq!(hold_owner([Some(5), Some(6)].into_iter()), None); + assert_eq!(hold_owner([Some(5), None].into_iter()), None); + assert_eq!(hold_owner([Some(0)].into_iter()), None); + assert_eq!(hold_owner(core::iter::empty()), None); + } + + #[test] + fn a_render_kill_names_an_own_listed_queue_never_0_or_1() { + // Listed by the scheduler page but not the context's own: no target. + assert_eq!(render_kill_target(1 << 40, 1 << 41), None); + // The reserved queues are never named. + assert_eq!(render_kill_target(0b11, 0b11), None); + assert_eq!(render_kill_target(0b111 | 1 << 40, 0b111 | 1 << 40), Some(2)); + assert_eq!(render_kill_target(1 << 40 | 1 << 90, 1 << 90), Some(90)); + } +} diff --git a/drivers/gpu/drm/asahi/g17/runtime/render/exit.rs b/drivers/gpu/drm/asahi/g17/runtime/render/exit.rs index 25e1335c5ba13..6c58f728b3eb5 100644 --- a/drivers/gpu/drm/asahi/g17/runtime/render/exit.rs +++ b/drivers/gpu/drm/asahi/g17/runtime/render/exit.rs @@ -39,6 +39,14 @@ impl RenderState { slots } + /// The admission owner whose identities the pair in `slot` was built with. + pub(in crate::g17::runtime) fn slot_owner(&self, slot: u8) -> Option { + self.entries + .get(usize::from(slot)) + .and_then(Option::as_ref) + .map(|entry| entry.reservation.owner) + } + pub(in crate::g17::runtime) fn qids_for_slot(&self, slot: u8) -> u128 { self.entries .get(usize::from(slot)) diff --git a/drivers/gpu/drm/asahi/g17/teardown.rs b/drivers/gpu/drm/asahi/g17/teardown.rs index 53518a76e91a4..fa2a72a77f060 100644 --- a/drivers/gpu/drm/asahi/g17/teardown.rs +++ b/drivers/gpu/drm/asahi/g17/teardown.rs @@ -24,6 +24,45 @@ struct Entry { phase: Phase, armed: u64, announce_pending: bool, + render_kill: Option, + /// Since when, and whether reported, a render-only kill has waited for other contexts' + /// compute to pause. + kill_wait: Option<(u64, bool)>, + /// A render-only kill of this context was refused (counted once per context). + kill_refused: bool, +} + +/// The kill of a closed context whose compute side is clean, published so that its render +/// pairs' executed but unstamped commands can be released (see `Pair::firmware_drained`). +/// The scheduler page's killed state, not this record, proves the kill to the pairs. +#[derive(Clone, Copy)] +struct RenderKill { + cookie: u64, + published: u64, + announced: bool, + acknowledged: bool, +} + +impl RenderKill { + fn new(cookie: u64, now: u64) -> Self { + Self { + cookie, + published: now, + announced: false, + acknowledged: false, + } + } + /// Only this kill's exact cookie acknowledges it, once. + fn acknowledge(&mut self, cookie: u64) -> bool { + if cookie == 0 || cookie != self.cookie || self.acknowledged { + return false; + } + self.acknowledged = true; + true + } + fn awaiting(&self, now: u64, window: u64) -> bool { + !self.acknowledged && now.saturating_sub(self.published) < window + } } /// Publication and announcement errors retain the entry; no timeout frees @@ -104,6 +143,9 @@ impl Pending { phase, armed: now, announce_pending: false, + render_kill: None, + kill_wait: None, + kill_refused: false, }; self.entries.push(entry, GFP_KERNEL)?; Ok(()) @@ -233,6 +275,94 @@ impl Pending { Ok(None) } + /// A render-only kill of the closed context at `index` was published. + pub(crate) fn render_kill_published(&self, index: usize) -> bool { + self.entries.get(index).is_some_and(|entry| entry.render_kill.is_some()) + } + + /// Record a published render-only kill. Publication is irrevocable: the record stays + /// with the entry until the context's scheduler release is consumed. + pub(crate) fn record_render_kill(&mut self, index: usize, cookie: u64, now: u64) -> Result { + let entry = self.entries.get_mut(index).ok_or(EINVAL)?; + if entry.render_kill.is_some() { + return Err(EINVAL); + } + entry.render_kill = Some(RenderKill::new(cookie, now)); + Ok(()) + } + + /// Ring the doorbell for published render-only kills not yet announced. A failed + /// doorbell is retried on a later pass and never duplicates the record. + pub(crate) fn announce_render_kills(&mut self, mut notify: impl FnMut() -> Result) { + for entry in self.entries.iter_mut() { + if let Some(kill) = entry.render_kill.as_mut() { + if !kill.announced && !kill.acknowledged && notify().is_ok() { + kill.announced = true; + } + } + } + } + + /// Only the exact cookie of an unacknowledged render-only kill is accepted. + pub(crate) fn acknowledge_render_kill(&mut self, cookie: u64) -> bool { + self.entries + .iter_mut() + .filter_map(|entry| entry.render_kill.as_mut()) + .any(|kill| kill.acknowledge(cookie)) + } + + /// A render-only kill awaits its acknowledgement within the polling window. + pub(crate) fn render_kill_polling(&self, now: u64, window: u64) -> bool { + self.entries + .iter() + .any(|entry| entry.render_kill.is_some_and(|kill| kill.awaiting(now, window))) + } + + /// The render-only kill of the entry at `index` waits; how long, and whether this is the + /// first time it crossed `slow`. + pub(crate) fn note_kill_wait(&mut self, index: usize, now: u64, slow: u64) -> (u64, bool) { + let Some(entry) = self.entries.get_mut(index) else { + return (0, false); + }; + let (since, warned) = entry.kill_wait.get_or_insert((now, false)); + let waited = now.saturating_sub(*since); + let crossed = waited >= slow && !*warned; + *warned |= crossed; + (waited, crossed) + } + + /// The render-only kill was published: how long it waited, if it did. + pub(crate) fn take_kill_wait(&mut self, index: usize, now: u64) -> u64 { + self.entries + .get_mut(index) + .and_then(|entry| entry.kill_wait.take()) + .map_or(0, |(since, _)| now.saturating_sub(since)) + } + + /// A render-only kill of the context at `index` was refused: it no longer waits, and this + /// returns whether it is the first refusal for this context. + pub(crate) fn refuse_render_kill(&mut self, index: usize) -> bool { + let Some(entry) = self.entries.get_mut(index) else { + return false; + }; + entry.kill_wait = None; + !core::mem::replace(&mut entry.kill_refused, true) + } + + /// The context at `index` is not waiting for a render-only kill (any more). + pub(crate) fn clear_kill_wait(&mut self, index: usize) { + if let Some(entry) = self.entries.get_mut(index) { + entry.kill_wait = None; + } + } + + /// How long each waiting render-only kill has waited. + pub(crate) fn kill_waits(&self, now: u64) -> impl Iterator + '_ { + self.entries + .iter() + .filter_map(move |entry| entry.kill_wait.map(|(since, _)| now.saturating_sub(since))) + } + /// Poll for a bounded window at close and again at actual release publication. /// Failed announcements remain retryable on later service even after polling /// expires. Time passage never substitutes for a firmware-consumption witness. @@ -242,3 +372,21 @@ impl Pending { }) } } + +#[kernel::macros::kunit_tests(asahi_g17_render_kill)] +mod render_kill_tests { + use super::RenderKill; + + #[test] + fn only_the_exact_cookie_acknowledges_a_render_kill_once() { + let mut kill = RenderKill::new(7, 1_000); + assert!(kill.awaiting(1_000, 10)); + assert!(!kill.acknowledge(0)); + assert!(!kill.acknowledge(8)); + assert!(kill.awaiting(1_009, 10)); + assert!(!kill.awaiting(1_010, 10)); + assert!(kill.acknowledge(7)); + assert!(!kill.acknowledge(7)); + assert!(!kill.awaiting(1_001, 10)); + } +} diff --git a/drivers/gpu/drm/asahi/g17/trace.rs b/drivers/gpu/drm/asahi/g17/trace.rs index 72197f0eac0b7..78d88db4718c6 100644 --- a/drivers/gpu/drm/asahi/g17/trace.rs +++ b/drivers/gpu/drm/asahi/g17/trace.rs @@ -77,8 +77,9 @@ pub(super) fn message(kind: u8, value: u64) { /// Failed-context teardown: 1 compute fault (qid, stamp), 2 unstamped render retire (slot, /// ordinal), 3 kill published (cookie, context, held slots | 1 if it skipped the wait for other -/// contexts' compute under memory pressure; slot 0 is never held), 4 kill acknowledged (cookie, qid), -/// 5 detach (context, slots, linked), 6 render slot to a new owner (slot, old, new | killed << 63). +/// contexts' compute under memory pressure; slot 0 is never held), 4 kill acknowledged (cookie, +/// qid, or 0x100 for a render-only kill), 5 detach (context, slots, linked), 6 render slot to a +/// new owner (slot, old, new | killed << 63), 7 render-only kill published (as 3). pub(crate) fn teardown(kind: u8, a: u64, b: u64, c: u64) { // SAFETY: Plain values only. unsafe { asahi_g17_trace_teardown(kind, a, b, c) }; From a6a9f961ec9797edba701eb0bfa789dc2addacd5 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Tue, 27 Oct 2026 02:19:20 -0400 Subject: [PATCH 152/184] drm/asahi: take a G17 render-only kill's cookie before its admission hold A render-only kill took its admission hold on the context's render pairs and only then its cookie. If the cookie counter was exhausted, the EOVERFLOW returned with the hold still installed, so those pairs stayed held for a kill that was never published. Take the cookie first, as a failed context's kill already does. Signed-off-by: Ryan Murray (cherry picked from commit d6d0c93e679fdb31a290b6d3ffa1fbd334411836) Assisted-by: LLM --- drivers/gpu/drm/asahi/g17/runtime/compute_exit.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/asahi/g17/runtime/compute_exit.rs b/drivers/gpu/drm/asahi/g17/runtime/compute_exit.rs index c8e6de387d91c..cd7f2999d0312 100644 --- a/drivers/gpu/drm/asahi/g17/runtime/compute_exit.rs +++ b/drivers/gpu/drm/asahi/g17/runtime/compute_exit.rs @@ -374,6 +374,9 @@ impl Firmware { refuse_render_kill(&mut self.queues.teardown, index, RENDER_KILL_OWNER); return Ok(()); }; + // As for a failed context's kill: the cookie first, so that running out of cookies + // leaves no admission hold behind. + let cookie = exit::next_cookie()?; if !self .queues .admission @@ -382,7 +385,6 @@ impl Firmware { refuse_render_kill(&mut self.queues.teardown, index, RENDER_KILL_HOLD); return Ok(()); } - let cookie = exit::next_cookie()?; let record = ControlRecord::ContextKill(ContextKill::new(context.scheduler_va(), cookie)); if let Err(error) = Rings::publish_control(&self.init, &record) { self.queues.admission.release_exit_kill_hold(hold); From 8a97e866ef16506d01b978a42f7158cef05bc23f Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Tue, 27 Oct 2026 03:27:37 -0400 Subject: [PATCH 153/184] drm/asahi: kill a G17 context at most once A closed context qualifies for a render-only kill while one of its pairs holds executed but unstamped commands and no compute queue still runs it, switches away from it or killed it. A context killed through its compute queue normally leaves that state at once, since the acknowledged kill releases the pair in the same service pass, but only that timing kept the render-only path from killing it again: the compute queue's own record of the kill ends when the queue is rebound. A second kill of a killed context is harmful. In a diagnostic run that held the pair's release back for 100 ms after the kill, 31 contexts were killed twice, 0.1 to 2.5 ms apart, and twice the firmware recovered the GPU 0.3 ms after acknowledging the second kill, on a page fault in the killed pair's own tiling queue. The 35 contexts killed once in that run caused no recovery. Record on the context that a kill was published, by either path, and never publish a render-only kill for a context that has one. Signed-off-by: Ryan Murray (cherry picked from commit 7a9ebf019036a7fae177e3cc626625e903f4332d) Assisted-by: LLM --- drivers/gpu/drm/asahi/g17/context.rs | 14 ++++++++ .../gpu/drm/asahi/g17/runtime/compute_exit.rs | 35 +++++++++++++++---- 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/asahi/g17/context.rs b/drivers/gpu/drm/asahi/g17/context.rs index ba74140d4f243..0b3bd97fb42fe 100644 --- a/drivers/gpu/drm/asahi/g17/context.rs +++ b/drivers/gpu/drm/asahi/g17/context.rs @@ -114,6 +114,9 @@ pub(crate) struct Context { scheduler: Arc, scheduler_generation: u32, published: AtomicBool, + /// A kill of this context was published, through a compute queue or on its render pairs' + /// behalf. A context is killed at most once. + kill_published: AtomicBool, /// Advisory only: publication still checks the physical binding under the /// device mutex. False skips an inline attempt known to need admission. compute_binding_hint: AtomicBool, @@ -197,6 +200,7 @@ impl Context { scheduler, scheduler_generation, published: AtomicBool::new(false), + kill_published: AtomicBool::new(false), compute_binding_hint: AtomicBool::new(false), render_seen: AtomicBool::new(false), qos, @@ -316,6 +320,16 @@ impl Context { Ok(head) } + /// Record that a kill of this context was published (see `kill_published`). + pub(crate) fn note_kill_published(&self) { + self.kill_published.store(true, Ordering::Release); + } + + /// A kill of this context was published. + pub(crate) fn kill_published(&self) -> bool { + self.kill_published.load(Ordering::Acquire) + } + pub(crate) fn is_published(&self) -> bool { self.published.load(Ordering::Acquire) } diff --git a/drivers/gpu/drm/asahi/g17/runtime/compute_exit.rs b/drivers/gpu/drm/asahi/g17/runtime/compute_exit.rs index cd7f2999d0312..41960369b04a3 100644 --- a/drivers/gpu/drm/asahi/g17/runtime/compute_exit.rs +++ b/drivers/gpu/drm/asahi/g17/runtime/compute_exit.rs @@ -52,6 +52,16 @@ fn render_kill_target(listed: u128, own: u128) -> Option { (candidates != 0).then(|| candidates.trailing_zeros() as u8) } +/// A closed context may be killed on its render pairs' behalf once it has been closed for +/// [`RENDER_KILL_DELAY_NS`], it was never killed, a pair of it holds executed but unstamped +/// commands and no compute queue still runs it, switches away from it or killed it. A second kill +/// of a killed context made the firmware fault on the killed pair's own tiling queue within a +/// millisecond of acknowledging it: with the pair's release held back, a context killed through +/// its compute queue still looked like a render-only candidate. +fn render_kill_eligible(age: u64, killed: bool, executed: bool, compute_clear: bool) -> bool { + age >= RENDER_KILL_DELAY_NS && !killed && executed && compute_clear +} + /// The one admission owner of every pair a kill holds, if there is one. fn hold_owner(owners: impl Iterator>) -> Option { let mut owner = None; @@ -296,6 +306,7 @@ impl Firmware { PRESSED_KILL_NS.store(now.max(1), Ordering::Relaxed); } queue.record_exit(cookie, now); + context.note_kill_published(); crate::g17::trace::teardown(3, cookie, u64::from(context.id()), hold | u64::from(beside)); fence(Ordering::SeqCst); if let Err(error) = queue.announce_exit(now, || self.primary.notify(MSG_CONTROL_NOTIFY)) { @@ -315,12 +326,14 @@ impl Firmware { /// yet those pairs are released only after one: without this the context, its pairs and /// its memory stay until the processors stop. fn render_kill_candidate(&self, index: usize, context: &Arc, age: u64) -> bool { - age >= RENDER_KILL_DELAY_NS - && !self.queues.teardown.render_kill_published(index) - && self.queues.render.executed_slots(context) != 0 - && self.queues.compute.iter().flatten().filter_map(|entry| entry.queue.as_deref()).all(|queue| { + render_kill_eligible( + age, + self.queues.teardown.render_kill_published(index) || context.kill_published(), + self.queues.render.executed_slots(context) != 0, + self.queues.compute.iter().flatten().filter_map(|entry| entry.queue.as_deref()).all(|queue| { queue.idle_for_context(context) && !queue.switching_from(context) && !queue.exit_published(context) - }) + }), + ) } /// Kill a render-only candidate (`render_kill_candidate`). The rules of a failed @@ -393,6 +406,7 @@ impl Firmware { // Publication is irrevocable. The held pairs stay held until this context's // scheduler release is consumed (`release_killed_slots` at detach). self.queues.teardown.record_render_kill(index, cookie, now)?; + context.note_kill_published(); record_kill_wait(self.queues.teardown.take_kill_wait(index, now)); if beside { *pressed = false; @@ -491,7 +505,16 @@ impl Firmware { #[kernel::macros::kunit_tests(asahi_g17_render_kill_target)] mod render_kill_target_tests { - use super::{hold_owner, render_kill_target}; + use super::{hold_owner, render_kill_eligible, render_kill_target, RENDER_KILL_DELAY_NS}; + + #[test] + fn a_killed_context_is_never_killed_again() { + assert!(render_kill_eligible(RENDER_KILL_DELAY_NS, false, true, true)); + assert!(!render_kill_eligible(RENDER_KILL_DELAY_NS, true, true, true)); + assert!(!render_kill_eligible(RENDER_KILL_DELAY_NS - 1, false, true, true)); + assert!(!render_kill_eligible(RENDER_KILL_DELAY_NS, false, false, true)); + assert!(!render_kill_eligible(RENDER_KILL_DELAY_NS, false, true, false)); + } #[test] fn a_render_kill_holds_pairs_of_one_owner_only() { From 33e0e46b60829208e3f3d0752fb8025162899920 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Tue, 27 Oct 2026 18:06:29 +1100 Subject: [PATCH 154/184] hwmon: macsmc: Unlock fan control on firmware that keeps fans in system mode On the M4 Pro MacBook Pro (J616s) both fans start in mode 3, the firmware's own fan policy, and the SMC refuses the manual mode write of the fan_control path with an error, so fan control from Linux did nothing. Public reports for M4 describe the same refusal and the Ftst key that releases the fans to the OS. When userspace first asks for a target on a fan whose mode write is refused while it reads mode 3, set Ftst, wait for the fan to leave system mode and ask again. When the last manual fan goes back to auto, clear Ftst and wait for every fan to return to mode 3. Firmware that takes the mode write directly keeps the mode 0 restore; firmware that refuses it without an Ftst key gets the error as before. Hand every manual fan back on unbind or remove, at shutdown and before suspend, and whenever a write in the sequence fails. Targets are now clamped to the fan's minimum and maximum instead of refused, so a target can never go below the minimum. Everything stays behind the existing fan_control parameter; without it nothing is written. The new KUnit suite models locked and legacy firmware. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/hwmon/Kconfig | 7 +- drivers/hwmon/macsmc-hwmon-test.c | 262 +++++++++++++++++++++++++++++- drivers/hwmon/macsmc-hwmon.c | 261 +++++++++++++++++++++++++---- 3 files changed, 497 insertions(+), 33 deletions(-) diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 54eb5745105ff..9b6b0593310da 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1252,10 +1252,13 @@ config SENSORS_MACSMC_HWMON_KUNIT_TEST depends on SENSORS_MACSMC_HWMON && KUNIT=y default KUNIT_ALL_TESTS help - Unit tests for the channel numbering of the Apple SMC hwmon driver. + Unit tests for the channel numbering and the manual fan control of + the Apple SMC hwmon driver. Thermal zones refer to temperature channels by index, so a sensor that cannot be set up must keep its channel; the tests check that. - No SMC is needed. Only useful for kernel developers. + No SMC is needed: the fan tests model its fan keys and need + MFD_MACSMC_KUNIT_TEST for its read and write stubs. Only useful for + kernel developers. config SENSORS_MAX1111 tristate "Maxim MAX1111 Serial 8-bit ADC chip and compatibles" diff --git a/drivers/hwmon/macsmc-hwmon-test.c b/drivers/hwmon/macsmc-hwmon-test.c index e7239986ce3c9..596e904a6f3e8 100644 --- a/drivers/hwmon/macsmc-hwmon-test.c +++ b/drivers/hwmon/macsmc-hwmon-test.c @@ -1,11 +1,12 @@ // SPDX-License-Identifier: GPL-2.0-only OR MIT /* * KUnit tests for the Apple SMC hwmon driver: channel numbering of the - * temperature sensors, which thermal zones depend on. + * temperature sensors, which thermal zones depend on, and manual fan control. * * Included by macsmc-hwmon.c. */ +#include #include /* What macsmc_hwmon_create_sensor() leaves behind for one node. */ @@ -84,4 +85,261 @@ static struct kunit_suite macsmc_hwmon_test_suite = { .test_cases = macsmc_hwmon_test_cases, }; -kunit_test_suite(macsmc_hwmon_test_suite); +/* + * Manual fan control against a model of the SMC fan keys. In "locked" firmware + * the fans start in system mode (3) and refuse manual mode until Ftst is 1; + * Ftst back to 0 returns them to system mode. "Legacy" firmware takes the mode + * write directly. The model needs the SMC read/write stubs of + * CONFIG_MFD_MACSMC_KUNIT_TEST. + */ +#define FAN_TEST_F1350 0x44a8c000 /* 1350.0f */ +#define FAN_TEST_F2500 0x451c4000 /* 2500.0f */ +#define FAN_TEST_F5777 0x45b48800 /* 5777.0f */ + +struct macsmc_fan_test { + struct apple_smc smc; + struct kunit *test; + struct macsmc_hwmon hwmon; + struct macsmc_hwmon_fan fans[2]; + bool legacy; + u8 mode[2]; + u32 target[2]; + u8 ftst; + int ftst_writes; + int refused; + smc_key fail_key; +}; + +static int fan_test_index(smc_key key) +{ + return ((key >> 16) & 0xff) - '0'; +} + +static int fan_test_read(struct apple_smc *smc, smc_key key, void *buf, size_t size) +{ + struct macsmc_fan_test *t = container_of(smc, struct macsmc_fan_test, smc); + u32 value; + int i = fan_test_index(key); + + switch (key & 0xff00ffff) { + case __SMC_KEY('F', 0, 'M', 'd'): + value = t->mode[i]; + break; + case __SMC_KEY('F', 0, 'M', 'n'): + value = FAN_TEST_F1350; + break; + case __SMC_KEY('F', 0, 'M', 'x'): + value = FAN_TEST_F5777; + break; + case __SMC_KEY('F', 0, 'T', 'g'): + value = t->target[i]; + break; + default: + KUNIT_FAIL(t->test, "unexpected read of %08x", key); + return -EINVAL; + } + memcpy(buf, &value, size); + return size; +} + +static int fan_test_write(struct apple_smc *smc, smc_key key, const void *buf, size_t size) +{ + struct macsmc_fan_test *t = container_of(smc, struct macsmc_fan_test, smc); + u32 value = 0; + int i = fan_test_index(key); + + memcpy(&value, buf, min(size, sizeof(value))); + if (key == t->fail_key) + return -EIO; + + if (key == SMC_KEY(Ftst)) { + t->ftst_writes++; + t->ftst = value; + for (i = 0; i < ARRAY_SIZE(t->mode); i++) { + if (value && t->mode[i] == MACSMC_FAN_MODE_SYSTEM) + t->mode[i] = MACSMC_FAN_MODE_AUTO; + else if (!value) + t->mode[i] = MACSMC_FAN_MODE_SYSTEM; + } + return size; + } + + switch (key & 0xff00ffff) { + case __SMC_KEY('F', 0, 'M', 'd'): + if (!t->legacy && t->mode[i] == MACSMC_FAN_MODE_SYSTEM) { + t->refused++; + return -EIO; + } + t->mode[i] = value; + return size; + case __SMC_KEY('F', 0, 'T', 'g'): + t->target[i] = value; + return size; + } + KUNIT_FAIL(t->test, "unexpected write of %08x", key); + return -EINVAL; +} + +static void fan_test_key(struct macsmc_hwmon_sensor *sensor, const char *key, u32 type) +{ + sensor->macsmc_key = _SMC_KEY(key); + sensor->info.type_code = type; + sensor->info.flags = APPLE_SMC_READABLE | APPLE_SMC_WRITABLE; +} + +static int macsmc_fan_test_init(struct kunit *test) +{ + static const char *const keys[2][5] = { + { "F0Ac", "F0Mn", "F0Mx", "F0Tg", "F0Md" }, + { "F1Ac", "F1Mn", "F1Mx", "F1Tg", "F1Md" }, + }; + const u32 flt = __SMC_KEY('f', 'l', 't', ' '), ui8 = __SMC_KEY('u', 'i', '8', ' '); + struct macsmc_fan_test *t; + int i; + + if (!IS_ENABLED(CONFIG_MFD_MACSMC_KUNIT_TEST)) + kunit_skip(test, "needs CONFIG_MFD_MACSMC_KUNIT_TEST for the SMC stubs"); + + t = kunit_kzalloc(test, sizeof(*t), GFP_KERNEL); + if (!t) + return -ENOMEM; + t->test = test; + for (i = 0; i < 2; i++) { + fan_test_key(&t->fans[i].now, keys[i][0], flt); + fan_test_key(&t->fans[i].min, keys[i][1], flt); + fan_test_key(&t->fans[i].max, keys[i][2], flt); + fan_test_key(&t->fans[i].set, keys[i][3], flt); + fan_test_key(&t->fans[i].mode, keys[i][4], ui8); + t->mode[i] = MACSMC_FAN_MODE_SYSTEM; + } + t->hwmon.smc = &t->smc; + t->hwmon.fan.fans = t->fans; + t->hwmon.fan.count = 2; + t->hwmon.fan.unlock_key = SMC_KEY(Ftst); + mutex_init(&t->hwmon.fan.lock); + test->priv = t; + fan_control = true; + kunit_activate_static_stub(test, apple_smc_read, fan_test_read); + kunit_activate_static_stub(test, apple_smc_write, fan_test_write); + return 0; +} + +static void macsmc_fan_test_exit(struct kunit *test) +{ + fan_control = false; +} + +static void fan_test_released(struct kunit *test, struct macsmc_fan_test *t) +{ + KUNIT_EXPECT_EQ(test, t->ftst, 0); + KUNIT_EXPECT_EQ(test, t->mode[0], MACSMC_FAN_MODE_SYSTEM); + KUNIT_EXPECT_EQ(test, t->mode[1], MACSMC_FAN_MODE_SYSTEM); + KUNIT_EXPECT_EQ(test, t->hwmon.fan.manual, 0); + KUNIT_EXPECT_FALSE(test, t->hwmon.fan.unlocked); + KUNIT_EXPECT_FALSE(test, t->fans[0].manual); +} + +/* Locked firmware: unlock on the first manual request, release on auto. */ +static void macsmc_fan_test_unlock_and_release(struct kunit *test) +{ + struct macsmc_fan_test *t = test->priv; + + KUNIT_ASSERT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 0, 2500), 0); + KUNIT_EXPECT_EQ(test, t->ftst, 1); + KUNIT_EXPECT_EQ(test, t->mode[0], MACSMC_FAN_MODE_MANUAL); + KUNIT_EXPECT_EQ(test, t->target[0], FAN_TEST_F2500); + KUNIT_EXPECT_EQ(test, t->hwmon.fan.manual, 1); + + /* A second target needs no new unlock. */ + KUNIT_ASSERT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 0, 2500), 0); + KUNIT_EXPECT_EQ(test, t->ftst_writes, 1); + + KUNIT_ASSERT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 0, 0), 0); + fan_test_released(test, t); + KUNIT_EXPECT_EQ(test, t->ftst_writes, 2); +} + +/* Targets are clamped to [min, max]; negative ones are refused. */ +static void macsmc_fan_test_clamp(struct kunit *test) +{ + struct macsmc_fan_test *t = test->priv; + + KUNIT_ASSERT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 0, 100), 0); + KUNIT_EXPECT_EQ(test, t->target[0], FAN_TEST_F1350); + KUNIT_ASSERT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 0, 100000), 0); + KUNIT_EXPECT_EQ(test, t->target[0], FAN_TEST_F5777); + KUNIT_EXPECT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 0, -1), -EINVAL); + KUNIT_ASSERT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 0, 0), 0); + fan_test_released(test, t); +} + +/* A failed target write hands the fan straight back. */ +static void macsmc_fan_test_target_write_fails(struct kunit *test) +{ + struct macsmc_fan_test *t = test->priv; + + t->fail_key = SMC_KEY(F0Tg); + KUNIT_EXPECT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 0, 2500), -EIO); + fan_test_released(test, t); +} + +/* Two manual fans: the unlock is released only with the last one. */ +static void macsmc_fan_test_two_fans(struct kunit *test) +{ + struct macsmc_fan_test *t = test->priv; + + KUNIT_ASSERT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 0, 2500), 0); + KUNIT_ASSERT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 1, 2500), 0); + KUNIT_EXPECT_EQ(test, t->hwmon.fan.manual, 2); + KUNIT_ASSERT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 0, 0), 0); + KUNIT_EXPECT_EQ(test, t->ftst, 1); + KUNIT_EXPECT_EQ(test, t->mode[1], MACSMC_FAN_MODE_MANUAL); + /* Removal, shutdown and suspend all restore through this. */ + macsmc_hwmon_fans_restore(&t->hwmon); + fan_test_released(test, t); +} + +/* Locked firmware without the unlock key: refused, nothing else written. */ +static void macsmc_fan_test_no_unlock_key(struct kunit *test) +{ + struct macsmc_fan_test *t = test->priv; + + t->hwmon.fan.unlock_key = 0; + KUNIT_EXPECT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 0, 2500), -EIO); + KUNIT_EXPECT_EQ(test, t->ftst_writes, 0); + KUNIT_EXPECT_EQ(test, t->mode[0], MACSMC_FAN_MODE_SYSTEM); + KUNIT_EXPECT_EQ(test, t->hwmon.fan.manual, 0); +} + +/* Firmware that takes the mode directly keeps the mode 0 restore. */ +static void macsmc_fan_test_legacy(struct kunit *test) +{ + struct macsmc_fan_test *t = test->priv; + + t->legacy = true; + t->mode[0] = MACSMC_FAN_MODE_AUTO; + KUNIT_ASSERT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 0, 2500), 0); + KUNIT_EXPECT_EQ(test, t->mode[0], MACSMC_FAN_MODE_MANUAL); + KUNIT_ASSERT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 0, 0), 0); + KUNIT_EXPECT_EQ(test, t->mode[0], MACSMC_FAN_MODE_AUTO); + KUNIT_EXPECT_EQ(test, t->ftst_writes, 0); + KUNIT_EXPECT_EQ(test, t->refused, 0); +} + +static struct kunit_case macsmc_fan_test_cases[] = { + KUNIT_CASE(macsmc_fan_test_unlock_and_release), + KUNIT_CASE(macsmc_fan_test_clamp), + KUNIT_CASE(macsmc_fan_test_target_write_fails), + KUNIT_CASE(macsmc_fan_test_two_fans), + KUNIT_CASE(macsmc_fan_test_no_unlock_key), + KUNIT_CASE(macsmc_fan_test_legacy), + {} +}; + +static struct kunit_suite macsmc_fan_test_suite = { + .name = "macsmc-hwmon-fan", + .init = macsmc_fan_test_init, + .exit = macsmc_fan_test_exit, + .test_cases = macsmc_fan_test_cases, +}; +kunit_test_suites(&macsmc_hwmon_test_suite, &macsmc_fan_test_suite); diff --git a/drivers/hwmon/macsmc-hwmon.c b/drivers/hwmon/macsmc-hwmon.c index 06d2c6e2d0ac3..76f07add003d3 100644 --- a/drivers/hwmon/macsmc-hwmon.c +++ b/drivers/hwmon/macsmc-hwmon.c @@ -23,12 +23,15 @@ #include #include #include +#include #include #include #include #include +#include #include #include +#include #include #include #include @@ -189,10 +192,30 @@ struct macsmc_hwmon_sensors { u32 count; }; +/* Fan mode key values */ +#define MACSMC_FAN_MODE_AUTO 0 +#define MACSMC_FAN_MODE_MANUAL 1 +/* The firmware's own fan policy; it refuses a manual mode request. */ +#define MACSMC_FAN_MODE_SYSTEM 3 + +/* How long the firmware gets to leave or re-enter its system mode */ +#define MACSMC_FAN_MODE_TIMEOUT_MS 10000 +#define MACSMC_FAN_MODE_POLL_MS 100 + struct macsmc_hwmon_fans { struct hwmon_channel_info channel_info; struct macsmc_hwmon_fan *fans; u32 count; + /* Serialises manual fan control and its unlock. */ + struct mutex lock; + /* + * Firmware that keeps its fans in system mode releases them to the + * OS only while this key is 1. Zero if the SMC has no such key. + */ + smc_key unlock_key; + bool unlocked; + /* Fans in manual mode */ + u32 manual; }; struct macsmc_hwmon { @@ -466,57 +489,199 @@ static int macsmc_hwmon_read_fan(struct macsmc_hwmon *hwmon, u32 attr, int chan, } } -static int macsmc_hwmon_write_fan(struct device *dev, u32 attr, int channel, - long val) +static int macsmc_hwmon_fan_read_mode(struct macsmc_hwmon *hwmon, + struct macsmc_hwmon_fan *fan, u8 *mode) { - struct macsmc_hwmon *hwmon = dev_get_drvdata(dev); + return apple_smc_read_u8(hwmon->smc, fan->mode.macsmc_key, mode); +} + +/* + * Hand the fans back to the firmware once no fan is manual: clear the unlock + * key and wait for every fan to return to system mode. Called with the fan + * lock held. + */ +static int macsmc_hwmon_fans_relock(struct macsmc_hwmon *hwmon) +{ + unsigned long timeout; + u32 i; + u8 mode; + int ret; + + ret = apple_smc_write_u8(hwmon->smc, hwmon->fan.unlock_key, 0); + if (ret < 0) { + dev_err(hwmon->dev, "cannot hand the fans back to the SMC (%d)\n", ret); + return ret; + } + hwmon->fan.unlocked = false; + + timeout = jiffies + msecs_to_jiffies(MACSMC_FAN_MODE_TIMEOUT_MS); + for (i = 0; i < hwmon->fan.count; i++) { + struct macsmc_hwmon_fan *fan = &hwmon->fan.fans[i]; + + if (!fan->mode.macsmc_key) + continue; + for (;;) { + ret = macsmc_hwmon_fan_read_mode(hwmon, fan, &mode); + if (ret) + return ret; + if (mode == MACSMC_FAN_MODE_SYSTEM) + break; + if (time_after(jiffies, timeout)) { + dev_warn(hwmon->dev, "%s stays in mode %u after release\n", + fan->label, mode); + return -ETIMEDOUT; + } + msleep(MACSMC_FAN_MODE_POLL_MS); + } + } + + return 0; +} + +/* Return a fan to firmware control. Called with the fan lock held. */ +static int macsmc_hwmon_fan_auto(struct macsmc_hwmon *hwmon, + struct macsmc_hwmon_fan *fan) +{ + int ret, relock; + + if (!fan->manual) + return 0; + + ret = macsmc_hwmon_write_key(hwmon->smc, &fan->mode, MACSMC_FAN_MODE_AUTO); + fan->manual = false; + hwmon->fan.manual--; + + if (hwmon->fan.unlocked && !hwmon->fan.manual) { + relock = macsmc_hwmon_fans_relock(hwmon); + if (relock) + return relock; + } + + return ret < 0 ? ret : 0; +} + +/* + * Put a fan in manual mode. Firmware in system mode refuses that until the + * unlock key is set; it then leaves system mode and accepts the request. + * Called with the fan lock held. + */ +static int macsmc_hwmon_fan_manual(struct macsmc_hwmon *hwmon, + struct macsmc_hwmon_fan *fan) +{ + unsigned long timeout; + u8 mode; + int ret; + + if (fan->manual) + return 0; + + ret = macsmc_hwmon_write_key(hwmon->smc, &fan->mode, MACSMC_FAN_MODE_MANUAL); + if (ret < 0 && hwmon->fan.unlock_key && !hwmon->fan.unlocked && + !macsmc_hwmon_fan_read_mode(hwmon, fan, &mode) && + mode == MACSMC_FAN_MODE_SYSTEM) { + ret = apple_smc_write_u8(hwmon->smc, hwmon->fan.unlock_key, 1); + if (ret < 0) { + /* The key may have been set anyway; clear it. */ + apple_smc_write_u8(hwmon->smc, hwmon->fan.unlock_key, 0); + return ret; + } + hwmon->fan.unlocked = true; + + /* Ask again once the fan has left system mode. */ + timeout = jiffies + msecs_to_jiffies(MACSMC_FAN_MODE_TIMEOUT_MS); + for (;;) { + ret = macsmc_hwmon_fan_read_mode(hwmon, fan, &mode); + if (!ret && mode == MACSMC_FAN_MODE_SYSTEM) + ret = -EBUSY; + else if (!ret) + ret = macsmc_hwmon_write_key(hwmon->smc, &fan->mode, + MACSMC_FAN_MODE_MANUAL); + if (ret >= 0 || time_after(jiffies, timeout)) + break; + msleep(MACSMC_FAN_MODE_POLL_MS); + } + if (ret < 0) { + dev_err(hwmon->dev, "%s refused manual mode after unlock (%d)\n", + fan->label, ret); + if (!hwmon->fan.manual) + macsmc_hwmon_fans_relock(hwmon); + return ret; + } + } + if (ret < 0) + return ret; + + fan->manual = true; + hwmon->fan.manual++; + return 0; +} + +static int macsmc_hwmon_set_fan(struct macsmc_hwmon *hwmon, int channel, long val) +{ + struct macsmc_hwmon_fan *fan = &hwmon->fan.fans[channel]; long min, max; int ret; - if (!fan_control || hwmon->fan.fans[channel].mode.macsmc_key == 0) + if (!fan_control || fan->mode.macsmc_key == 0) return -EOPNOTSUPP; + if (val < 0) + return -EINVAL; + + guard(mutex)(&hwmon->fan.lock); + + /* 0 hands the fan back to the firmware. */ + if (!val) + return macsmc_hwmon_fan_auto(hwmon, fan); /* * The SMC does no sanity checks on requested fan speeds, so we need to. */ - ret = macsmc_hwmon_read_key(hwmon->smc, &hwmon->fan.fans[channel].min, - 1, &min); + ret = macsmc_hwmon_read_key(hwmon->smc, &fan->min, 1, &min); if (ret) return ret; - ret = macsmc_hwmon_read_key(hwmon->smc, &hwmon->fan.fans[channel].max, - 1, &max); + ret = macsmc_hwmon_read_key(hwmon->smc, &fan->max, 1, &max); if (ret) return ret; - if (val >= min && val <= max) { - if (!hwmon->fan.fans[channel].manual) { - /* Write 1 to mode key for manual control */ - ret = macsmc_hwmon_write_key(hwmon->smc, - &hwmon->fan.fans[channel].mode, 1); - if (ret < 0) - return ret; + if (min <= 0 || max < min) + return -EIO; + val = clamp(val, min, max); - hwmon->fan.fans[channel].manual = true; - } - return macsmc_hwmon_write_key(hwmon->smc, - &hwmon->fan.fans[channel].set, val); - } else if (!val) { - if (hwmon->fan.fans[channel].manual) { - ret = macsmc_hwmon_write_key(hwmon->smc, - &hwmon->fan.fans[channel].mode, 0); - if (ret < 0) - return ret; + ret = macsmc_hwmon_fan_manual(hwmon, fan); + if (ret) + return ret; - hwmon->fan.fans[channel].manual = false; - } - } else { - return -EINVAL; + ret = macsmc_hwmon_write_key(hwmon->smc, &fan->set, val); + if (ret < 0) { + macsmc_hwmon_fan_auto(hwmon, fan); + return ret; } return 0; } +static int macsmc_hwmon_write_fan(struct device *dev, u32 attr, int channel, + long val) +{ + return macsmc_hwmon_set_fan(dev_get_drvdata(dev), channel, val); +} + +/* Hand every manual fan back to the firmware. */ +static void macsmc_hwmon_fans_restore(struct macsmc_hwmon *hwmon) +{ + u32 i; + + guard(mutex)(&hwmon->fan.lock); + for (i = 0; i < hwmon->fan.count; i++) + macsmc_hwmon_fan_auto(hwmon, &hwmon->fan.fans[i]); +} + +static void macsmc_hwmon_fans_restore_action(void *data) +{ + macsmc_hwmon_fans_restore(data); +} + static int macsmc_hwmon_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long *val) { @@ -1343,6 +1508,11 @@ static int macsmc_hwmon_probe(struct platform_device *pdev) hwmon->dev = &pdev->dev; hwmon->smc = smc; hwmon->register_thermal_zones = true; + platform_set_drvdata(pdev, hwmon); + + ret = devm_mutex_init(&pdev->dev, &hwmon->fan.lock); + if (ret) + return ret; ret = macsmc_hwmon_populate_sensors(hwmon, hwmon->dev->of_node); if (ret) { @@ -1372,6 +1542,21 @@ static int macsmc_hwmon_probe(struct platform_device *pdev) if (ret) return ret; + if (hwmon->fan.count) { + struct apple_smc_key_info info; + + if (!apple_smc_get_key_info(smc, SMC_KEY(Ftst), &info) && + info.type_code == __SMC_KEY('u', 'i', '8', ' ') && + (info.flags & APPLE_SMC_WRITABLE)) + hwmon->fan.unlock_key = SMC_KEY(Ftst); + + /* Runs after the hwmon device is gone, so no write races it. */ + ret = devm_add_action_or_reset(&pdev->dev, + macsmc_hwmon_fans_restore_action, hwmon); + if (ret) + return ret; + } + hwmon->chip_info.ops = &macsmc_hwmon_ops; hwmon->chip_info.info = (const struct hwmon_channel_info *const *)&hwmon->channel_infos; @@ -1407,11 +1592,29 @@ static const struct of_device_id macsmc_hwmon_of_table[] = { }; MODULE_DEVICE_TABLE(of, macsmc_hwmon_of_table); +static void macsmc_hwmon_shutdown(struct platform_device *pdev) +{ + struct macsmc_hwmon *hwmon = platform_get_drvdata(pdev); + + if (hwmon) + macsmc_hwmon_fans_restore(hwmon); +} + +static int macsmc_hwmon_suspend(struct device *dev) +{ + macsmc_hwmon_fans_restore(dev_get_drvdata(dev)); + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(macsmc_hwmon_pm_ops, macsmc_hwmon_suspend, NULL); + static struct platform_driver macsmc_hwmon_driver = { .probe = macsmc_hwmon_probe, + .shutdown = macsmc_hwmon_shutdown, .driver = { .name = "macsmc-hwmon", .of_match_table = macsmc_hwmon_of_table, + .pm = pm_sleep_ptr(&macsmc_hwmon_pm_ops), }, }; module_platform_driver(macsmc_hwmon_driver); From dac4a3ae7858f8ef1b4f054d11efbac9c4785c67 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Tue, 27 Oct 2026 17:59:10 +1100 Subject: [PATCH 155/184] arm64: dts: apple: t6040-j616s: Shed CPU power at the GPU firmware limit The GPU zones shed CPU power from 105 C, a pure backstop. But the J616s fans give no extra headroom under Linux: the SMC keeps them in its own mode (3), turns them on only at minimum speed (seen from 93-99 C die on a cold machine, from about 60 C once it is heat-soaked) and refuses an OS write of manual mode. The GPU firmware itself starts limiting at 101 C, and conformance runs reached 103.8 C. Start the GPU zones' passive trip at that same 101 C, so that once the GPU is at its own limit the CPU clusters give up power to it instead of both being limited separately. The critical trip stays at 118 C. Assisted-by: LLM Signed-off-by: Ryan Murray --- arch/arm64/boot/dts/apple/t6040-j616s.dts | 28 +++++++++++++---------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/arch/arm64/boot/dts/apple/t6040-j616s.dts b/arch/arm64/boot/dts/apple/t6040-j616s.dts index 6a85dda0804ac..69a891d7111d5 100644 --- a/arch/arm64/boot/dts/apple/t6040-j616s.dts +++ b/arch/arm64/boot/dts/apple/t6040-j616s.dts @@ -313,11 +313,15 @@ * failure of that loop; they never act in normal use. * * The GPU has no cooling device of its own: its firmware sets the GPU - * performance state. The seven hottest GPU die sensors (Tg05, Tg0S, - * Tg0Y, Tg0k, Tg0z, Tg1V, Tg1l) shed package power through the CPU - * clusters from 105 C, above the 103.8 C peak measured during - * conformance runs, and shut down at 118 C. While the GPU is powered - * off its sensors report placeholder values from -4 to 4 C. + * performance state and starts limiting at 101 C. The SMC runs the + * fans in its own mode (3), only at their minimum speed in every load + * measured under Linux, and refuses manual fan modes from the OS. So + * when a GPU die sensor reaches the GPU firmware's limit, the seven + * hottest ones (Tg05, Tg0S, Tg0Y, Tg0k, Tg0z, Tg1V, Tg1l) also shed + * CPU power through the CPU clusters, from the same 101 C, leaving the + * shared heat budget to the GPU. They shut down at 118 C. While the + * GPU is powered off its sensors report placeholder values from -4 to + * 4 C. */ thermal-zones { cpu-die-thermal { @@ -886,7 +890,7 @@ trips { gpu0_passive: trip-passive { - temperature = <105000>; + temperature = <101000>; hysteresis = <5000>; type = "passive"; }; @@ -937,7 +941,7 @@ trips { gpu1_passive: trip-passive { - temperature = <105000>; + temperature = <101000>; hysteresis = <5000>; type = "passive"; }; @@ -988,7 +992,7 @@ trips { gpu2_passive: trip-passive { - temperature = <105000>; + temperature = <101000>; hysteresis = <5000>; type = "passive"; }; @@ -1039,7 +1043,7 @@ trips { gpu3_passive: trip-passive { - temperature = <105000>; + temperature = <101000>; hysteresis = <5000>; type = "passive"; }; @@ -1090,7 +1094,7 @@ trips { gpu4_passive: trip-passive { - temperature = <105000>; + temperature = <101000>; hysteresis = <5000>; type = "passive"; }; @@ -1141,7 +1145,7 @@ trips { gpu5_passive: trip-passive { - temperature = <105000>; + temperature = <101000>; hysteresis = <5000>; type = "passive"; }; @@ -1192,7 +1196,7 @@ trips { gpu6_passive: trip-passive { - temperature = <105000>; + temperature = <101000>; hysteresis = <5000>; type = "passive"; }; From 2c01e788b9a7dbc6cd5e05c161379df2a47675b6 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Tue, 27 Oct 2026 15:02:20 +1100 Subject: [PATCH 156/184] arm64: dts: apple: describe the remaining T6040 PMGR power states Describe the power-state registers and their parent graph. Leave providers without a qualified Linux consumer disabled, since registering an unused domain may power down hardware still owned by firmware. Keep the existing tested providers unchanged. Assisted-by: LLM Signed-off-by: Ryan Murray --- arch/arm64/boot/dts/apple/t6040-pmgr.dtsi | 1762 +++++++++++++++++++++ arch/arm64/boot/dts/apple/t6040.dtsi | 21 + 2 files changed, 1783 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t6040-pmgr.dtsi b/arch/arm64/boot/dts/apple/t6040-pmgr.dtsi index bf357745d581d..18ec5df7d33bf 100644 --- a/arch/arm64/boot/dts/apple/t6040-pmgr.dtsi +++ b/arch/arm64/boot/dts/apple/t6040-pmgr.dtsi @@ -506,3 +506,1765 @@ power-domains = <&ps_atc2_phymxwrap>; }; }; + +/* + * Remaining ADT power states. Keep providers without a qualified consumer + * disabled: registering an unused genpd can power down firmware-owned blocks. + * Enable the provider and its parents together with the consumer. + */ +&pmgr { + ps_apcie_gp: power-controller@100 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x100 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "apcie_gp"; + status = "disabled"; + }; + + ps_fab4_soc: power-controller@128 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x128 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "fab4_soc"; + power-domains = <&ps_afc>; + apple,always-on; + status = "disabled"; + }; + + ps_sio_cpu: power-controller@130 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x130 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "sio_cpu"; + power-domains = <&ps_sio>; + apple,always-on; + status = "disabled"; + }; + + ps_fpwm1: power-controller@140 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x140 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "fpwm1"; + power-domains = <&ps_sio>; + status = "disabled"; + }; + + ps_fpwm2: power-controller@148 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x148 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "fpwm2"; + power-domains = <&ps_sio>; + status = "disabled"; + }; + + ps_i2c0: power-controller@150 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x150 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "i2c0"; + power-domains = <&ps_sio>; + status = "disabled"; + }; + + ps_i2c5: power-controller@178 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x178 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "i2c5"; + power-domains = <&ps_sio>; + status = "disabled"; + }; + + ps_i2c7: power-controller@188 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x188 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "i2c7"; + power-domains = <&ps_sio>; + status = "disabled"; + }; + + ps_spi_p: power-controller@198 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x198 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "spi_p"; + power-domains = <&ps_sio>; + status = "disabled"; + }; + + ps_uart_p: power-controller@1a0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1a0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "uart_p"; + power-domains = <&ps_sio>; + status = "disabled"; + }; + + ps_dpa_p: power-controller@1a8 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1a8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dpa_p"; + power-domains = <&ps_sio>; + apple,always-on; + status = "disabled"; + }; + + ps_aes: power-controller@1b0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1b0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "aes"; + power-domains = <&ps_sio>; + apple,always-on; + status = "disabled"; + }; + + ps_spi0: power-controller@1b8 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1b8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "spi0"; + power-domains = <&ps_spi_p>; + status = "disabled"; + }; + + ps_spi1: power-controller@1c0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1c0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "spi1"; + power-domains = <&ps_spi_p>; + status = "disabled"; + }; + + ps_spi2: power-controller@1c8 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1c8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "spi2"; + power-domains = <&ps_spi_p>; + status = "disabled"; + }; + + ps_spi3: power-controller@1d0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1d0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "spi3"; + power-domains = <&ps_spi_p>; + status = "disabled"; + }; + + ps_spi4: power-controller@1d8 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1d8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "spi4"; + power-domains = <&ps_spi_p>; + status = "disabled"; + }; + + ps_spi5: power-controller@1e0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1e0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "spi5"; + power-domains = <&ps_spi_p>; + status = "disabled"; + }; + + ps_qspi: power-controller@1e8 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1e8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "qspi"; + power-domains = <&ps_spi_p>; + status = "disabled"; + }; + + ps_uart_n: power-controller@1f0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1f0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "uart_n"; + power-domains = <&ps_uart_p>; + status = "disabled"; + }; + + ps_uart0: power-controller@1f8 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1f8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "uart0"; + power-domains = <&ps_uart_p>; + status = "disabled"; + }; + + ps_uart1: power-controller@200 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x200 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "uart1"; + power-domains = <&ps_uart_p>; + status = "disabled"; + }; + + ps_uart2: power-controller@208 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x208 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "uart2"; + power-domains = <&ps_uart_p>; + status = "disabled"; + }; + + ps_uart3: power-controller@210 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x210 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "uart3"; + power-domains = <&ps_uart_p>; + status = "disabled"; + }; + + ps_uart4: power-controller@218 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x218 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "uart4"; + power-domains = <&ps_uart_p>; + status = "disabled"; + }; + + ps_uart5: power-controller@220 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x220 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "uart5"; + power-domains = <&ps_uart_p>; + status = "disabled"; + }; + + ps_uart6: power-controller@228 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x228 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "uart6"; + power-domains = <&ps_uart_p>; + status = "disabled"; + }; + + ps_dpa0: power-controller@230 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x230 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dpa0"; + power-domains = <&ps_dpa_p>; + status = "disabled"; + }; + + ps_dpa1: power-controller@238 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x238 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dpa1"; + power-domains = <&ps_dpa_p>; + status = "disabled"; + }; + + ps_dpa2: power-controller@240 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x240 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dpa2"; + power-domains = <&ps_dpa_p>; + status = "disabled"; + }; + + ps_dpa3: power-controller@248 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x248 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dpa3"; + power-domains = <&ps_dpa_p>; + status = "disabled"; + }; + + ps_dpa4: power-controller@250 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x250 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dpa4"; + power-domains = <&ps_dpa_p>; + status = "disabled"; + }; + + ps_disp_sys: power-controller@258 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x258 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "disp_sys"; + power-domains = <&ps_fab6_soc>; + status = "disabled"; + }; + + ps_amcc4: power-controller@260 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x260 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "amcc4"; + power-domains = <&ps_afc>; + apple,always-on; + status = "disabled"; + }; + + ps_amcc5: power-controller@268 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x268 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "amcc5"; + power-domains = <&ps_afc>; + apple,always-on; + status = "disabled"; + }; + + ps_amcc6: power-controller@270 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x270 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "amcc6"; + power-domains = <&ps_afc>; + apple,always-on; + status = "disabled"; + }; + + ps_amcc7: power-controller@278 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x278 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "amcc7"; + power-domains = <&ps_afc>; + apple,always-on; + status = "disabled"; + }; + + ps_amcc12: power-controller@280 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x280 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "amcc12"; + power-domains = <&ps_afc>; + apple,always-on; + status = "disabled"; + }; + + ps_amcc13: power-controller@288 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x288 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "amcc13"; + power-domains = <&ps_afc>; + apple,always-on; + status = "disabled"; + }; + + ps_amcc14: power-controller@290 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x290 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "amcc14"; + power-domains = <&ps_afc>; + apple,always-on; + status = "disabled"; + }; + + ps_amcc15: power-controller@298 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x298 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "amcc15"; + power-domains = <&ps_afc>; + apple,always-on; + status = "disabled"; + }; + + ps_dcs_04: power-controller@2e0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x2e0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dcs_04"; + power-domains = <&ps_amcc4>; + apple,always-on; + status = "disabled"; + }; + + ps_dcs_05: power-controller@2e8 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x2e8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dcs_05"; + power-domains = <&ps_amcc5>; + apple,always-on; + status = "disabled"; + }; + + ps_dcs_06: power-controller@2f0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x2f0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dcs_06"; + power-domains = <&ps_amcc6>; + apple,always-on; + status = "disabled"; + }; + + ps_dcs_07: power-controller@2f8 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x2f8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dcs_07"; + power-domains = <&ps_amcc7>; + apple,always-on; + status = "disabled"; + }; + + ps_dcs_12: power-controller@300 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x300 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dcs_12"; + power-domains = <&ps_amcc12>; + apple,always-on; + status = "disabled"; + }; + + ps_dcs_13: power-controller@308 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x308 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dcs_13"; + power-domains = <&ps_amcc13>; + apple,always-on; + status = "disabled"; + }; + + ps_dcs_14: power-controller@310 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x310 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dcs_14"; + power-domains = <&ps_amcc14>; + apple,always-on; + status = "disabled"; + }; + + ps_dcs_15: power-controller@318 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x318 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dcs_15"; + power-domains = <&ps_amcc15>; + apple,always-on; + status = "disabled"; + }; + + ps_disp_fe: power-controller@368 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x368 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "disp_fe"; + power-domains = <&ps_disp_sys>; + status = "disabled"; + }; + + ps_apcie_sys_gp: power-controller@390 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x390 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "apcie_sys_gp"; + power-domains = <&ps_fab3_soc>, <&ps_apcie_gp>; + status = "disabled"; + }; + + ps_apcie_phy_sw: power-controller@398 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x398 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "apcie_phy_sw"; + power-domains = <&ps_apcie_sys_st0>, <&ps_apcie_sys_st1>; + status = "disabled"; + }; + + ps_jpg: power-controller@3a0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x3a0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "jpg"; + power-domains = <&ps_fab4_soc>; + status = "disabled"; + }; + + ps_ane_sys: power-controller@3c0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x3c0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "ane_sys"; + power-domains = <&ps_fab5_soc>, <&ps_fab4_soc>; + status = "disabled"; + }; + + ps_sep: power-controller@c00 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0xc00 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "sep"; + power-domains = <&ps_fab3_soc>; + apple,always-on; + status = "disabled"; + }; +}; + +&pmgr_aux { + ps_ecpu_0: power-controller@0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "ecpu_0"; + status = "disabled"; + }; + + ps_ecpu_1: power-controller@8 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "ecpu_1"; + status = "disabled"; + }; + + ps_ecpu_2: power-controller@10 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x10 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "ecpu_2"; + status = "disabled"; + }; + + ps_ecpu_3: power-controller@18 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x18 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "ecpu_3"; + status = "disabled"; + }; + + ps_pcpu0_0: power-controller@20 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x20 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pcpu0_0"; + status = "disabled"; + }; + + ps_pcpu0_1: power-controller@28 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x28 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pcpu0_1"; + status = "disabled"; + }; + + ps_pcpu0_2: power-controller@30 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x30 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pcpu0_2"; + status = "disabled"; + }; + + ps_pcpu0_3: power-controller@38 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x38 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pcpu0_3"; + status = "disabled"; + }; + + ps_pcpu0_4: power-controller@40 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x40 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pcpu0_4"; + status = "disabled"; + }; + + ps_pcpu0_5: power-controller@48 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x48 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pcpu0_5"; + status = "disabled"; + }; + + ps_pcpu1_0: power-controller@50 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x50 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pcpu1_0"; + status = "disabled"; + }; + + ps_pcpu1_1: power-controller@58 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x58 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pcpu1_1"; + status = "disabled"; + }; + + ps_pcpu1_2: power-controller@60 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x60 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pcpu1_2"; + status = "disabled"; + }; + + ps_pcpu1_3: power-controller@68 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x68 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pcpu1_3"; + status = "disabled"; + }; + + ps_pcpu1_4: power-controller@70 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x70 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pcpu1_4"; + status = "disabled"; + }; + + ps_pcpu1_5: power-controller@78 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x78 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pcpu1_5"; + status = "disabled"; + }; + + ps_ecpm: power-controller@80 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x80 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "ecpm"; + status = "disabled"; + }; + + ps_pcpm0: power-controller@88 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x88 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pcpm0"; + status = "disabled"; + }; + + ps_pcpm1: power-controller@90 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x90 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pcpm1"; + status = "disabled"; + }; + + ps_dwi: power-controller@108 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x108 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dwi"; + status = "disabled"; + }; + + ps_aic: power-controller@118 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x118 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "aic"; + power-domains = <&ps_pms>; + status = "disabled"; + }; + + ps_pmc: power-controller@120 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x120 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pmc"; + power-domains = <&ps_pms>; + apple,always-on; + status = "disabled"; + }; + + ps_pms_c1ppt: power-controller@128 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x128 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pms_c1ppt"; + power-domains = <&ps_pms>; + apple,always-on; + status = "disabled"; + }; + + ps_soc_dpe: power-controller@130 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x130 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "soc_dpe"; + power-domains = <&ps_pms>; + apple,always-on; + status = "disabled"; + }; + + ps_pmgr_soc_ocla: power-controller@138 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x138 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pmgr_soc_ocla"; + status = "disabled"; + }; + + ps_pms_fpwm0: power-controller@140 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x140 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pms_fpwm0"; + power-domains = <&ps_pms>; + apple,always-on; + status = "disabled"; + }; + + ps_pms_fpwm1: power-controller@148 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x148 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pms_fpwm1"; + power-domains = <&ps_pms>; + apple,always-on; + status = "disabled"; + }; + + ps_pms_fpwm2: power-controller@150 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x150 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pms_fpwm2"; + power-domains = <&ps_pms>; + apple,always-on; + status = "disabled"; + }; + + ps_pms_fpwm3: power-controller@158 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x158 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pms_fpwm3"; + power-domains = <&ps_pms>; + apple,always-on; + status = "disabled"; + }; + + ps_pms_fpwm4: power-controller@160 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x160 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pms_fpwm4"; + power-domains = <&ps_pms>; + apple,always-on; + status = "disabled"; + }; + + ps_fab0_soc: power-controller@168 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x168 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "fab0_soc"; + power-domains = <&ps_afi>; + apple,always-on; + status = "disabled"; + }; + + ps_amcc0: power-controller@180 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x180 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "amcc0"; + power-domains = <&ps_afc>; + apple,always-on; + status = "disabled"; + }; + + ps_dcs_00: power-controller@188 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x188 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dcs_00"; + power-domains = <&ps_amcc0>; + apple,always-on; + status = "disabled"; + }; + + ps_rom: power-controller@190 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x190 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "rom"; + power-domains = <&ps_amcc0>; + status = "disabled"; + }; + + ps_amcc1: power-controller@198 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x198 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "amcc1"; + power-domains = <&ps_afc>; + apple,always-on; + status = "disabled"; + }; + + ps_dcs_01: power-controller@1a0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1a0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dcs_01"; + power-domains = <&ps_amcc1>; + apple,always-on; + status = "disabled"; + }; + + ps_amcc2: power-controller@1a8 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1a8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "amcc2"; + power-domains = <&ps_afc>; + apple,always-on; + status = "disabled"; + }; + + ps_dcs_02: power-controller@1b0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1b0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dcs_02"; + power-domains = <&ps_amcc2>; + apple,always-on; + status = "disabled"; + }; + + ps_amcc3: power-controller@1b8 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1b8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "amcc3"; + power-domains = <&ps_afc>; + apple,always-on; + status = "disabled"; + }; + + ps_dcs_03: power-controller@1c0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1c0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dcs_03"; + power-domains = <&ps_amcc3>; + apple,always-on; + status = "disabled"; + }; + + ps_amcc8: power-controller@1c8 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1c8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "amcc8"; + power-domains = <&ps_afc>; + apple,always-on; + status = "disabled"; + }; + + ps_dcs_08: power-controller@1d0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1d0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dcs_08"; + power-domains = <&ps_amcc8>; + apple,always-on; + status = "disabled"; + }; + + ps_amcc9: power-controller@1d8 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1d8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "amcc9"; + power-domains = <&ps_afc>; + apple,always-on; + status = "disabled"; + }; + + ps_dcs_09: power-controller@1e0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1e0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dcs_09"; + power-domains = <&ps_amcc9>; + apple,always-on; + status = "disabled"; + }; + + ps_amcc10: power-controller@1e8 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1e8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "amcc10"; + power-domains = <&ps_afc>; + apple,always-on; + status = "disabled"; + }; + + ps_dcs_10: power-controller@1f0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1f0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dcs_10"; + power-domains = <&ps_amcc10>; + apple,always-on; + status = "disabled"; + }; + + ps_amcc11: power-controller@1f8 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1f8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "amcc11"; + power-domains = <&ps_afc>; + apple,always-on; + status = "disabled"; + }; + + ps_dcs_11: power-controller@200 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x200 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dcs_11"; + power-domains = <&ps_amcc11>; + apple,always-on; + status = "disabled"; + }; + + ps_venc0_sys: power-controller@278 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x278 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "venc0_sys"; + power-domains = <&ps_fab0_soc>; + status = "disabled"; + }; + + ps_fab_gw_0: power-controller@288 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x288 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "fab_gw_0"; + apple,always-on; + status = "disabled"; + }; + + ps_fab_gw_1: power-controller@290 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x290 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "fab_gw_1"; + apple,always-on; + status = "disabled"; + }; + + ps_fab_afr: power-controller@298 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x298 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "fab_afr"; + apple,always-on; + status = "disabled"; + }; + + ps_fab1_soc: power-controller@2d0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x2d0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "fab1_soc"; + power-domains = <&ps_afi>; + apple,always-on; + status = "disabled"; + }; + + ps_scodec: power-controller@328 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x328 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "scodec"; + power-domains = <&ps_fab1_soc>; + status = "disabled"; + }; + + ps_scodec_streaming: power-controller@330 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x330 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "scodec_streaming"; + power-domains = <&ps_scodec>; + status = "disabled"; + }; + + ps_fab2_soc: power-controller@338 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x338 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "fab2_soc"; + power-domains = <&ps_afi>; + apple,always-on; + status = "disabled"; + }; + + ps_dispext0_sys: power-controller@340 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x340 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dispext0_sys"; + power-domains = <&ps_fab2_soc>; + status = "disabled"; + }; + + ps_dispext0_fe: power-controller@348 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x348 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dispext0_fe"; + power-domains = <&ps_dispext0_sys>; + status = "disabled"; + }; + + ps_dispext0_cpu: power-controller@350 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x350 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dispext0_cpu"; + power-domains = <&ps_dispext0_fe>; + status = "disabled"; + }; + + ps_atc0_cio: power-controller@398 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x398 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc0_cio"; + power-domains = <&ps_atc0_common>; + status = "disabled"; + }; + + ps_atc0_pcie: power-controller@3a0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x3a0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc0_pcie"; + power-domains = <&ps_atc0_common>; + status = "disabled"; + }; + + ps_atc0_cio_pcie: power-controller@3a8 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x3a8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc0_cio_pcie"; + power-domains = <&ps_atc0_cio>; + status = "disabled"; + }; + + ps_atc0_cio_usb: power-controller@3b0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x3b0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc0_cio_usb"; + power-domains = <&ps_atc0_cio>; + status = "disabled"; + }; + + ps_atc1_cio: power-controller@3c0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x3c0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc1_cio"; + power-domains = <&ps_atc1_common>; + status = "disabled"; + }; + + ps_atc1_pcie: power-controller@3c8 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x3c8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc1_pcie"; + power-domains = <&ps_atc1_common>; + status = "disabled"; + }; + + ps_atc1_cio_pcie: power-controller@3d0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x3d0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc1_cio_pcie"; + power-domains = <&ps_atc1_cio>; + status = "disabled"; + }; + + ps_atc1_cio_usb: power-controller@3d8 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x3d8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc1_cio_usb"; + power-domains = <&ps_atc1_cio>; + status = "disabled"; + }; + + ps_atc2_cio: power-controller@3e8 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x3e8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc2_cio"; + power-domains = <&ps_atc2_common>; + status = "disabled"; + }; + + ps_atc2_pcie: power-controller@3f0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x3f0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc2_pcie"; + power-domains = <&ps_atc2_common>; + status = "disabled"; + }; + + ps_atc2_cio_pcie: power-controller@3f8 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x3f8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc2_cio_pcie"; + power-domains = <&ps_atc2_cio>; + status = "disabled"; + }; + + ps_atc2_cio_usb: power-controller@400 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x400 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc2_cio_usb"; + power-domains = <&ps_atc2_cio>; + status = "disabled"; + }; + + ps_atc3_common: power-controller@408 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x408 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc3_common"; + power-domains = <&ps_fab2_soc>; + status = "disabled"; + }; + + ps_atc3_cio: power-controller@420 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x420 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc3_cio"; + power-domains = <&ps_atc3_common>; + status = "disabled"; + }; + + ps_atc3_pcie: power-controller@428 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x428 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc3_pcie"; + power-domains = <&ps_atc3_common>; + status = "disabled"; + }; + + ps_atc3_cio_pcie: power-controller@430 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x430 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc3_cio_pcie"; + power-domains = <&ps_atc3_cio>; + status = "disabled"; + }; + + ps_atc3_cio_usb: power-controller@438 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x438 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc3_cio_usb"; + power-domains = <&ps_atc3_cio>; + status = "disabled"; + }; + + ps_fab5_soc: power-controller@440 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x440 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "fab5_soc"; + power-domains = <&ps_afi>; + apple,always-on; + status = "disabled"; + }; + + ps_sbr: power-controller@448 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x448 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "sbr"; + power-domains = <&ps_fab5_soc>; + apple,always-on; + status = "disabled"; + }; + + ps_gpio: power-controller@450 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x450 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "gpio"; + power-domains = <&ps_sbr>; + status = "disabled"; + }; + + ps_msg: power-controller@458 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x458 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "msg"; + power-domains = <&ps_sbr>; + status = "disabled"; + }; + + ps_ap_tmm: power-controller@468 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x468 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "ap_tmm"; + power-domains = <&ps_sbr>; + status = "disabled"; + }; + + ps_msr0: power-controller@470 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x470 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "msr0"; + power-domains = <&ps_fab5_soc>; + status = "disabled"; + }; + + ps_msr0_ase_core: power-controller@478 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x478 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "msr0_ase_core"; + power-domains = <&ps_msr0>; + status = "disabled"; + }; + + ps_fab7_soc: power-controller@488 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x488 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "fab7_soc"; + power-domains = <&ps_afi>; + apple,always-on; + status = "disabled"; + }; + + ps_dispext1_sys: power-controller@490 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x490 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dispext1_sys"; + power-domains = <&ps_fab7_soc>; + status = "disabled"; + }; + + ps_dispext1_fe: power-controller@498 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x498 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dispext1_fe"; + power-domains = <&ps_dispext1_sys>; + status = "disabled"; + }; + + ps_dispext1_cpu: power-controller@4a0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x4a0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dispext1_cpu"; + power-domains = <&ps_dispext1_fe>; + status = "disabled"; + }; +}; + +&pmgr_ane { + ps_ane_mpm: power-controller@0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "ane_mpm"; + power-domains = <&ps_ane_sys>; + status = "disabled"; + }; + + ps_ane_cpu: power-controller@8 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "ane_cpu"; + power-domains = <&ps_ane_sys>; + status = "disabled"; + }; + + ps_ane_td: power-controller@10 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x10 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "ane_td"; + power-domains = <&ps_ane_sys>; + status = "disabled"; + }; + + ps_ane_base: power-controller@18 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x18 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "ane_base"; + power-domains = <&ps_ane_td>; + status = "disabled"; + }; +}; + +&pmgr_mini { + ps_debug_gated: power-controller@0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "debug_gated"; + apple,always-on; + status = "disabled"; + }; + + ps_nub_spmi_ahb: power-controller@58 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x58 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "nub_spmi_ahb"; + apple,always-on; + status = "disabled"; + }; + + ps_nub_spmi0: power-controller@60 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x60 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "nub_spmi0"; + power-domains = <&ps_nub_spmi_ahb>; + apple,always-on; + status = "disabled"; + }; + + ps_nub_spmi_a2: power-controller@78 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x78 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "nub_spmi_a2"; + apple,always-on; + status = "disabled"; + }; + + ps_nub_spi0: power-controller@80 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x80 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "nub_spi0"; + apple,always-on; + status = "disabled"; + }; + + ps_nub_aon: power-controller@88 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x88 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "nub_aon"; + apple,always-on; + status = "disabled"; + }; + + ps_nub_ocla: power-controller@98 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x98 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "nub_ocla"; + apple,always-on; + status = "disabled"; + }; + + ps_atc3_common_dp: power-controller@b8 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0xb8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc3_common_dp"; + status = "disabled"; + }; + + ps_nub_spmi1: power-controller@c0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0xc0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "nub_spmi1"; + power-domains = <&ps_nub_spmi_ahb>; + apple,always-on; + status = "disabled"; + }; + + ps_atc3_usb_aon: power-controller@e0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0xe0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc3_usb_aon"; + status = "disabled"; + }; + + ps_nub_spmi2: power-controller@e8 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0xe8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "nub_spmi2"; + power-domains = <&ps_nub_spmi_ahb>; + apple,always-on; + status = "disabled"; + }; + + ps_nub_spmi3: power-controller@f0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0xf0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "nub_spmi3"; + power-domains = <&ps_nub_spmi_ahb>; + apple,always-on; + status = "disabled"; + }; + + ps_nub_spmi4: power-controller@f8 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0xf8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "nub_spmi4"; + power-domains = <&ps_nub_spmi_ahb>; + apple,always-on; + status = "disabled"; + }; + + ps_nub_fabric: power-controller@100 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x100 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "nub_fabric"; + apple,always-on; + status = "disabled"; + }; + + ps_nub_sram: power-controller@108 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x108 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "nub_sram"; + power-domains = <&ps_nub_fabric>; + apple,always-on; + status = "disabled"; + }; + + ps_debug_switch: power-controller@110 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x110 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "debug_switch"; + apple,always-on; + status = "disabled"; + }; + + ps_atc3_usb: power-controller@148 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x148 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc3_usb"; + power-domains = <&ps_atc3_common>, <&ps_atc3_usb_aon>; + status = "disabled"; + }; + + ps_atc3_phymxwrap: power-controller@168 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x168 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc3_phymxwrap"; + power-domains = <&ps_atc3_common>, <&ps_atc3_usb_aon>; + status = "disabled"; + }; +}; + +&pmgr_disp { + ps_disp_cpu: power-controller@0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "disp_cpu"; + power-domains = <&ps_disp_fe>; + status = "disabled"; + }; +}; + +&pmgr_gfx { + ps_gpx: power-controller@0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "gpx"; + apple,always-on; + status = "disabled"; + }; +}; + +&pmgr_venc { + ps_venc0_dma: power-controller@0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "venc0_dma"; + power-domains = <&ps_venc0_sys>; + status = "disabled"; + }; + + ps_venc0_pipe4: power-controller@8 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "venc0_pipe4"; + power-domains = <&ps_venc0_dma>; + status = "disabled"; + }; + + ps_venc0_pipe5: power-controller@10 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x10 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "venc0_pipe5"; + power-domains = <&ps_venc0_dma>; + status = "disabled"; + }; + + ps_venc0_me0: power-controller@18 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x18 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "venc0_me0"; + power-domains = <&ps_venc0_dma>; + status = "disabled"; + }; + + ps_venc0_me1: power-controller@20 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x20 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "venc0_me1"; + power-domains = <&ps_venc0_me0>; + status = "disabled"; + }; +}; diff --git a/arch/arm64/boot/dts/apple/t6040.dtsi b/arch/arm64/boot/dts/apple/t6040.dtsi index c054f5accf531..ba79030262ad6 100644 --- a/arch/arm64/boot/dts/apple/t6040.dtsi +++ b/arch/arm64/boot/dts/apple/t6040.dtsi @@ -1030,6 +1030,27 @@ /* child nodes are added in t6040-pmgr.dtsi */ }; + pmgr_ane: power-management@50228c000 { + compatible = "apple,t6041-pmgr", "apple,t8103-pmgr", "syscon", "simple-mfd"; + reg = <0x5 0x0228c000 0x0 0x4000>; + #address-cells = <1>; + #size-cells = <1>; + }; + + pmgr_disp: power-management@502290000 { + compatible = "apple,t6041-pmgr", "apple,t8103-pmgr", "syscon", "simple-mfd"; + reg = <0x5 0x02290000 0x0 0x4000>; + #address-cells = <1>; + #size-cells = <1>; + }; + + pmgr_venc: power-management@502808000 { + compatible = "apple,t6041-pmgr", "apple,t8103-pmgr", "syscon", "simple-mfd"; + reg = <0x5 0x02808000 0x0 0x4000>; + #address-cells = <1>; + #size-cells = <1>; + }; + pmgr_isp: power-management@502284000 { compatible = "apple,t6041-pmgr", "apple,t8103-pmgr", "syscon", "simple-mfd"; #address-cells = <1>; From 7a2b3a73e6f6516cf6bc88add0d0b5fe6a34a363 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Tue, 27 Oct 2026 15:04:10 +1100 Subject: [PATCH 157/184] arm64: dts: apple: describe the remaining T6040 DART banks Describe the separate ANE and AVE translation banks, PCIe port 1, SIO, external displays, scaler, APR and JPEG IOMMUs. Keep each disabled until its DMA consumer owns its power and streams. Shared DAPF and remap apertures are not independent IOMMU instances. Assisted-by: LLM Signed-off-by: Ryan Murray --- arch/arm64/boot/dts/apple/t6040.dtsi | 173 +++++++++++++++++++++++++++ 1 file changed, 173 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t6040.dtsi b/arch/arm64/boot/dts/apple/t6040.dtsi index ba79030262ad6..fb4cde463cc2d 100644 --- a/arch/arm64/boot/dts/apple/t6040.dtsi +++ b/arch/arm64/boot/dts/apple/t6040.dtsi @@ -1808,6 +1808,179 @@ phy-names = "usb2-phy", "usb3-phy"; status = "disabled"; }; + /* Additional DARTs; enabled together with their DMA consumers. */ + ave0_dart1: iommu@283080000 { + compatible = "apple,t6041-dart", "apple,t8110-dart"; + reg = <0x2 0x83080000 0x0 0xc000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_venc0_sys>; + status = "disabled"; + }; + + ave0_dart0: iommu@2830a0000 { + compatible = "apple,t6041-dart", "apple,t8110-dart"; + reg = <0x2 0x830a0000 0x0 0xc000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_venc0_sys>; + status = "disabled"; + }; + + apr0_dart: iommu@301020000 { + compatible = "apple,t6041-dart", "apple,t8110-dart"; + reg = <0x3 0x01020000 0x0 0xc000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_scodec>; + status = "disabled"; + }; + + apciec0_dart: iommu@384000000 { + compatible = "apple,t6041-dart", "apple,t8110-dart"; + reg = <0x3 0x84000000 0x0 0x20000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + status = "disabled"; + }; + + apciec1_dart: iommu@38c000000 { + compatible = "apple,t6041-dart", "apple,t8110-dart"; + reg = <0x3 0x8c000000 0x0 0x20000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + status = "disabled"; + }; + + apciec2_dart: iommu@394000000 { + compatible = "apple,t6041-dart", "apple,t8110-dart"; + reg = <0x3 0x94000000 0x0 0x20000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + status = "disabled"; + }; + + dispext0_dart: iommu@3a2300000 { + compatible = "apple,t6041-dart", "apple,t8110-dart"; + reg = <0x3 0xa2300000 0x0 0xc000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + status = "disabled"; + }; + + dcpext0_dart: iommu@3a2340000 { + compatible = "apple,t6041-dart", "apple,t8110-dart"; + reg = <0x3 0xa2340000 0x0 0x20000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + status = "disabled"; + }; + + apcie1_dart: iommu@411000000 { + compatible = "apple,t6041-dart", "apple,t8110-dart"; + reg = <0x4 0x11000000 0x0 0x20000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + status = "disabled"; + }; + + sio_dart: iommu@429060000 { + compatible = "apple,t6041-dart", "apple,t8110-dart"; + reg = <0x4 0x29060000 0x0 0x20000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_sio>; + status = "disabled"; + }; + + ane0_dart0: iommu@485800000 { + compatible = "apple,t6041-dart", "apple,t8110-dart"; + reg = <0x4 0x85800000 0x0 0xc000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_ane_sys>; + status = "disabled"; + }; + + ane0_dart1: iommu@485820000 { + compatible = "apple,t6041-dart", "apple,t8110-dart"; + reg = <0x4 0x85820000 0x0 0xc000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_ane_sys>; + status = "disabled"; + }; + + ane0_dart2: iommu@485840000 { + compatible = "apple,t6041-dart", "apple,t8110-dart"; + reg = <0x4 0x85840000 0x0 0xc000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_ane_sys>; + status = "disabled"; + }; + + jpeg0_dart: iommu@487020000 { + compatible = "apple,t6041-dart", "apple,t8110-dart"; + reg = <0x4 0x87020000 0x0 0xc000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_jpg>; + status = "disabled"; + }; + + jpeg1_dart: iommu@487080000 { + compatible = "apple,t6041-dart", "apple,t8110-dart"; + reg = <0x4 0x87080000 0x0 0xc000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_jpg>; + status = "disabled"; + }; + + scaler0_dart: iommu@521260000 { + compatible = "apple,t6041-dart", "apple,t8110-dart"; + reg = <0x5 0x21260000 0x0 0xc000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_msr0>; + status = "disabled"; + }; + + dispext1_dart: iommu@602300000 { + compatible = "apple,t6041-dart", "apple,t8110-dart"; + reg = <0x6 0x02300000 0x0 0xc000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + status = "disabled"; + }; + + dcpext1_dart: iommu@602340000 { + compatible = "apple,t6041-dart", "apple,t8110-dart"; + reg = <0x6 0x02340000 0x0 0xc000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + status = "disabled"; + }; + }; #include "t6040-pmgr.dtsi" From 7d819964d0bcf133a082dddc8caba8f09ad361bb Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Tue, 27 Oct 2026 15:06:22 +1100 Subject: [PATCH 158/184] arm64: dts: apple: describe the remaining T6040 GPIO and SPMI controllers Add the AOP and SMC pin banks with the ADT pin counts and interrupt groups, and the four remaining NUB SPMI buses plus the AOP SPMI bus. Leave coprocessor-owned banks disabled until their ownership can be transferred. Assisted-by: LLM Signed-off-by: Ryan Murray --- arch/arm64/boot/dts/apple/t6040.dtsi | 89 ++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t6040.dtsi b/arch/arm64/boot/dts/apple/t6040.dtsi index fb4cde463cc2d..0a2d7104a103b 100644 --- a/arch/arm64/boot/dts/apple/t6040.dtsi +++ b/arch/arm64/boot/dts/apple/t6040.dtsi @@ -1981,6 +1981,95 @@ status = "disabled"; }; + pinctrl_aop: pinctrl@510824000 { + compatible = "apple,t6041-pinctrl", "apple,t8103-pinctrl"; + reg = <0x5 0x10824000 0x0 0x4000>; + clocks = <&clkref>; + gpio-controller; + #gpio-cells = <2>; + gpio-ranges = <&pinctrl_aop 0 0 85>; + apple,npins = <85>; + interrupt-controller; + #interrupt-cells = <2>; + interrupt-parent = <&aic>; + interrupts = , + , + , + , + , + , + ; + /* Coprocessor pins and interrupt groups need an ownership handoff. */ + status = "disabled"; + }; + + pinctrl_smc: pinctrl@50c824000 { + compatible = "apple,t6041-pinctrl", "apple,t8103-pinctrl"; + reg = <0x5 0x0c824000 0x0 0x4000>; + clocks = <&clkref>; + gpio-controller; + #gpio-cells = <2>; + gpio-ranges = <&pinctrl_smc 0 0 24>; + apple,npins = <24>; + interrupt-controller; + #interrupt-cells = <2>; + interrupt-parent = <&aic>; + interrupts = , + , + , + , + , + , + ; + /* Coprocessor pins and interrupt groups need an ownership handoff. */ + status = "disabled"; + }; + + nub_spmi1: spmi@509054000 { + compatible = "apple,t6041-spmi", "apple,t8103-spmi"; + reg = <0x5 0x09054000 0x0 0x100>; + #address-cells = <2>; + #size-cells = <0>; + power-domains = <&ps_nub_spmi1>; + status = "disabled"; + }; + + nub_spmi2: spmi@509094000 { + compatible = "apple,t6041-spmi", "apple,t8103-spmi"; + reg = <0x5 0x09094000 0x0 0x100>; + #address-cells = <2>; + #size-cells = <0>; + power-domains = <&ps_nub_spmi2>; + status = "disabled"; + }; + + nub_spmi3: spmi@5090d4000 { + compatible = "apple,t6041-spmi", "apple,t8103-spmi"; + reg = <0x5 0x090d4000 0x0 0x100>; + #address-cells = <2>; + #size-cells = <0>; + power-domains = <&ps_nub_spmi3>; + status = "disabled"; + }; + + nub_spmi4: spmi@509114000 { + compatible = "apple,t6041-spmi", "apple,t8103-spmi"; + reg = <0x5 0x09114000 0x0 0x100>; + #address-cells = <2>; + #size-cells = <0>; + power-domains = <&ps_nub_spmi4>; + status = "disabled"; + }; + + aop_spmi0: spmi@510914000 { + compatible = "apple,t6041-spmi", "apple,t8103-spmi"; + reg = <0x5 0x10914000 0x0 0x100>; + #address-cells = <2>; + #size-cells = <0>; + /* Shared with AOP firmware; no AP bus ownership handoff yet. */ + status = "disabled"; + }; + }; #include "t6040-pmgr.dtsi" From c5b1a0547e5ab38add5caa5374eb87fcd982edb8 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Tue, 27 Oct 2026 15:06:33 +1100 Subject: [PATCH 159/184] arm64: dts: apple: describe the J616s additional Abbey PMICs Expose the Abbey PMICs at USID 14 on NUB SPMI buses 1 and 2 through the existing NVMEM binding. Enable their bus power states and shared parent. Use polled bus access as on NUB SPMI0 so firmware-owned peripheral interrupts remain untouched. Assisted-by: LLM Signed-off-by: Ryan Murray --- arch/arm64/boot/dts/apple/t6040-j616s.dts | 30 +++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t6040-j616s.dts b/arch/arm64/boot/dts/apple/t6040-j616s.dts index 69a891d7111d5..5fa132fea4fb3 100644 --- a/arch/arm64/boot/dts/apple/t6040-j616s.dts +++ b/arch/arm64/boot/dts/apple/t6040-j616s.dts @@ -4370,3 +4370,33 @@ #include "t6040-j616s-extdisp-mem-loader.dtsi" #include "t6040-j616s-extdisp-hdmi.dtsi" #include "t6040-j616s-extdisp-usbc.dtsi" +/* Additional Abbey PMICs on the two independent NUB SPMI buses. */ +&nub_spmi1 { + status = "okay"; + + pmic@e { + compatible = "apple,abbey-pmic", "apple,spmi-nvmem"; + reg = <0xe SPMI_USID>; + }; +}; + +&nub_spmi2 { + status = "okay"; + + pmic@e { + compatible = "apple,abbey-pmic", "apple,spmi-nvmem"; + reg = <0xe SPMI_USID>; + }; +}; + +&ps_nub_spmi_ahb { + status = "okay"; +}; + +&ps_nub_spmi1 { + status = "okay"; +}; + +&ps_nub_spmi2 { + status = "okay"; +}; From 4766298ee81470cc6284facdb967a2ccd3314f67 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Tue, 27 Oct 2026 15:09:30 +1100 Subject: [PATCH 160/184] arm64: dts: apple: describe T6040 SIO, panel SPI and QSPIMC Describe the SIO DMA coprocessor and its mailbox, panel SPI controller and firmware NOR controller with their register, interrupt, clock and power resources. Extend the SIO and QSPIMC bindings for T6041. Keep them disabled pending firmware-table, panel-ownership and flash-write-policy qualification. Assisted-by: LLM Signed-off-by: Ryan Murray --- .../devicetree/bindings/dma/apple,sio.yaml | 1 + .../devicetree/bindings/spi/apple,qspi.yaml | 1 + arch/arm64/boot/dts/apple/t6040.dtsi | 61 +++++++++++++++++++ 3 files changed, 63 insertions(+) diff --git a/Documentation/devicetree/bindings/dma/apple,sio.yaml b/Documentation/devicetree/bindings/dma/apple,sio.yaml index dbaaa2d3d1a1a..ab6bdf3686971 100644 --- a/Documentation/devicetree/bindings/dma/apple,sio.yaml +++ b/Documentation/devicetree/bindings/dma/apple,sio.yaml @@ -23,6 +23,7 @@ properties: - enum: - apple,t6000-sio - apple,t6030-sio + - apple,t6041-sio - apple,t8103-sio - const: apple,sio diff --git a/Documentation/devicetree/bindings/spi/apple,qspi.yaml b/Documentation/devicetree/bindings/spi/apple,qspi.yaml index 39924afc9a8b6..0b98af1ad68d2 100644 --- a/Documentation/devicetree/bindings/spi/apple,qspi.yaml +++ b/Documentation/devicetree/bindings/spi/apple,qspi.yaml @@ -22,6 +22,7 @@ properties: enum: - apple,t8122-qspi - apple,t8132-qspi + - apple,t6041-qspi - apple,t8140-qspi reg: diff --git a/arch/arm64/boot/dts/apple/t6040.dtsi b/arch/arm64/boot/dts/apple/t6040.dtsi index 0a2d7104a103b..1be0417d0a8b8 100644 --- a/arch/arm64/boot/dts/apple/t6040.dtsi +++ b/arch/arm64/boot/dts/apple/t6040.dtsi @@ -2070,6 +2070,67 @@ status = "disabled"; }; + /* The QSPIMC input is ADT clock 434 (200 MHz). */ + clk_200m: clock-200m { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <200000000>; + clock-output-names = "clk_200m"; + }; + + spi4: spi@429110000 { + compatible = "apple,t6041-spi", "apple,t8103-spi"; + reg = <0x4 0x29110000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + clocks = <&clkref>; + power-domains = <&ps_spi4>; + #address-cells = <1>; + #size-cells = <0>; + /* Panel/TCON control belongs to the display firmware. */ + status = "disabled"; + }; + + qspi: spi@429118000 { + compatible = "apple,t6041-qspi"; + reg = <0x4 0x29118000 0x0 0x4800>; + interrupt-parent = <&aic>; + interrupts = ; + clocks = <&clk_200m>; + power-domains = <&ps_qspi>; + #address-cells = <1>; + #size-cells = <0>; + /* The firmware NOR needs a qualified partition/write policy. */ + status = "disabled"; + }; + + /* The loader supplies the SIO firmware regions and protocol tables. */ + sio: dma-controller@429e00000 { + compatible = "apple,t6041-sio", "apple,sio"; + reg = <0x4 0x29e00000 0x0 0x8000>; + dma-channels = <128>; + #dma-cells = <1>; + mboxes = <&sio_mbox>; + iommus = <&sio_dart 0>; + power-domains = <&ps_sio_cpu>; + status = "disabled"; + }; + + sio_mbox: mailbox@429e08000 { + compatible = "apple,t6041-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x4 0x29e08000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + power-domains = <&ps_sio_cpu>; + status = "disabled"; + }; + }; #include "t6040-pmgr.dtsi" From 6d9b58b2fe74cb67585e34c5253d3fde4b373f73 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Tue, 27 Oct 2026 15:14:05 +1100 Subject: [PATCH 161/184] arm64: dts: apple: describe the T6040 Neural Engine Describe the engine, its interrupt and three DART translation banks. Extend the ANE binding for the single-interrupt T6041 firmware interface and leave the engine disabled until that interface has a Linux backend. Assisted-by: LLM Signed-off-by: Ryan Murray --- .../devicetree/bindings/npu/apple,ane.yaml | 27 ++++++++++++++++--- arch/arm64/boot/dts/apple/t6040.dtsi | 12 +++++++++ 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/Documentation/devicetree/bindings/npu/apple,ane.yaml b/Documentation/devicetree/bindings/npu/apple,ane.yaml index 79cf942654f7b..feae5b133ac2f 100644 --- a/Documentation/devicetree/bindings/npu/apple,ane.yaml +++ b/Documentation/devicetree/bindings/npu/apple,ane.yaml @@ -15,7 +15,9 @@ description: drives the engine through its register window. On T602x and T8112 a coprocessor runs the ANE firmware and the host talks to it through an ASC mailbox. T8140 has two interrupts and one system power domain; its - coprocessor uses a different firmware interface. + coprocessor uses a different firmware interface. T6041 likewise needs a + generation-specific firmware backend; it has one interrupt and its CPU + power state supplies the engine through the ANE system domain. properties: compatible: @@ -25,6 +27,7 @@ properties: - apple,t6020-ane - apple,t6021-ane - apple,t6022-ane + - apple,t6041-ane - apple,t8112-ane - apple,t8140-ane @@ -87,19 +90,35 @@ required: - power-domains allOf: + - if: + properties: + compatible: + const: apple,t8140-ane + then: + properties: + interrupts: + minItems: 2 + - if: + properties: + compatible: + const: apple,t6041-ane + then: + properties: + interrupts: + maxItems: 1 - if: properties: compatible: contains: - const: apple,t8140-ane + enum: + - apple,t8140-ane + - apple,t6041-ane then: properties: reg: maxItems: 1 reg-names: maxItems: 1 - interrupts: - minItems: 2 interrupt-names: false iommus: minItems: 3 diff --git a/arch/arm64/boot/dts/apple/t6040.dtsi b/arch/arm64/boot/dts/apple/t6040.dtsi index 1be0417d0a8b8..dd58eab792838 100644 --- a/arch/arm64/boot/dts/apple/t6040.dtsi +++ b/arch/arm64/boot/dts/apple/t6040.dtsi @@ -2131,6 +2131,18 @@ status = "disabled"; }; + /* Firmware interface support is required before enabling the ANE. */ + ane: ane@484000000 { + compatible = "apple,t6041-ane"; + reg = <0x4 0x84000000 0x0 0x2000000>; + reg-names = "engine"; + interrupt-parent = <&aic>; + interrupts = ; + iommus = <&ane0_dart0 0>, <&ane0_dart1 0>, <&ane0_dart2 0>; + power-domains = <&ps_ane_cpu>; + status = "disabled"; + }; + }; #include "t6040-pmgr.dtsi" From cc9f3a23e3972b456cf5161f74a984f034ff68c8 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Tue, 27 Oct 2026 15:14:14 +1100 Subject: [PATCH 162/184] arm64: dts: apple: describe the T6040 video encoder Describe the codec, mailbox and coprocessor windows, all five interrupts, the five hardware power states and both DART banks using the AVE binding. Keep it disabled until firmware support is available. Assisted-by: LLM Signed-off-by: Ryan Murray --- .../bindings/media/apple,t8140-ave.yaml | 8 +++++--- arch/arm64/boot/dts/apple/t6040.dtsi | 20 +++++++++++++++++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/media/apple,t8140-ave.yaml b/Documentation/devicetree/bindings/media/apple,t8140-ave.yaml index 110ebbc3d09e2..61e10feb5df71 100644 --- a/Documentation/devicetree/bindings/media/apple,t8140-ave.yaml +++ b/Documentation/devicetree/bindings/media/apple,t8140-ave.yaml @@ -4,19 +4,21 @@ $id: http://devicetree.org/schemas/media/apple,t8140-ave.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# -title: Apple T8140 video encoder +title: Apple video encoder maintainers: - Miguel Cruz <7362641+Acelogic@users.noreply.github.com> description: | - The T8140 AVE coprocessor encodes video using firmware preloaded by the + The T6041 and T8140 AVE coprocessors encode video using firmware preloaded by the bootloader. Its two DART instances translate the same stream. The bootloader supplies two reserved firmware regions and their IOVA mappings. properties: compatible: - const: apple,t8140-ave + enum: + - apple,t6041-ave + - apple,t8140-ave reg: items: diff --git a/arch/arm64/boot/dts/apple/t6040.dtsi b/arch/arm64/boot/dts/apple/t6040.dtsi index dd58eab792838..f113708592ddf 100644 --- a/arch/arm64/boot/dts/apple/t6040.dtsi +++ b/arch/arm64/boot/dts/apple/t6040.dtsi @@ -2143,6 +2143,26 @@ status = "disabled"; }; + /* The video encoder stays disabled until its firmware backend is available. */ + ave: video-codec@283100000 { + compatible = "apple,t6041-ave"; + reg = <0x2 0x83100000 0x0 0x45c000>, + <0x2 0x83050000 0x0 0x4000>, + <0x2 0x83800000 0x0 0x800000>; + reg-names = "codec", "mailbox", "coproc"; + interrupt-parent = <&aic>; + interrupts = , + , + , + , + ; + power-domains = <&ps_venc0_dma>, <&ps_venc0_pipe4>, + <&ps_venc0_pipe5>, <&ps_venc0_me0>, <&ps_venc0_me1>; + power-domain-names = "dma", "pipe4", "pipe5", "me0", "me1"; + iommus = <&ave0_dart0 0>, <&ave0_dart1 0>; + status = "disabled"; + }; + }; #include "t6040-pmgr.dtsi" From 8640ed5980bc2373bc822536e3aba88ea049ad5d Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Tue, 27 Oct 2026 15:14:43 +1100 Subject: [PATCH 163/184] arm64: dts: apple: describe the J616s SD reader PCIe port Describe port 1, its interrupt, reset and clock-request pins and its separate DART mapping. Leave the port disabled because its SMC power switch is not a hexadecimal gPxx GPIO and requires a separate service binding. Assisted-by: LLM Signed-off-by: Ryan Murray --- arch/arm64/boot/dts/apple/t6040-j616s.dts | 33 +++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t6040-j616s.dts b/arch/arm64/boot/dts/apple/t6040-j616s.dts index 5fa132fea4fb3..f4e37feca2475 100644 --- a/arch/arm64/boot/dts/apple/t6040-j616s.dts +++ b/arch/arm64/boot/dts/apple/t6040-j616s.dts @@ -4400,3 +4400,36 @@ &ps_nub_spmi2 { status = "okay"; }; + +&pinctrl { + sd_clkreq_pins: sd-clkreq-pins { + pinmux = ; + }; +}; + +&pcie0 { + interrupts = , + ; + bus-range = <0 2>; + iommu-map = <0x100 &pcie_dart 1 2>, <0x200 &apcie1_dart 1 1>; + + port01: pci@1,0 { + device_type = "pci"; + reg = <0x800 0 0 0 0>; + reset-gpios = <&pinctrl 5 GPIO_ACTIVE_LOW>; + pinctrl-names = "default"; + pinctrl-0 = <&sd_clkreq_pins>; + #address-cells = <3>; + #size-cells = <2>; + ranges; + interrupt-controller; + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0 0 0 1 &port01 0 0 0 0>, + <0 0 0 2 &port01 0 0 0 1>, + <0 0 0 3 &port01 0 0 0 2>, + <0 0 0 4 &port01 0 0 0 3>; + /* The SD power switch needs an SMC service binding. */ + status = "disabled"; + }; +}; From a47d8eceb40a8adee654bfd80b98c130f25d7057 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Tue, 27 Oct 2026 15:15:26 +1100 Subject: [PATCH 164/184] arm64: dts: apple: describe the J616s console transports and tether variant Move the UART and DockChannel descriptions into the SoC tree, disabled by default, and provide a tether variant that enables its consoles while leaving the host-owned ATC0 port untouched. Give the normal board the PMP alias needed for its firmware handoff as well. Assisted-by: LLM Signed-off-by: Ryan Murray --- arch/arm64/boot/dts/apple/Makefile | 1 + .../boot/dts/apple/t6040-j616s-tether.dts | 45 +++++++++++++++++++ arch/arm64/boot/dts/apple/t6040-j616s.dts | 1 + arch/arm64/boot/dts/apple/t6040.dtsi | 25 +++++++++++ 4 files changed, 72 insertions(+) create mode 100644 arch/arm64/boot/dts/apple/t6040-j616s-tether.dts diff --git a/arch/arm64/boot/dts/apple/Makefile b/arch/arm64/boot/dts/apple/Makefile index 007e204fdbc56..0f6e6714f7de7 100644 --- a/arch/arm64/boot/dts/apple/Makefile +++ b/arch/arm64/boot/dts/apple/Makefile @@ -95,6 +95,7 @@ dtb-$(CONFIG_ARCH_APPLE) += t6032-j575d.dtb dtb-$(CONFIG_ARCH_APPLE) += t6034-j514m.dtb dtb-$(CONFIG_ARCH_APPLE) += t6034-j516m.dtb dtb-$(CONFIG_ARCH_APPLE) += t6040-j616s.dtb +dtb-$(CONFIG_ARCH_APPLE) += t6040-j616s-tether.dtb dtb-$(CONFIG_ARCH_APPLE) += t8112-j413.dtb dtb-$(CONFIG_ARCH_APPLE) += t8112-j415.dtb dtb-$(CONFIG_ARCH_APPLE) += t8112-j473.dtb diff --git a/arch/arm64/boot/dts/apple/t6040-j616s-tether.dts b/arch/arm64/boot/dts/apple/t6040-j616s-tether.dts new file mode 100644 index 0000000000000..619e09a88d091 --- /dev/null +++ b/arch/arm64/boot/dts/apple/t6040-j616s-tether.dts @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* J616s tether console; the host owns ATC0 during the debug session. */ +#include "t6040-j616s.dts" + +/ { + aliases { + serial0 = &serial0; + }; +}; + +&serial0 { + status = "okay"; +}; + +&ps_uart0 { + status = "okay"; +}; + +&dcuart { + status = "okay"; +}; + +&hpm0 { + status = "disabled"; +}; + +&atcphy0 { + status = "disabled"; +}; + +&atcphy0_mbox { + status = "disabled"; +}; + +&usb_drd0 { + status = "disabled"; +}; + +&usb_drd0_dart_0 { + status = "disabled"; +}; + +&usb_drd0_dart_1 { + status = "disabled"; +}; diff --git a/arch/arm64/boot/dts/apple/t6040-j616s.dts b/arch/arm64/boot/dts/apple/t6040-j616s.dts index f4e37feca2475..d9db0cb08d603 100644 --- a/arch/arm64/boot/dts/apple/t6040-j616s.dts +++ b/arch/arm64/boot/dts/apple/t6040-j616s.dts @@ -29,6 +29,7 @@ disp0-piodma = &disp0_piodma; /* the boot loader finds the GPU node through this alias */ gpu = &gpu; + pmp = &pmp; /* the boot loader reserves the SEP firmware through this alias */ sep = &sep; }; diff --git a/arch/arm64/boot/dts/apple/t6040.dtsi b/arch/arm64/boot/dts/apple/t6040.dtsi index f113708592ddf..74ac312cdf8d6 100644 --- a/arch/arm64/boot/dts/apple/t6040.dtsi +++ b/arch/arm64/boot/dts/apple/t6040.dtsi @@ -2163,6 +2163,31 @@ status = "disabled"; }; + serial0: serial@429200000 { + compatible = "apple,s5l-uart"; + reg = <0x4 0x29200000 0x0 0x4000>; + reg-io-width = <4>; + interrupt-parent = <&aic>; + interrupts = ; + clocks = <&clkref>, <&clkref>; + clock-names = "uart", "clk_uart_baud0"; + power-domains = <&ps_uart0>; + status = "disabled"; + }; + + dcuart: serial@50883c000 { + compatible = "apple,dockchannel-uart"; + reg = <0x5 0x0883c000 0x0 0x4000>, + <0x5 0x08838000 0x0 0x4000>, + <0x5 0x0880c000 0x0 0x4000>; + reg-names = "data", "config", "irq"; + interrupt-parent = <&aic>; + interrupts = ; + apple,channel = <2>; + apple,sw-drain; + status = "disabled"; + }; + }; #include "t6040-pmgr.dtsi" From 01ac06477cbdb05210bbd446b45c8dee6800d2de Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Tue, 27 Oct 2026 15:19:09 +1100 Subject: [PATCH 165/184] arm64: dts: apple: complete J616s console supply and fixed-PHY constraints Enable the UART supply parent with the tether UART and omit USB reset-provider cells on the fixed DisplayPort PHY, as required by its binding. Assisted-by: LLM Signed-off-by: Ryan Murray --- arch/arm64/boot/dts/apple/t6040-j616s-tether.dts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t6040-j616s-tether.dts b/arch/arm64/boot/dts/apple/t6040-j616s-tether.dts index 619e09a88d091..8027360847204 100644 --- a/arch/arm64/boot/dts/apple/t6040-j616s-tether.dts +++ b/arch/arm64/boot/dts/apple/t6040-j616s-tether.dts @@ -12,6 +12,10 @@ status = "okay"; }; +&ps_uart_p { + status = "okay"; +}; + &ps_uart0 { status = "okay"; }; From 422be5b4013e60eb05d0c290c76df85729faf7b3 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Tue, 27 Oct 2026 15:20:29 +1100 Subject: [PATCH 166/184] arm64: dts: apple: describe the T6040 ACIO mailboxes and DARTs Describe all three USB4 coprocessor mailboxes and their NHI translation banks with their interrupt and power resources. Keep them disabled until the host-router reset, SRAM and stream handoff is qualified. Assisted-by: LLM Signed-off-by: Ryan Murray --- arch/arm64/boot/dts/apple/t6040.dtsi | 75 ++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t6040.dtsi b/arch/arm64/boot/dts/apple/t6040.dtsi index 74ac312cdf8d6..5c833124a04f0 100644 --- a/arch/arm64/boot/dts/apple/t6040.dtsi +++ b/arch/arm64/boot/dts/apple/t6040.dtsi @@ -2188,6 +2188,81 @@ status = "disabled"; }; + /* ACIO 0 remains disabled until its host-router handoff is supported. */ + usb4_0_mbox: mailbox@381108000 { + compatible = "apple,t6041-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x3 0x81108000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + power-domains = <&ps_atc0_cio>; + status = "disabled"; + }; + + usb4_0_dart: iommu@381a80000 { + compatible = "apple,t6041-dart", "apple,t8110-dart"; + reg = <0x3 0x81a80000 0x0 0xc000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + status = "disabled"; + }; + + /* ACIO 1 remains disabled until its host-router handoff is supported. */ + usb4_1_mbox: mailbox@389108000 { + compatible = "apple,t6041-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x3 0x89108000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + power-domains = <&ps_atc1_cio>; + status = "disabled"; + }; + + usb4_1_dart: iommu@389a80000 { + compatible = "apple,t6041-dart", "apple,t8110-dart"; + reg = <0x3 0x89a80000 0x0 0xc000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + status = "disabled"; + }; + + /* ACIO 2 remains disabled until its host-router handoff is supported. */ + usb4_2_mbox: mailbox@391108000 { + compatible = "apple,t6041-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x3 0x91108000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + power-domains = <&ps_atc2_cio>; + status = "disabled"; + }; + + usb4_2_dart: iommu@391a80000 { + compatible = "apple,t6041-dart", "apple,t8110-dart"; + reg = <0x3 0x91a80000 0x0 0xc000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + status = "disabled"; + }; + }; #include "t6040-pmgr.dtsi" From 0f12f5891a36c8ed4ac6674db6d0e9353d40a263 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Tue, 27 Oct 2026 15:26:18 +1100 Subject: [PATCH 167/184] arm64: dts: apple: complete active T6040 power-state parent links Connect the three ATC common states, AVD, PMP and PMS SRAM to their ADT hardware parents. Enable the two missing fabric providers, both marked critical by the ADT, so active consumers never depend on disabled suppliers. Retain the existing additional PHY wrapper ordering dependencies. Assisted-by: LLM Signed-off-by: Ryan Murray --- arch/arm64/boot/dts/apple/t6040-pmgr.dtsi | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/apple/t6040-pmgr.dtsi b/arch/arm64/boot/dts/apple/t6040-pmgr.dtsi index 18ec5df7d33bf..eab3825a95c26 100644 --- a/arch/arm64/boot/dts/apple/t6040-pmgr.dtsi +++ b/arch/arm64/boot/dts/apple/t6040-pmgr.dtsi @@ -297,6 +297,7 @@ #power-domain-cells = <0>; #reset-cells = <0>; label = "avd_sys"; + power-domains = <&ps_fab0_soc>; }; ps_afc: power-controller@178 { @@ -315,6 +316,7 @@ #reset-cells = <0>; label = "pmp"; apple,always-on; + power-domains = <&ps_pms>; }; ps_pms_sram: power-controller@418 { @@ -324,6 +326,7 @@ #reset-cells = <0>; label = "pms_sram"; apple,always-on; + power-domains = <&ps_pms>; }; ps_fab6_soc: power-controller@480 { @@ -358,6 +361,7 @@ #reset-cells = <0>; label = "atc0_common"; apple,always-on; + power-domains = <&ps_fab2_soc>; }; ps_atc1_common: power-controller@3b8 { @@ -367,6 +371,7 @@ #reset-cells = <0>; label = "atc1_common"; apple,always-on; + power-domains = <&ps_fab2_soc>; }; ps_atc2_common: power-controller@3e0 { @@ -376,6 +381,7 @@ #reset-cells = <0>; label = "atc2_common"; apple,always-on; + power-domains = <&ps_fab2_soc>; }; }; @@ -1380,7 +1386,6 @@ label = "fab0_soc"; power-domains = <&ps_afi>; apple,always-on; - status = "disabled"; }; ps_amcc0: power-controller@180 { @@ -1648,7 +1653,6 @@ label = "fab2_soc"; power-domains = <&ps_afi>; apple,always-on; - status = "disabled"; }; ps_dispext0_sys: power-controller@340 { From cb347bca1a0a2e4a6ef205ed3bf6827a156452a1 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Tue, 27 Oct 2026 15:56:32 +1100 Subject: [PATCH 168/184] arm64: dts: apple: keep T6041 SIO on its own firmware compatible The T6041 firmware protocol has not been qualified against the legacy SIO backend. Describe its resources with a generation-specific compatible, so a loader cannot accidentally bind that backend by enabling the node. Assisted-by: LLM Signed-off-by: Ryan Murray --- .../devicetree/bindings/dma/apple,sio.yaml | 16 +++++++++------- arch/arm64/boot/dts/apple/t6040.dtsi | 2 +- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/Documentation/devicetree/bindings/dma/apple,sio.yaml b/Documentation/devicetree/bindings/dma/apple,sio.yaml index ab6bdf3686971..495adc52c7e6e 100644 --- a/Documentation/devicetree/bindings/dma/apple,sio.yaml +++ b/Documentation/devicetree/bindings/dma/apple,sio.yaml @@ -19,13 +19,15 @@ allOf: properties: compatible: - items: - - enum: - - apple,t6000-sio - - apple,t6030-sio - - apple,t6041-sio - - apple,t8103-sio - - const: apple,sio + oneOf: + - items: + - enum: + - apple,t6000-sio + - apple,t6030-sio + - apple,t8103-sio + - const: apple,sio + - description: T6041 firmware requires a generation-specific backend. + const: apple,t6041-sio reg: maxItems: 1 diff --git a/arch/arm64/boot/dts/apple/t6040.dtsi b/arch/arm64/boot/dts/apple/t6040.dtsi index 5c833124a04f0..77c74accdaa35 100644 --- a/arch/arm64/boot/dts/apple/t6040.dtsi +++ b/arch/arm64/boot/dts/apple/t6040.dtsi @@ -2106,7 +2106,7 @@ /* The loader supplies the SIO firmware regions and protocol tables. */ sio: dma-controller@429e00000 { - compatible = "apple,t6041-sio", "apple,sio"; + compatible = "apple,t6041-sio"; reg = <0x4 0x29e00000 0x0 0x8000>; dma-channels = <128>; #dma-cells = <1>; From 608c9f78845a6613a90b28ebf9a582469f2a8e1a Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Tue, 27 Oct 2026 16:14:43 +1100 Subject: [PATCH 169/184] arm64: dts: apple: retain the active J616s PCIe scan range Keep the existing host bus range while the SD port is disabled. Generic ECAM access does not filter unavailable OF ports, so describing that port must not expand downstream configuration probing before its power handoff is qualified. Assisted-by: LLM Signed-off-by: Ryan Murray --- arch/arm64/boot/dts/apple/t6040-j616s.dts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/apple/t6040-j616s.dts b/arch/arm64/boot/dts/apple/t6040-j616s.dts index d9db0cb08d603..933edf6ff2d63 100644 --- a/arch/arm64/boot/dts/apple/t6040-j616s.dts +++ b/arch/arm64/boot/dts/apple/t6040-j616s.dts @@ -4411,7 +4411,7 @@ &pcie0 { interrupts = , ; - bus-range = <0 2>; + /* Widen bus-range only with the SD power/port ownership handoff. */ iommu-map = <0x100 &pcie_dart 1 2>, <0x200 &apcie1_dart 1 1>; port01: pci@1,0 { From 40b5c45bd09d3375990e4d5bb943418107ea9498 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Tue, 27 Oct 2026 17:45:07 +1100 Subject: [PATCH 170/184] ASoC: apple: macaudio: expose speaker variant straps from the device tree Allow a machine card to describe speaker identity inputs with speaker-id-gpios, least significant bit first. Expose their logical levels in a read-only volatile Speaker Variant ID control. Cards without the property retain their existing controls. Describe the two J616s speaker variant inputs on AP GPIO 52 and 53. Assisted-by: LLM Signed-off-by: Ryan Murray --- .../bindings/sound/apple,macaudio.yaml | 9 +++ arch/arm64/boot/dts/apple/t6040-j616s.dts | 2 + sound/soc/apple/macaudio.c | 60 +++++++++++++++++++ 3 files changed, 71 insertions(+) diff --git a/Documentation/devicetree/bindings/sound/apple,macaudio.yaml b/Documentation/devicetree/bindings/sound/apple,macaudio.yaml index fdf3d1883a99d..755c5d195cf0f 100644 --- a/Documentation/devicetree/bindings/sound/apple,macaudio.yaml +++ b/Documentation/devicetree/bindings/sound/apple,macaudio.yaml @@ -36,6 +36,15 @@ properties: Model name for presentation to users $ref: /schemas/types.yaml#/definitions/string + speaker-id-gpios: + minItems: 1 + maxItems: 8 + description: + Inputs encoding the fitted speaker assembly variant, least significant + bit first. The machine driver exposes their logical levels through the + read-only Speaker Variant ID control. GPIO polarity flags define each + bit's logical level. + patternProperties: "^dai-link(@[0-9a-f]+)?$": description: | diff --git a/arch/arm64/boot/dts/apple/t6040-j616s.dts b/arch/arm64/boot/dts/apple/t6040-j616s.dts index 933edf6ff2d63..3995f5d4db16a 100644 --- a/arch/arm64/boot/dts/apple/t6040-j616s.dts +++ b/arch/arm64/boot/dts/apple/t6040-j616s.dts @@ -1268,6 +1268,8 @@ sound: sound { compatible = "apple,j616-macaudio", "apple,macaudio"; model = "MacBook Pro J616"; + speaker-id-gpios = <&pinctrl 52 GPIO_ACTIVE_HIGH>, + <&pinctrl 53 GPIO_ACTIVE_HIGH>; #address-cells = <1>; #size-cells = <0>; diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c index cc1123e10f398..b5511ed056887 100644 --- a/sound/soc/apple/macaudio.c +++ b/sound/soc/apple/macaudio.c @@ -24,6 +24,7 @@ #include #include +#include #include #include #include @@ -62,6 +63,7 @@ #define MACAUDIO_NUM_FE_LINKS 3 /* milliseconds */ #define SPEAKER_LOCK_TIMEOUT 250 +#define MACAUDIO_MAX_SPEAKER_ID_GPIOS 8 enum macaudio_amp_type { AMP_NONE, @@ -166,6 +168,7 @@ struct macaudio_snd_data { bool has_sense; bool has_safety; unsigned int max_channels; + struct gpio_descs *speaker_id_gpios; struct macaudio_link_props { /* frontend props */ @@ -1430,6 +1433,50 @@ static const struct snd_soc_jack_pin macaudio_jack_pins[] = { }, }; +static int macaudio_speaker_id_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = BIT(ma->speaker_id_gpios->ndescs) - 1; + uinfo->value.integer.step = 1; + + return 0; +} + +static int macaudio_speaker_id_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); + unsigned int value = 0; + unsigned int i; + int ret; + + for (i = 0; i < ma->speaker_id_gpios->ndescs; i++) { + ret = gpiod_get_value_cansleep(ma->speaker_id_gpios->desc[i]); + if (ret < 0) + return ret; + if (ret) + value |= BIT(i); + } + uvalue->value.integer.value[0] = value; + + return 0; +} + +static const struct snd_kcontrol_new macaudio_speaker_id_control = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Speaker Variant ID", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = macaudio_speaker_id_info, + .get = macaudio_speaker_id_get, +}; + static int macaudio_probe(struct snd_soc_card *card) { struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); @@ -1460,6 +1507,9 @@ static int macaudio_probe(struct snd_soc_card *card) snd_jack_set_key(ma->jack.jack, SND_JACK_BTN_2, KEY_VOLUMEDOWN); snd_jack_set_key(ma->jack.jack, SND_JACK_BTN_3, KEY_VOICECOMMAND); + if (ma->speaker_id_gpios) + return snd_soc_add_card_controls(card, &macaudio_speaker_id_control, 1); + return ret; } @@ -2183,6 +2233,16 @@ static int macaudio_snd_platform_probe(struct platform_device *pdev) if (ret) return ret; + data->speaker_id_gpios = devm_gpiod_get_array_optional(dev, "speaker-id", + GPIOD_IN); + if (IS_ERR(data->speaker_id_gpios)) + return dev_err_probe(dev, PTR_ERR(data->speaker_id_gpios), + "Failed to get speaker ID inputs\n"); + if (data->speaker_id_gpios && + (!data->speaker_id_gpios->ndescs || + data->speaker_id_gpios->ndescs > MACAUDIO_MAX_SPEAKER_ID_GPIOS)) + return dev_err_probe(dev, -EINVAL, "Invalid speaker ID input count\n"); + /* * Without sense the PCM2 link is gone, and a card whose front-ends * are not the MCA's (the J616 jack) has no PCM2 RX widget at all. From 6e7b99dc0bbac333b8fd640568e0715ad3bcb2a8 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Tue, 27 Oct 2026 18:12:58 +1100 Subject: [PATCH 171/184] ASoC: apple: macaudio: append variant identity after existing controls Register the optional read-only identity control in late_probe, after the existing card and codec controls have been created. Their numeric identifiers remain stable when a board gains speaker-id-gpios. Assisted-by: LLM Signed-off-by: Ryan Murray --- sound/soc/apple/macaudio.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c index b5511ed056887..96cd57aae29a8 100644 --- a/sound/soc/apple/macaudio.c +++ b/sound/soc/apple/macaudio.c @@ -1507,9 +1507,6 @@ static int macaudio_probe(struct snd_soc_card *card) snd_jack_set_key(ma->jack.jack, SND_JACK_BTN_2, KEY_VOLUMEDOWN); snd_jack_set_key(ma->jack.jack, SND_JACK_BTN_3, KEY_VOICECOMMAND); - if (ma->speaker_id_gpios) - return snd_soc_add_card_controls(card, &macaudio_speaker_id_control, 1); - return ret; } @@ -1634,6 +1631,13 @@ static int macaudio_late_probe(struct snd_soc_card *card) mutex_unlock(&ma->volume_lock_mutex); } + /* Append identity metadata without renumbering the existing controls. */ + if (ma->speaker_id_gpios) { + ret = snd_soc_add_card_controls(card, &macaudio_speaker_id_control, 1); + if (ret) + return ret; + } + /* The notifier walks the card's controls: only while the card is bound */ return register_pm_notifier(&ma->pm_nb); } From d34a031e71dab2b5373270ef5133295a8221881b Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Tue, 27 Oct 2026 18:46:20 +1100 Subject: [PATCH 172/184] arm64: dts: apple: retain the enabled T6040 SEP power suppliers The complete power-state table and the SEP series both describe SPI_P, SPI2 and SEP. Replaying their individual patches during a rebase leaves duplicate nodes whose later disabled status overrides the enabled suppliers required by the SPI2 fingerprint transport and SEP. Keep one definition of each physical power state and retain the enabled SEP-series definitions, as in the previously integrated tree. This also restores the active-provider invariant checked against the compiled DT. Assisted-by: LLM Signed-off-by: Ryan Murray --- arch/arm64/boot/dts/apple/t6040-pmgr.dtsi | 28 ----------------------- 1 file changed, 28 deletions(-) diff --git a/arch/arm64/boot/dts/apple/t6040-pmgr.dtsi b/arch/arm64/boot/dts/apple/t6040-pmgr.dtsi index eab3825a95c26..e0af3e1903cfb 100644 --- a/arch/arm64/boot/dts/apple/t6040-pmgr.dtsi +++ b/arch/arm64/boot/dts/apple/t6040-pmgr.dtsi @@ -600,15 +600,6 @@ status = "disabled"; }; - ps_spi_p: power-controller@198 { - compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; - reg = <0x198 4>; - #power-domain-cells = <0>; - #reset-cells = <0>; - label = "spi_p"; - power-domains = <&ps_sio>; - status = "disabled"; - }; ps_uart_p: power-controller@1a0 { compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; @@ -662,15 +653,6 @@ status = "disabled"; }; - ps_spi2: power-controller@1c8 { - compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; - reg = <0x1c8 4>; - #power-domain-cells = <0>; - #reset-cells = <0>; - label = "spi2"; - power-domains = <&ps_spi_p>; - status = "disabled"; - }; ps_spi3: power-controller@1d0 { compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; @@ -1078,16 +1060,6 @@ status = "disabled"; }; - ps_sep: power-controller@c00 { - compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; - reg = <0xc00 4>; - #power-domain-cells = <0>; - #reset-cells = <0>; - label = "sep"; - power-domains = <&ps_fab3_soc>; - apple,always-on; - status = "disabled"; - }; }; &pmgr_aux { From e1a25b5e952e52ceb9343a03c88ebd5a2e980f48 Mon Sep 17 00:00:00 2001 From: Ryan Murray <81541646+RyanTheTide@users.noreply.github.com> Date: Tue, 27 Oct 2026 20:50:57 +1100 Subject: [PATCH 173/184] selftests: apple-dcp: initialize Type-C retry fixture state The synthetic retry fixture left boolean state uninitialized, which let sanitizer runs fail before exercising the intended budget. Start it from the same zero state as the other fixture cases. Assisted-by: LLM Signed-off-by: Ryan Murray --- tools/testing/selftests/drivers/gpu/apple-dcp/pm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/pm.py b/tools/testing/selftests/drivers/gpu/apple-dcp/pm.py index d5b3890d30861..664ba046dad35 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/pm.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/pm.py @@ -547,7 +547,7 @@ def function(text, name): int main(void) { (void)cd321x_pm_notifications; struct cd321x c; - struct cd321x_pm_state budget; + struct cd321x_pm_state budget={0}; cd321x_pm_init(&budget);assert(cd321x_pm_ready(&budget)); assert(cd321x_display_taken(-ENOSPC,true)&&!cd321x_display_taken(-EBUSY,true)&&!cd321x_display_taken(0,true)&&cd321x_display_taken(-EBUSY,false)&&!cd321x_display_taken(-ENOSPC,false)&&!cd321x_display_taken(0,false)); for(unsigned int i=0;i Date: Tue, 27 Oct 2026 23:06:12 +1100 Subject: [PATCH 174/184] phy: apple: t6040: support two-lane DP pin D beside USB3 --- drivers/phy/apple/atc-t6040-dp2-data.h | 462 +++++++++++++++++++++++++ drivers/phy/apple/t6040-usb2.c | 104 ++++-- 2 files changed, 541 insertions(+), 25 deletions(-) create mode 100644 drivers/phy/apple/atc-t6040-dp2-data.h diff --git a/drivers/phy/apple/atc-t6040-dp2-data.h b/drivers/phy/apple/atc-t6040-dp2-data.h new file mode 100644 index 0000000000000..c76fbe14d5ac3 --- /dev/null +++ b/drivers/phy/apple/atc-t6040-dp2-data.h @@ -0,0 +1,462 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Two-lane DisplayPort link sequences for the T6040 ATC core. + * From Dj's public T6050 combo PHY tables, licensed GPL-2.0-only OR MIT: + * https://github.com/aurora-silicon/linux/pull/480 + * The common ATC banks and two USB3/DP lane groups match the T6040 core. + */ +static const struct atc_op dp_group0_off[] = { + { ATC_MASK, 39, 0x14, 0x1007, 0x7, 0 }, + { ATC_MASK, 39, 0x18, 0x1, 0x0, 0 }, + { ATC_POLL, 39, 0x1c, 0x1, 0x0, 5000 }, + { ATC_MASK, 14, 0x0, 0x1, 0x1, 0 }, + { ATC_MASK, 22, 0x1c, 0xc00000, 0xc00000, 0 }, + { ATC_MASK, 22, 0x10, 0x600, 0x400, 0 }, + { ATC_MASK, 22, 0x4, 0x30000000, 0x20000000, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 22, 0x44, 0x100000, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 22, 0x44, 0x400000, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 22, 0x44, 0x4000000, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 22, 0x44, 0x1000000, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 22, 0x44, 0x1000, 0x0, 0 }, + { ATC_MASK, 2, 0x120, 0x100, 0x100, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x120, 0x1, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x120, 0x10, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 19, 0x44, 0x6000000, 0x4000000, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 19, 0x44, 0x18000000, 0x10000000, 0 }, + { ATC_MASK, 19, 0x24, 0xc0000000, 0x80000000, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 19, 0x24, 0x30000000, 0x20000000, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 19, 0x28, 0x10, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 19, 0x40, 0xc000, 0xc000, 0 }, + { ATC_MASK, 16, 0x1d0, 0x3, 0x1, 0 }, + { ATC_MASK, 19, 0x40, 0x10000, 0x0, 0 }, + { ATC_MASK, 2, 0xc, 0x4000, 0x4000, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0xc, 0x40, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0xc, 0x400, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x10, 0x1, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x10, 0x10, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 14, 0x0, 0xc, 0x8, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x160, 0x40000, 0x40000, 0 }, + { ATC_MASK, 3, 0x8, 0x1, 0x1, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x160, 0x80000, 0x0, 0 }, + { ATC_MASK, 3, 0x8, 0x2, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x160, 0x80000, 0x80000, 0 }, + { ATC_MASK, 3, 0x8, 0x2, 0x2, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x160, 0x40000, 0x0, 0 }, + { ATC_MASK, 3, 0x8, 0x1, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 6, 0x1c, 0x7, 0x1, 0 }, + { ATC_MASK, 6, 0x1c, 0x8, 0x8, 0 }, + { ATC_POLL, 6, 0x2c, 0xff00, 0x100, 500000 }, + { ATC_MASK, 6, 0x1c, 0x8, 0x0, 0 }, + { ATC_POLL, 6, 0x2c, 0xff00, 0x0, 500000 }, + { ATC_POLL, 14, 0x34, 0x8, 0x0, 500000 }, + { ATC_DELAY, 0, 0x0, 0x0, 0xa, 0 }, + { ATC_MASK, 6, 0x34, 0xf, 0x1, 0 }, + { ATC_MASK, 6, 0x1c, 0x7, 0x0, 0 }, + { ATC_MASK, 6, 0x1c, 0x8, 0x8, 0 }, + { ATC_POLL, 6, 0x2c, 0xff00, 0x100, 500000 }, + { ATC_MASK, 6, 0x1c, 0x8, 0x0, 0 }, + { ATC_POLL, 6, 0x2c, 0xff00, 0x0, 500000 }, + { ATC_MASK, 8, 0x0, 0xaaa, 0x0, 0 }, +}; + +static const struct atc_rate_op dp_group0_on[] = { + { { ATC_MASK, 39, 0x14, 0x7, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 39, 0x18, 0x1, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0x6c, 0x1c, 0, 0 }, 0xf, { 0x8, 0x8, 0x8, 0x8 } }, + { { ATC_MASK, 14, 0x0, 0x40fff0, 0, 0 }, 0xf, { 0x1110, 0x1110, 0x1110, 0x1110 } }, + { { ATC_MASK, 8, 0x34, 0x3, 0, 0 }, 0x1, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x80, 0xffffffff, 0, 0 }, 0x1, { 0x1e0e021c, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x84, 0xfffffff, 0, 0 }, 0x1, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x88, 0x7fffff, 0, 0 }, 0x1, { 0x156600, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 8, 0x8, 0x1f0000, 0, 0 }, 0x1, { 0x70000, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 8, 0x18, 0x1, 0, 0 }, 0x1, { 0x1, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 8, 0x34, 0x3, 0, 0 }, 0x2, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x80, 0xffffffff, 0, 0 }, 0x2, { 0x0, 0x1e0e01c2, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x84, 0xfffffff, 0, 0 }, 0x2, { 0x0, 0x7fffffe, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x88, 0x7fffff, 0, 0 }, 0x2, { 0x0, 0x155200, 0x0, 0x0 } }, + { { ATC_MASK, 8, 0x8, 0x1f0000, 0, 0 }, 0x2, { 0x0, 0x70000, 0x0, 0x0 } }, + { { ATC_MASK, 8, 0x18, 0x1, 0, 0 }, 0x2, { 0x0, 0x1, 0x0, 0x0 } }, + { { ATC_MASK, 8, 0x34, 0x3, 0, 0 }, 0x4, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x80, 0xffffffff, 0, 0 }, 0x4, { 0x0, 0x0, 0x1e0e01c2, 0x0 } }, + { { ATC_MASK, 7, 0x84, 0xfffffff, 0, 0 }, 0x4, { 0x0, 0x0, 0x7fffffe, 0x0 } }, + { { ATC_MASK, 7, 0x88, 0x7fffff, 0, 0 }, 0x4, { 0x0, 0x0, 0x554800, 0x0 } }, + { { ATC_MASK, 8, 0x8, 0x1f0000, 0, 0 }, 0x4, { 0x0, 0x0, 0x70000, 0x0 } }, + { { ATC_MASK, 8, 0x18, 0x1, 0, 0 }, 0x4, { 0x0, 0x0, 0x1, 0x0 } }, + { { ATC_MASK, 8, 0x34, 0x3, 0, 0 }, 0x8, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x80, 0xffffffff, 0, 0 }, 0x8, { 0x0, 0x0, 0x0, 0x1e0e02a3 } }, + { { ATC_MASK, 7, 0x84, 0xfffffff, 0, 0 }, 0x8, { 0x0, 0x0, 0x0, 0xbff7ffc } }, + { { ATC_MASK, 7, 0x88, 0x7fffff, 0, 0 }, 0x8, { 0x0, 0x0, 0x0, 0x564800 } }, + { { ATC_MASK, 8, 0x8, 0x1f0000, 0, 0 }, 0x8, { 0x0, 0x0, 0x0, 0x70000 } }, + { { ATC_MASK, 8, 0x18, 0x1, 0, 0 }, 0x8, { 0x0, 0x0, 0x0, 0x1 } }, + { { ATC_MASK, 8, 0x0, 0xaaa, 0, 0 }, 0xf, { 0xaaa, 0xaaa, 0xaaa, 0xaaa } }, + { { ATC_MASK, 6, 0x30, 0x4000, 0, 0 }, 0xf, { 0x4000, 0x4000, 0x4000, 0x4000 } }, + { { ATC_MASK, 6, 0x34, 0xf, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 6, 0x18, 0x1fe000, 0, 0 }, 0xf, { 0x2000, 0x2000, 0x2000, 0x2000 } }, + { { ATC_MASK, 6, 0x20, 0x1, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 6, 0x1c, 0x7, 0, 0 }, 0xf, { 0x7, 0x7, 0x7, 0x7 } }, + { { ATC_MASK, 6, 0x1c, 0x8, 0, 0 }, 0xf, { 0x8, 0x8, 0x8, 0x8 } }, + { { ATC_POLL, 6, 0x2c, 0xff00, 0, 500000 }, 0xf, { 0x100, 0x100, 0x100, 0x100 } }, + { { ATC_MASK, 6, 0x1c, 0x8, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_POLL, 6, 0x2c, 0xff00, 0, 500000 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_POLL, 14, 0x34, 0x8, 0, 500000 }, 0xf, { 0x8, 0x8, 0x8, 0x8 } }, + { { ATC_MASK, 19, 0x3c, 0x18000, 0, 0 }, 0xf, { 0x10000, 0x10000, 0x10000, 0x10000 } }, + { { ATC_MASK, 14, 0x0, 0xc, 0, 0 }, 0xf, { 0xc, 0xc, 0xc, 0xc } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0x120, 0x10, 0, 0 }, 0xf, { 0x10, 0x10, 0x10, 0x10 } }, + { { ATC_MASK, 2, 0x120, 0x40, 0, 0 }, 0xf, { 0x40, 0x40, 0x40, 0x40 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0x120, 0x1, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0x120, 0x4, 0, 0 }, 0xf, { 0x4, 0x4, 0x4, 0x4 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0x120, 0x100, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 2, 0x120, 0x400, 0, 0 }, 0xf, { 0x400, 0x400, 0x400, 0x400 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 22, 0x44, 0xfffffc0, 0, 0 }, 0xf, { 0xaafb800, 0xaafb800, 0xaafb800, 0xaafb800 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 22, 0x44, 0x1000000, 0, 0 }, 0xf, { 0x1000000, 0x1000000, 0x1000000, 0x1000000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 22, 0x44, 0x4000000, 0, 0 }, 0xf, { 0x4000000, 0x4000000, 0x4000000, 0x4000000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 22, 0x44, 0x7c0, 0, 0 }, 0xf, { 0x300, 0x300, 0x300, 0x300 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 22, 0x44, 0x6c000, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 21, 0x100, 0x100000, 0, 0 }, 0xf, { 0x100000, 0x100000, 0x100000, 0x100000 } }, + { { ATC_MASK, 21, 0x58, 0xc00f, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 21, 0x104, 0x20000000, 0, 0 }, 0xf, { 0x20000000, 0x20000000, 0x20000000, 0x20000000 } }, + { { ATC_MASK, 21, 0x100, 0x7ffff, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 22, 0x44, 0x100000, 0, 0 }, 0x7, { 0x100000, 0x100000, 0x100000, 0x0 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0x7, { 0x1, 0x1, 0x1, 0x0 } }, + { { ATC_MASK, 22, 0x44, 0x400000, 0, 0 }, 0x7, { 0x400000, 0x400000, 0x400000, 0x0 } }, + { { ATC_MASK, 22, 0x4, 0x3000000, 0, 0 }, 0xf, { 0x2000000, 0x2000000, 0x2000000, 0x2000000 } }, + { { ATC_MASK, 22, 0x4, 0x30000000, 0, 0 }, 0xf, { 0x30000000, 0x30000000, 0x30000000, 0x30000000 } }, + { { ATC_MASK, 22, 0xc, 0x7000000, 0, 0 }, 0x1, { 0x5000000, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 22, 0xc, 0x7000000, 0, 0 }, 0xe, { 0x0, 0x4000000, 0x4000000, 0x4000000 } }, + { { ATC_MASK, 22, 0x8, 0x3, 0, 0 }, 0xf, { 0x2, 0x2, 0x2, 0x2 } }, + { { ATC_MASK, 22, 0x10, 0x600, 0, 0 }, 0xf, { 0x600, 0x600, 0x600, 0x600 } }, + { { ATC_MASK, 22, 0x1c, 0xc00000, 0, 0 }, 0xf, { 0x800000, 0x800000, 0x800000, 0x800000 } }, + { { ATC_MASK, 19, 0xf4, 0x3000, 0, 0 }, 0xf, { 0x3000, 0x3000, 0x3000, 0x3000 } }, + { { ATC_MASK, 16, 0x188, 0x1, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 16, 0x1d4, 0x700, 0, 0 }, 0xf, { 0x300, 0x300, 0x300, 0x300 } }, + { { ATC_MASK, 19, 0x24, 0x3f0000, 0, 0 }, 0xf, { 0x2a0000, 0x2a0000, 0x2a0000, 0x2a0000 } }, + { { ATC_MASK, 19, 0x3c, 0x18000, 0, 0 }, 0xf, { 0x10000, 0x10000, 0x10000, 0x10000 } }, + { { ATC_MASK, 19, 0x78, 0xc0000, 0, 0 }, 0xf, { 0x80000, 0x80000, 0x80000, 0x80000 } }, + { { ATC_MASK, 19, 0x7c, 0xc0, 0, 0 }, 0xf, { 0x80, 0x80, 0x80, 0x80 } }, + { { ATC_MASK, 19, 0x84, 0xc000, 0, 0 }, 0xf, { 0x8000, 0x8000, 0x8000, 0x8000 } }, + { { ATC_MASK, 19, 0xd4, 0x600000, 0, 0 }, 0xf, { 0x400000, 0x400000, 0x400000, 0x400000 } }, + { { ATC_MASK, 19, 0xd8, 0x300, 0, 0 }, 0xf, { 0x200, 0x200, 0x200, 0x200 } }, + { { ATC_MASK, 19, 0x40, 0xc00, 0, 0 }, 0xf, { 0x800, 0x800, 0x800, 0x800 } }, + { { ATC_MASK, 19, 0x48, 0x600, 0, 0 }, 0xf, { 0x400, 0x400, 0x400, 0x400 } }, + { { ATC_MASK, 19, 0x28, 0x1ff030, 0, 0 }, 0xf, { 0x11c020, 0x11c020, 0x11c020, 0x11c020 } }, + { { ATC_MASK, 19, 0x2c, 0xfc, 0, 0 }, 0xf, { 0xb0, 0xb0, 0xb0, 0xb0 } }, + { { ATC_MASK, 19, 0x28, 0xe00, 0, 0 }, 0xf, { 0x800, 0x800, 0x800, 0x800 } }, + { { ATC_MASK, 19, 0x28, 0x1c0, 0, 0 }, 0xf, { 0x180, 0x180, 0x180, 0x180 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0x10, 0x10, 0, 0 }, 0xf, { 0x10, 0x10, 0x10, 0x10 } }, + { { ATC_MASK, 2, 0x10, 0x40, 0, 0 }, 0xf, { 0x40, 0x40, 0x40, 0x40 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0x10, 0x1, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0x10, 0x4, 0, 0 }, 0xf, { 0x4, 0x4, 0x4, 0x4 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0xc, 0x400, 0, 0 }, 0xf, { 0x400, 0x400, 0x400, 0x400 } }, + { { ATC_MASK, 2, 0xc, 0x1000, 0, 0 }, 0xf, { 0x1000, 0x1000, 0x1000, 0x1000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0xc, 0x40, 0, 0 }, 0xf, { 0x40, 0x40, 0x40, 0x40 } }, + { { ATC_MASK, 2, 0xc, 0x100, 0, 0 }, 0xf, { 0x100, 0x100, 0x100, 0x100 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0xc, 0x4000, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 2, 0xc, 0x10000, 0, 0 }, 0xf, { 0x10000, 0x10000, 0x10000, 0x10000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 16, 0x1d0, 0x3, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 16, 0x1d0, 0x2, 0, 0 }, 0xf, { 0x2, 0x2, 0x2, 0x2 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 19, 0x40, 0x30000, 0, 0 }, 0xf, { 0x30000, 0x30000, 0x30000, 0x30000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 19, 0x40, 0xc000, 0, 0 }, 0xf, { 0xc000, 0xc000, 0xc000, 0xc000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 19, 0x28, 0x3010, 0, 0 }, 0xf, { 0x1010, 0x1010, 0x1010, 0x1010 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 19, 0x28, 0x600, 0, 0 }, 0xf, { 0x200, 0x200, 0x200, 0x200 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 19, 0x28, 0x3000, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 19, 0x24, 0x30000000, 0, 0 }, 0xf, { 0x30000000, 0x30000000, 0x30000000, 0x30000000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 19, 0x24, 0xc0000000, 0, 0 }, 0xf, { 0xc0000000, 0xc0000000, 0xc0000000, 0xc0000000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 19, 0x28, 0x600, 0, 0 }, 0xf, { 0x600, 0x600, 0x600, 0x600 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 19, 0x28, 0x600, 0, 0 }, 0xf, { 0x400, 0x400, 0x400, 0x400 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 19, 0x28, 0x600, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x2, 0x2, 0x2, 0x2 } }, + { { ATC_MASK, 19, 0x44, 0x18000000, 0, 0 }, 0xf, { 0x18000000, 0x18000000, 0x18000000, 0x18000000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x2, 0x2, 0x2, 0x2 } }, + { { ATC_MASK, 19, 0x40, 0x38, 0, 0 }, 0x1, { 0x28, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 19, 0x40, 0x38, 0, 0 }, 0xe, { 0x0, 0x20, 0x20, 0x20 } }, + { { ATC_MASK, 19, 0x44, 0x60000000, 0, 0 }, 0xf, { 0x40000000, 0x40000000, 0x40000000, 0x40000000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 19, 0x44, 0x6000000, 0, 0 }, 0xf, { 0x6000000, 0x6000000, 0x6000000, 0x6000000 } }, + { { ATC_MASK, 19, 0x40, 0xc000, 0, 0 }, 0xf, { 0x8000, 0x8000, 0x8000, 0x8000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 20, 0x68, 0xc00f, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 20, 0xac, 0x20000000, 0, 0 }, 0xf, { 0x20000000, 0x20000000, 0x20000000, 0x20000000 } }, + { { ATC_MASK, 20, 0xa8, 0x7ffff, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 20, 0x18, 0x38, 0, 0 }, 0xf, { 0x20, 0x20, 0x20, 0x20 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 14, 0x0, 0x1, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_POLL, 39, 0x1c, 0x1, 0, 5000 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, +}; + +static const struct atc_op dp_group1_off[] = { + { ATC_MASK, 39, 0x14, 0x1007, 0x7, 0 }, + { ATC_MASK, 39, 0x18, 0x1, 0x0, 0 }, + { ATC_POLL, 39, 0x1c, 0x1, 0x0, 5000 }, + { ATC_MASK, 14, 0x0, 0x1, 0x1, 0 }, + { ATC_MASK, 31, 0x1c, 0xc00000, 0xc00000, 0 }, + { ATC_MASK, 31, 0x10, 0x600, 0x400, 0 }, + { ATC_MASK, 31, 0x4, 0x30000000, 0x20000000, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 31, 0x44, 0x100000, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 31, 0x44, 0x400000, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 31, 0x44, 0x4000000, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 31, 0x44, 0x1000000, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 31, 0x44, 0x1000, 0x0, 0 }, + { ATC_MASK, 2, 0x120, 0x200, 0x200, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x120, 0x2, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x120, 0x20, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 28, 0x44, 0x6000000, 0x4000000, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 28, 0x44, 0x18000000, 0x10000000, 0 }, + { ATC_MASK, 28, 0x24, 0xc0000000, 0x80000000, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 28, 0x24, 0x30000000, 0x20000000, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 28, 0x28, 0x10, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 28, 0x40, 0xc000, 0xc000, 0 }, + { ATC_MASK, 25, 0x1d0, 0x3, 0x1, 0 }, + { ATC_MASK, 28, 0x40, 0x10000, 0x0, 0 }, + { ATC_MASK, 2, 0xc, 0x8000, 0x8000, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0xc, 0x80, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0xc, 0x800, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x10, 0x2, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x10, 0x20, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 14, 0x0, 0xc, 0x8, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x160, 0x400000, 0x400000, 0 }, + { ATC_MASK, 3, 0x8, 0x4, 0x4, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x160, 0x800000, 0x0, 0 }, + { ATC_MASK, 3, 0x8, 0x8, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x160, 0x800000, 0x800000, 0 }, + { ATC_MASK, 3, 0x8, 0x8, 0x8, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x160, 0x400000, 0x0, 0 }, + { ATC_MASK, 3, 0x8, 0x4, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 6, 0x1c, 0x7, 0x1, 0 }, + { ATC_MASK, 6, 0x1c, 0x8, 0x8, 0 }, + { ATC_POLL, 6, 0x2c, 0xff00, 0x100, 500000 }, + { ATC_MASK, 6, 0x1c, 0x8, 0x0, 0 }, + { ATC_POLL, 6, 0x2c, 0xff00, 0x0, 500000 }, + { ATC_POLL, 14, 0x34, 0x8, 0x0, 500000 }, + { ATC_DELAY, 0, 0x0, 0x0, 0xa, 0 }, + { ATC_MASK, 6, 0x34, 0xf, 0x1, 0 }, + { ATC_MASK, 6, 0x1c, 0x7, 0x0, 0 }, + { ATC_MASK, 6, 0x1c, 0x8, 0x8, 0 }, + { ATC_POLL, 6, 0x2c, 0xff00, 0x100, 500000 }, + { ATC_MASK, 6, 0x1c, 0x8, 0x0, 0 }, + { ATC_POLL, 6, 0x2c, 0xff00, 0x0, 500000 }, + { ATC_MASK, 8, 0x0, 0xaaa, 0x0, 0 }, +}; + +static const struct atc_rate_op dp_group1_on[] = { + { { ATC_MASK, 39, 0x14, 0x7, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 39, 0x18, 0x1, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0x6c, 0xe0, 0, 0 }, 0xf, { 0x40, 0x40, 0x40, 0x40 } }, + { { ATC_MASK, 14, 0x0, 0x40fff0, 0, 0 }, 0xf, { 0x1110, 0x1110, 0x1110, 0x1110 } }, + { { ATC_MASK, 8, 0x34, 0x3, 0, 0 }, 0x1, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x80, 0xffffffff, 0, 0 }, 0x1, { 0x1e0e021c, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x84, 0xfffffff, 0, 0 }, 0x1, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x88, 0x7fffff, 0, 0 }, 0x1, { 0x156600, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 8, 0x8, 0x1f0000, 0, 0 }, 0x1, { 0x70000, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 8, 0x18, 0x1, 0, 0 }, 0x1, { 0x1, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 8, 0x34, 0x3, 0, 0 }, 0x2, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x80, 0xffffffff, 0, 0 }, 0x2, { 0x0, 0x1e0e01c2, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x84, 0xfffffff, 0, 0 }, 0x2, { 0x0, 0x7fffffe, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x88, 0x7fffff, 0, 0 }, 0x2, { 0x0, 0x155200, 0x0, 0x0 } }, + { { ATC_MASK, 8, 0x8, 0x1f0000, 0, 0 }, 0x2, { 0x0, 0x70000, 0x0, 0x0 } }, + { { ATC_MASK, 8, 0x18, 0x1, 0, 0 }, 0x2, { 0x0, 0x1, 0x0, 0x0 } }, + { { ATC_MASK, 8, 0x34, 0x3, 0, 0 }, 0x4, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x80, 0xffffffff, 0, 0 }, 0x4, { 0x0, 0x0, 0x1e0e01c2, 0x0 } }, + { { ATC_MASK, 7, 0x84, 0xfffffff, 0, 0 }, 0x4, { 0x0, 0x0, 0x7fffffe, 0x0 } }, + { { ATC_MASK, 7, 0x88, 0x7fffff, 0, 0 }, 0x4, { 0x0, 0x0, 0x554800, 0x0 } }, + { { ATC_MASK, 8, 0x8, 0x1f0000, 0, 0 }, 0x4, { 0x0, 0x0, 0x70000, 0x0 } }, + { { ATC_MASK, 8, 0x18, 0x1, 0, 0 }, 0x4, { 0x0, 0x0, 0x1, 0x0 } }, + { { ATC_MASK, 8, 0x34, 0x3, 0, 0 }, 0x8, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x80, 0xffffffff, 0, 0 }, 0x8, { 0x0, 0x0, 0x0, 0x1e0e02a3 } }, + { { ATC_MASK, 7, 0x84, 0xfffffff, 0, 0 }, 0x8, { 0x0, 0x0, 0x0, 0xbff7ffc } }, + { { ATC_MASK, 7, 0x88, 0x7fffff, 0, 0 }, 0x8, { 0x0, 0x0, 0x0, 0x564800 } }, + { { ATC_MASK, 8, 0x8, 0x1f0000, 0, 0 }, 0x8, { 0x0, 0x0, 0x0, 0x70000 } }, + { { ATC_MASK, 8, 0x18, 0x1, 0, 0 }, 0x8, { 0x0, 0x0, 0x0, 0x1 } }, + { { ATC_MASK, 8, 0x0, 0xaaa, 0, 0 }, 0xf, { 0xaaa, 0xaaa, 0xaaa, 0xaaa } }, + { { ATC_MASK, 6, 0x30, 0x4000, 0, 0 }, 0xf, { 0x4000, 0x4000, 0x4000, 0x4000 } }, + { { ATC_MASK, 6, 0x34, 0xf, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 6, 0x18, 0x1fe000, 0, 0 }, 0xf, { 0x2000, 0x2000, 0x2000, 0x2000 } }, + { { ATC_MASK, 6, 0x20, 0x1, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 6, 0x1c, 0x7, 0, 0 }, 0xf, { 0x7, 0x7, 0x7, 0x7 } }, + { { ATC_MASK, 6, 0x1c, 0x8, 0, 0 }, 0xf, { 0x8, 0x8, 0x8, 0x8 } }, + { { ATC_POLL, 6, 0x2c, 0xff00, 0, 500000 }, 0xf, { 0x100, 0x100, 0x100, 0x100 } }, + { { ATC_MASK, 6, 0x1c, 0x8, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_POLL, 6, 0x2c, 0xff00, 0, 500000 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_POLL, 14, 0x34, 0x8, 0, 500000 }, 0xf, { 0x8, 0x8, 0x8, 0x8 } }, + { { ATC_MASK, 28, 0x3c, 0x18000, 0, 0 }, 0xf, { 0x10000, 0x10000, 0x10000, 0x10000 } }, + { { ATC_MASK, 14, 0x0, 0xc, 0, 0 }, 0xf, { 0xc, 0xc, 0xc, 0xc } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0x120, 0x20, 0, 0 }, 0xf, { 0x20, 0x20, 0x20, 0x20 } }, + { { ATC_MASK, 2, 0x120, 0x80, 0, 0 }, 0xf, { 0x80, 0x80, 0x80, 0x80 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0x120, 0x2, 0, 0 }, 0xf, { 0x2, 0x2, 0x2, 0x2 } }, + { { ATC_MASK, 2, 0x120, 0x8, 0, 0 }, 0xf, { 0x8, 0x8, 0x8, 0x8 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0x120, 0x200, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 2, 0x120, 0x800, 0, 0 }, 0xf, { 0x800, 0x800, 0x800, 0x800 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 31, 0x44, 0xfffffc0, 0, 0 }, 0xf, { 0xaafb800, 0xaafb800, 0xaafb800, 0xaafb800 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 31, 0x44, 0x1000000, 0, 0 }, 0xf, { 0x1000000, 0x1000000, 0x1000000, 0x1000000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 31, 0x44, 0x4000000, 0, 0 }, 0xf, { 0x4000000, 0x4000000, 0x4000000, 0x4000000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 31, 0x44, 0x7c0, 0, 0 }, 0xf, { 0x300, 0x300, 0x300, 0x300 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 31, 0x44, 0x6c000, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 30, 0x100, 0x100000, 0, 0 }, 0xf, { 0x100000, 0x100000, 0x100000, 0x100000 } }, + { { ATC_MASK, 30, 0x58, 0xc00f, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 30, 0x104, 0x20000000, 0, 0 }, 0xf, { 0x20000000, 0x20000000, 0x20000000, 0x20000000 } }, + { { ATC_MASK, 30, 0x100, 0x7ffff, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 31, 0x44, 0x100000, 0, 0 }, 0x7, { 0x100000, 0x100000, 0x100000, 0x0 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0x7, { 0x1, 0x1, 0x1, 0x0 } }, + { { ATC_MASK, 31, 0x44, 0x400000, 0, 0 }, 0x7, { 0x400000, 0x400000, 0x400000, 0x0 } }, + { { ATC_MASK, 31, 0x4, 0x3000000, 0, 0 }, 0xf, { 0x2000000, 0x2000000, 0x2000000, 0x2000000 } }, + { { ATC_MASK, 31, 0x4, 0x30000000, 0, 0 }, 0xf, { 0x30000000, 0x30000000, 0x30000000, 0x30000000 } }, + { { ATC_MASK, 31, 0xc, 0x7000000, 0, 0 }, 0x1, { 0x5000000, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 31, 0xc, 0x7000000, 0, 0 }, 0xe, { 0x0, 0x4000000, 0x4000000, 0x4000000 } }, + { { ATC_MASK, 31, 0x8, 0x3, 0, 0 }, 0xf, { 0x2, 0x2, 0x2, 0x2 } }, + { { ATC_MASK, 31, 0x10, 0x600, 0, 0 }, 0xf, { 0x600, 0x600, 0x600, 0x600 } }, + { { ATC_MASK, 31, 0x1c, 0xc00000, 0, 0 }, 0xf, { 0x800000, 0x800000, 0x800000, 0x800000 } }, + { { ATC_MASK, 28, 0xf4, 0x3000, 0, 0 }, 0xf, { 0x3000, 0x3000, 0x3000, 0x3000 } }, + { { ATC_MASK, 25, 0x188, 0x1, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 25, 0x1d4, 0x700, 0, 0 }, 0xf, { 0x300, 0x300, 0x300, 0x300 } }, + { { ATC_MASK, 28, 0x24, 0x3f0000, 0, 0 }, 0xf, { 0x2a0000, 0x2a0000, 0x2a0000, 0x2a0000 } }, + { { ATC_MASK, 28, 0x3c, 0x18000, 0, 0 }, 0xf, { 0x10000, 0x10000, 0x10000, 0x10000 } }, + { { ATC_MASK, 28, 0x78, 0xc0000, 0, 0 }, 0xf, { 0x80000, 0x80000, 0x80000, 0x80000 } }, + { { ATC_MASK, 28, 0x7c, 0xc0, 0, 0 }, 0xf, { 0x80, 0x80, 0x80, 0x80 } }, + { { ATC_MASK, 28, 0x84, 0xc000, 0, 0 }, 0xf, { 0x8000, 0x8000, 0x8000, 0x8000 } }, + { { ATC_MASK, 28, 0xd4, 0x600000, 0, 0 }, 0xf, { 0x400000, 0x400000, 0x400000, 0x400000 } }, + { { ATC_MASK, 28, 0xd8, 0x300, 0, 0 }, 0xf, { 0x200, 0x200, 0x200, 0x200 } }, + { { ATC_MASK, 28, 0x40, 0xc00, 0, 0 }, 0xf, { 0x800, 0x800, 0x800, 0x800 } }, + { { ATC_MASK, 28, 0x48, 0x600, 0, 0 }, 0xf, { 0x400, 0x400, 0x400, 0x400 } }, + { { ATC_MASK, 28, 0x28, 0x1ff030, 0, 0 }, 0xf, { 0x11c020, 0x11c020, 0x11c020, 0x11c020 } }, + { { ATC_MASK, 28, 0x2c, 0xfc, 0, 0 }, 0xf, { 0xb0, 0xb0, 0xb0, 0xb0 } }, + { { ATC_MASK, 28, 0x28, 0xe00, 0, 0 }, 0xf, { 0x800, 0x800, 0x800, 0x800 } }, + { { ATC_MASK, 28, 0x28, 0x1c0, 0, 0 }, 0xf, { 0x180, 0x180, 0x180, 0x180 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0x10, 0x20, 0, 0 }, 0xf, { 0x20, 0x20, 0x20, 0x20 } }, + { { ATC_MASK, 2, 0x10, 0x80, 0, 0 }, 0xf, { 0x80, 0x80, 0x80, 0x80 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0x10, 0x2, 0, 0 }, 0xf, { 0x2, 0x2, 0x2, 0x2 } }, + { { ATC_MASK, 2, 0x10, 0x8, 0, 0 }, 0xf, { 0x8, 0x8, 0x8, 0x8 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0xc, 0x800, 0, 0 }, 0xf, { 0x800, 0x800, 0x800, 0x800 } }, + { { ATC_MASK, 2, 0xc, 0x2000, 0, 0 }, 0xf, { 0x2000, 0x2000, 0x2000, 0x2000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0xc, 0x80, 0, 0 }, 0xf, { 0x80, 0x80, 0x80, 0x80 } }, + { { ATC_MASK, 2, 0xc, 0x200, 0, 0 }, 0xf, { 0x200, 0x200, 0x200, 0x200 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0xc, 0x8000, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 2, 0xc, 0x20000, 0, 0 }, 0xf, { 0x20000, 0x20000, 0x20000, 0x20000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 25, 0x1d0, 0x3, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 25, 0x1d0, 0x2, 0, 0 }, 0xf, { 0x2, 0x2, 0x2, 0x2 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 28, 0x40, 0x30000, 0, 0 }, 0xf, { 0x30000, 0x30000, 0x30000, 0x30000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 28, 0x40, 0xc000, 0, 0 }, 0xf, { 0xc000, 0xc000, 0xc000, 0xc000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 28, 0x28, 0x3010, 0, 0 }, 0xf, { 0x1010, 0x1010, 0x1010, 0x1010 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 28, 0x28, 0x600, 0, 0 }, 0xf, { 0x200, 0x200, 0x200, 0x200 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 28, 0x28, 0x3000, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 28, 0x24, 0x30000000, 0, 0 }, 0xf, { 0x30000000, 0x30000000, 0x30000000, 0x30000000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 28, 0x24, 0xc0000000, 0, 0 }, 0xf, { 0xc0000000, 0xc0000000, 0xc0000000, 0xc0000000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 28, 0x28, 0x600, 0, 0 }, 0xf, { 0x600, 0x600, 0x600, 0x600 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 28, 0x28, 0x600, 0, 0 }, 0xf, { 0x400, 0x400, 0x400, 0x400 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 28, 0x28, 0x600, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x2, 0x2, 0x2, 0x2 } }, + { { ATC_MASK, 28, 0x44, 0x18000000, 0, 0 }, 0xf, { 0x18000000, 0x18000000, 0x18000000, 0x18000000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x2, 0x2, 0x2, 0x2 } }, + { { ATC_MASK, 28, 0x40, 0x38, 0, 0 }, 0x1, { 0x28, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 28, 0x40, 0x38, 0, 0 }, 0xe, { 0x0, 0x20, 0x20, 0x20 } }, + { { ATC_MASK, 28, 0x44, 0x60000000, 0, 0 }, 0xf, { 0x40000000, 0x40000000, 0x40000000, 0x40000000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 28, 0x44, 0x6000000, 0, 0 }, 0xf, { 0x6000000, 0x6000000, 0x6000000, 0x6000000 } }, + { { ATC_MASK, 28, 0x40, 0xc000, 0, 0 }, 0xf, { 0x8000, 0x8000, 0x8000, 0x8000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 29, 0x68, 0xc00f, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 29, 0xac, 0x20000000, 0, 0 }, 0xf, { 0x20000000, 0x20000000, 0x20000000, 0x20000000 } }, + { { ATC_MASK, 29, 0xa8, 0x7ffff, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 29, 0x18, 0x38, 0, 0 }, 0xf, { 0x20, 0x20, 0x20, 0x20 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 14, 0x0, 0x1, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_POLL, 39, 0x1c, 0x1, 0, 5000 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, +}; diff --git a/drivers/phy/apple/t6040-usb2.c b/drivers/phy/apple/t6040-usb2.c index 20fb5e9da5cb0..5425cdfa25d38 100644 --- a/drivers/phy/apple/t6040-usb2.c +++ b/drivers/phy/apple/t6040-usb2.c @@ -106,7 +106,7 @@ #define T6040_PIPEHANDLER_DUMMY_PHY_READY BIT(15) /* - * DisplayPort (PHY mode 3). Offsets are into the "core" and "cfg" windows + * DisplayPort (four-lane mode 3 and two-lane mode 4). Offsets are into the "core" and "cfg" windows * the USB3 path uses; names follow the paired AppleT6040TypeCPhy driver. */ #define T6040_CORE_AUX_CTRL 0x28000 /* ACIOPHY_AUX_TOP_BLK_AUX_CTRL */ @@ -242,12 +242,15 @@ struct t6040_usb2_phy { bool usb3_lent; /* DP mode took the lanes of this Type-C port */ bool pipe_switched; /* the controller's PIPE runs on this PHY */ - /* DisplayPort (PHY mode 3); dp_ok is false when the DT lacks a DP route */ + /* DisplayPort; dp_ok is false when the DT lacks a DP route */ struct phy *dp; struct typec_mux_dev *mux; bool dp_ok; bool dp_up; /* DP mode is programmed on the PHY */ - bool dp_alt; /* the Type-C port is in DP alt mode, pin assignment C/E */ + bool dp_alt; /* the Type-C port is in DP alt mode */ + bool dp_two_lanes; /* pin D keeps USB3 on the other pair */ + bool dp_mode_two_lanes; /* latched until DP power_off, even after mux exit */ + bool dp_mode_flip; bool dp_flip; /* plug orientation reversed for the DP lanes */ int dp_rate; /* link rate index of the running link sequence, -1 = off */ }; @@ -952,6 +955,9 @@ static void t6040_usb3_down_locked(struct t6040_usb2_phy *tphy) t6040_usb3_pipe_park(tphy); tphy->pipe_switched = false; } + /* DWC3 can drop its role before the DP fabric has released pin D. */ + if (tphy->dp_up && tphy->dp_mode_two_lanes) + return; if (tphy->usb3_up) { u32 uc = readl(t6040_core(tphy) + T6040_CORE_UC_CTRL); @@ -1272,6 +1278,7 @@ struct atc_rate_op { }; #include "atc-t6050-dp-data.h" +#include "atc-t6040-dp2-data.h" static const struct { u8 bank; u32 offset; } t6040_dp_core_banks[] = { {2, 0}, {3, 0x400}, {4, 0x800}, {5, 0xa00}, {6, 0x1000}, @@ -1325,12 +1332,29 @@ static int t6040_dp_seq_step(struct t6040_usb2_phy *tphy, const struct atc_op *s /* Take the link down if it is up, then bring it up at rate index @idx (-1: off). */ static int t6040_dp_set_rate(struct t6040_usb2_phy *tphy, int idx) { + const struct atc_op *off = dp_link_off; + const struct atc_rate_op *on = dp_link_on; + size_t nr_off = ARRAY_SIZE(dp_link_off); + size_t nr_on = ARRAY_SIZE(dp_link_on); size_t i; int ret; + if (tphy->dp_mode_two_lanes) { + if (tphy->dp_mode_flip) { + off = dp_group0_off; + on = dp_group0_on; + nr_off = ARRAY_SIZE(dp_group0_off); + nr_on = ARRAY_SIZE(dp_group0_on); + } else { + off = dp_group1_off; + on = dp_group1_on; + nr_off = ARRAY_SIZE(dp_group1_off); + nr_on = ARRAY_SIZE(dp_group1_on); + } + } if (tphy->dp_rate >= 0) { - for (i = 0; i < ARRAY_SIZE(dp_link_off); i++) { - ret = t6040_dp_seq_step(tphy, &dp_link_off[i]); + for (i = 0; i < nr_off; i++) { + ret = t6040_dp_seq_step(tphy, &off[i]); if (ret) return ret; } @@ -1338,12 +1362,12 @@ static int t6040_dp_set_rate(struct t6040_usb2_phy *tphy, int idx) } if (idx < 0) return 0; - for (i = 0; i < ARRAY_SIZE(dp_link_on); i++) { - struct atc_op op = dp_link_on[i].op; + for (i = 0; i < nr_on; i++) { + struct atc_op op = on[i].op; - if (!(dp_link_on[i].rates & BIT(idx))) + if (!(on[i].rates & BIT(idx))) continue; - op.value = dp_link_on[i].values[idx]; + op.value = on[i].values[idx]; ret = t6040_dp_seq_step(tphy, &op); if (ret) return ret; @@ -1359,7 +1383,8 @@ static int t6040_dp_set_rate(struct t6040_usb2_phy *tphy, int idx) /* Drive settings of the T6050 sequence: lanes 0/1 on PMA group 1, 2/3 on group 0. */ static void t6040_dp_drive(struct t6040_usb2_phy *tphy, unsigned int lane, u32 preset) { - unsigned int group = lane < 2 ? 1 : 0; + unsigned int group = (lane < 2 ? 1 : 0) ^ + (tphy->dp_mode_two_lanes && tphy->dp_mode_flip); bool tx = lane & 1; void __iomem *base = t6040_dp_bank(tphy, (tx ? 21 : 20) + 9 * group); u32 ctrl = ((preset >> 2) & 0xf) | ((preset & 3) << 14); @@ -1407,11 +1432,25 @@ static int t6040_dp_power_on(struct phy *phy) guard(mutex)(&tphy->lock); if (!tphy->dp_ok || tphy->dp_up) return 0; - /* - * A Type-C PHY in DP alt mode (pin assignment C/E) hands all four - * lanes to DP: aciophy_phy_shutdown of the USB3 side first, then the - * same mode-3 init as the fixed DP PHY. - */ + /* Pin D shares the running PHY with USB3. Keep its PIPE and firmware. */ + if (tphy->dp_two_lanes) { + if (!tphy->usb3_ok || !tphy->dp_alt) + return -ENOLINK; + if (!tphy->usb3_up) + t6040_usb3_up_locked(tphy); + if (!tphy->usb3_up) + return -ENOLINK; + t6040_dp_fixed_tunables(t6040_core(tphy)); + t6040_dp_aux_init(tphy); + tphy->dp_rate = -1; + tphy->dp_mode_two_lanes = true; + tphy->dp_mode_flip = tphy->dp_flip; + tphy->dp_up = true; + dev_info(tphy->dev, "DP PHY pin D: two lanes, USB3 retained, %s orientation\n", + tphy->dp_flip ? "reverse" : "normal"); + return 0; + } + /* C/E and the fixed HDMI PHY hand all four lanes to DP. */ if (!t6040_dp_only(tphy)) { t6040_usb3_down_locked(tphy); tphy->usb3_lent = true; @@ -1423,6 +1462,7 @@ static int t6040_dp_power_on(struct phy *phy) } t6040_dp_mode_setup(tphy, tphy->dp_flip); tphy->dp_rate = -1; + tphy->dp_mode_two_lanes = false; tphy->dp_up = true; dev_dbg(tphy->dev, "DP PHY mode set, lane map %s\n", tphy->dp_flip ? "reversed" : "normal"); @@ -1432,22 +1472,33 @@ static int t6040_dp_power_on(struct phy *phy) static int t6040_dp_power_off(struct phy *phy) { struct t6040_usb2_phy *tphy = phy_get_drvdata(phy); + int ret = 0; guard(mutex)(&tphy->lock); + if (tphy->dp_up && tphy->dp_mode_two_lanes) { + ret = t6040_dp_set_rate(tphy, -1); + if (ret) + return ret; + } tphy->dp_up = false; + if (tphy->dp_mode_two_lanes && !tphy->usb3_on) + t6040_usb3_down_locked(tphy); + tphy->dp_mode_two_lanes = false; t6040_usb3_reclaim_locked(tphy); - return 0; + return ret; } -/* GET_MAX_LANE_COUNT: DP on all four lanes in mode 3. */ +/* GET_MAX_LANE_COUNT follows the negotiated pin assignment. */ static int t6040_dp_validate(struct phy *phy, enum phy_mode mode, int submode, union phy_configure_opts *opts_) { + struct t6040_usb2_phy *tphy = phy_get_drvdata(phy); struct phy_configure_opts_dp *opts = &opts_->dp; if (mode != PHY_MODE_DP) return -EINVAL; - opts->lanes = 4; + guard(mutex)(&tphy->lock); + opts->lanes = tphy->dp_two_lanes ? 2 : 4; return 0; } @@ -1461,6 +1512,9 @@ static int t6040_dp_configure(struct phy *phy, union phy_configure_opts *opts_) guard(mutex)(&tphy->lock); if (!tphy->dp_up) return -EINVAL; + if ((opts->set_lanes || opts->set_voltages) && + opts->lanes > (tphy->dp_mode_two_lanes ? 2 : 4)) + return -EINVAL; if (opts->set_rate) { switch (opts->link_rate) { @@ -1491,7 +1545,7 @@ static int t6040_dp_configure(struct phy *phy, union phy_configure_opts *opts_) if (opts->set_voltages && tphy->dp_rate >= 0) { unsigned int i; - for (i = 0; i < opts->lanes && i < 4; i++) { + for (i = 0; i < opts->lanes; i++) { unsigned int v = min_t(unsigned int, opts->voltage[i], 3); unsigned int pe = min_t(unsigned int, opts->pre[i], 3); @@ -1530,10 +1584,9 @@ static int t6040_dp_mux_set(struct typec_mux_dev *mux, struct typec_mux_state *s tphy->dp_flip = (tphy->orientation == TYPEC_ORIENTATION_REVERSE); /* * Latch the DP alt mode state for the DCP's Type-C route. Pin - * assignments C and E put DP on all four lanes (PHY mode 3, as the - * fixed DP route); D (two DP lanes next to USB3, PHY mode 4 with the - * coprocessor) is not supported. The DP lanes themselves are brought - * up by the DCP through phy_power_on()/phy_configure() on the DP phy. + * assignments C and E put DP on all four lanes. D leaves the USB3 + * pair with its running coprocessor and gives DP the other pair. + * The DCP brings the DP lanes up through phy_power_on()/phy_configure(). */ if (dp) { switch (state->mode) { @@ -1542,16 +1595,17 @@ static int t6040_dp_mux_set(struct typec_mux_dev *mux, struct typec_mux_state *s alt = true; break; case TYPEC_DP_STATE_D: - dev_warn_once(tphy->dev, "Type-C mux: DP pin assignment D (two lanes next to USB3) not supported\n"); + alt = true; break; default: break; } } tphy->dp_alt = alt; + tphy->dp_two_lanes = alt && state->mode == TYPEC_DP_STATE_D; dev_dbg(tphy->dev, "Type-C mux: %s state %lu -> DP alt mode %d, lane map %s\n", dp ? "DP" : "non-DP", state->mode, alt, tphy->dp_flip ? "reversed" : "normal"); - if (alt && tphy->dp_up) + if (alt && tphy->dp_up && !tphy->dp_mode_two_lanes && !tphy->dp_two_lanes) t6040_dp_xbar_config(tphy, tphy->dp_flip); t6040_usb3_reclaim_locked(tphy); return 0; From 31340abccfcc3f7f9669fa327d8771a3cadd4f3f Mon Sep 17 00:00:00 2001 From: Ryan Murray <81541646+RyanTheTide@users.noreply.github.com> Date: Tue, 27 Oct 2026 23:31:51 +1100 Subject: [PATCH 175/184] phy: apple: t6040: avoid rewriting live USB3 tunables for DP pin D --- drivers/phy/apple/t6040-usb2.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/phy/apple/t6040-usb2.c b/drivers/phy/apple/t6040-usb2.c index 5425cdfa25d38..cb4146d91faf1 100644 --- a/drivers/phy/apple/t6040-usb2.c +++ b/drivers/phy/apple/t6040-usb2.c @@ -1039,8 +1039,8 @@ static void t6040_usb3_switch_unregister(void *sw) * link rate through phy_configure(): the per-rate link sequence is the T6050 * fixed-DP table (atc-t6050-dp-data.h; the T6050 ADT names its PHY * "atc-phy,t6050", "atc-phy,t6040"), followed by the T6040 DP clock switch in - * the "cfg" window. Pin assignment D (two DP lanes next to USB3) is not - * supported. + * the "cfg" window. Pin assignment D shares USB3's running PHY and uses + * the two-lane group link sequences below. */ /* The fixed DPTX tunable writes (apply_dptx_tunables, shared with USB3). */ @@ -1440,7 +1440,7 @@ static int t6040_dp_power_on(struct phy *phy) t6040_usb3_up_locked(tphy); if (!tphy->usb3_up) return -ENOLINK; - t6040_dp_fixed_tunables(t6040_core(tphy)); + /* USB3 startup already applied these DPTX tunables before firmware. */ t6040_dp_aux_init(tphy); tphy->dp_rate = -1; tphy->dp_mode_two_lanes = true; From 763b4dd1e7c1d1a5e1e330b974d20c481da51d0f Mon Sep 17 00:00:00 2001 From: Jonathan Goyvaerts Date: Mon, 26 Oct 2026 22:02:48 -0300 Subject: [PATCH 176/184] drm/asahi: g17: never report a busy fragment queue on 25G83 The 25G83 firmware reads the tiling descriptor's fragment_busy word (TA+0x7a4) only in its out-of-memory lane, at the first out-of-memory stop of a pass. When it is non-zero the firmware compares the pass-seq byte of the last completed fragment kick on that queue with the lane's copy of TA+0x7b0. On a mismatch it resumes the stopped TA with the resume-after-grow register write but without adding memory, and retires the lane: the tiler then stops silently with no further out-of-memory stop and no completion, and the forward-progress watchdog fires (reason 0). We set fragment_busy whenever the previous fragment kick had not completed at publish, which is common in a pipelined stream. macOS always writes 0 there on this firmware. Add Variant::fragment_busy and clear it for V25G83, so the out-of-memory lane takes the path macOS takes. Renders that fit their tiler heap never reach that lane, so they are unaffected. V26A428 keeps its current behaviour. The firmware behaviour above is from a static disassembly of the 25G83 firmware and the macOS AGXG16X kext. In our testing on a J616s (T6040, 25G83), with the tiler heap at its tile-count size so ordinary heavy passes must grow, this alone took dEQP-VK.memory.pipeline_barrier.graphics.all_device.1048576_vertex_buffer_stride_2 from 7 firmware recoveries over three runs to 4; the next change removes the rest. Signed-off-by: Jonathan Goyvaerts Assisted-by: LLM --- drivers/gpu/drm/asahi/g17/fw/variant.rs | 7 +++++++ drivers/gpu/drm/asahi/g17/queue/render.rs | 8 ++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/asahi/g17/fw/variant.rs b/drivers/gpu/drm/asahi/g17/fw/variant.rs index d2d6d2404dfc8..60b46f628eb8a 100644 --- a/drivers/gpu/drm/asahi/g17/fw/variant.rs +++ b/drivers/gpu/drm/asahi/g17/fw/variant.rs @@ -57,6 +57,9 @@ pub(crate) struct Variant { pub(crate) few_primitives: bool, /// The firmware runs ray-traversal work (ray queries and intersection programs). pub(crate) ray_traversal: bool, + /// Tiling descriptors report whether the fragment queue still had earlier kicks + /// outstanding (`fragment_busy`). + pub(crate) fragment_busy: bool, /// Render item rings carry lead commands: InitBufferManager before the first tiling pass /// of a buffer manager, and FragmentBarrier before every fragment descriptor. pub(crate) lead_commands: bool, @@ -101,6 +104,7 @@ pub(crate) const V26A428: Variant = Variant { mcache: true, few_primitives: true, ray_traversal: true, + fragment_busy: true, lead_commands: false, programs: super::render::RegisterPrograms::G17P, }; @@ -126,6 +130,9 @@ pub(crate) const V25G83: Variant = Variant { few_primitives: false, // A stalled ray traversal wedges the GPU without a firmware recovery. ray_traversal: false, + // When a tiling pass runs out of memory with this set, the firmware resumes it without + // adding memory and the tiler stops silently; macOS always writes 0. + fragment_busy: false, lead_commands: true, programs: super::render::RegisterPrograms::V25G83, }; diff --git a/drivers/gpu/drm/asahi/g17/queue/render.rs b/drivers/gpu/drm/asahi/g17/queue/render.rs index 3261f2f0640b3..41a2a4bfbbbd9 100644 --- a/drivers/gpu/drm/asahi/g17/queue/render.rs +++ b/drivers/gpu/drm/asahi/g17/queue/render.rs @@ -587,8 +587,12 @@ impl Pair { let stamps = host.stamps(qids)?; fence(Ordering::Acquire); let covered = stamps.map(|stamp| retirement::stamp_covers(stamp, ordinal)); - self.memory - .completion_links(ordinal, kicks, payload, !covered[1])?; + self.memory.completion_links( + ordinal, + kicks, + payload, + self.variant.fragment_busy && !covered[1], + )?; let leads = match self.variant.lead_commands { true => { let init_manager = !self.memory.manager.initialized(); From ad14f2da457be86a30e212852f23d8e18166f6d7 Mon Sep 17 00:00:00 2001 From: Jonathan Goyvaerts Date: Mon, 26 Oct 2026 22:03:56 -0300 Subject: [PATCH 177/184] drm/asahi: g17: drop the 25G83 fragment barrier Commit 0d9efa515 ("drm/asahi: g17: render lead commands for the 25G83 firmware") led every 25G83 fragment descriptor with a FragmentBarrier (tag 4) that waits for the paired tiling stamp. macOS does not send that item: its fragment submission stores item tags 1, 0xc, 0xe, 0xf and 0x10 only, and keeps fragment-after-tiling ordering in the kick entry's dependency 0, which we already set. The barrier deadlocks a pass that runs out of tiler memory. The firmware's item-ring loop stops at an item whose handler returns 0, and the tag-4 handler returns 0 until the tiling stamp is reached, so the fragment kick's AddKicks is only processed after its tiling pass completes. A partial render needs that fragment kick announced while the tiling pass is stopped (the firmware releases it in out-of-memory state 7 by clearing the kick entry's dependency 0), so the tiling pass waits for the partial render and the partial render waits for the tiling pass. Remove FragmentBarrier and write only InitBufferManager as a lead command. V26A428 has no lead commands and is unaffected. The firmware behaviour above is from a static disassembly of the 25G83 firmware and the macOS AGXG16X kext. On a J616s (T6040, 25G83), with the tiler heap left at its tile-count size so ordinary heavy passes must grow, this and the previous change take dEQP-VK.memory.pipeline_barrier.graphics.all_device.1048576_vertex_buffer_stride_2 from 7 firmware recoveries over three runs to none. A 1/2000 vk-default slice is 769 pass / 0 fail / 0 crash, and the desktop and GL clients render as before. Fixes: 0d9efa515 ("drm/asahi: g17: render lead commands for the 25G83 firmware") Signed-off-by: Jonathan Goyvaerts Assisted-by: LLM --- drivers/gpu/drm/asahi/g17/fw/queue.rs | 59 ------------------- drivers/gpu/drm/asahi/g17/fw/variant.rs | 2 +- drivers/gpu/drm/asahi/g17/queue/render.rs | 2 +- .../gpu/drm/asahi/g17/queue/render/memory.rs | 36 +++-------- 4 files changed, 10 insertions(+), 89 deletions(-) diff --git a/drivers/gpu/drm/asahi/g17/fw/queue.rs b/drivers/gpu/drm/asahi/g17/fw/queue.rs index 5708a2251a062..66e7d07db48a5 100644 --- a/drivers/gpu/drm/asahi/g17/fw/queue.rs +++ b/drivers/gpu/drm/asahi/g17/fw/queue.rs @@ -96,8 +96,6 @@ pub(crate) enum CommandTag { QueueConfig = 0x0f, /// [`KickPredecessor`]. KickPredecessor = 0x10, - /// [`FragmentBarrier`] (25G83 firmware). - FragmentBarrier = 4, /// [`InitBufferManager`] (25G83 firmware). InitBufferManager = 5, } @@ -569,63 +567,6 @@ impl QueueConfig { } } -/// The 25G83 firmware's barrier before every fragment descriptor (tag 4): the fragment work -/// waits for the paired tiling stamp, in addition to the kick-entry dependency. -#[repr(C, packed)] -#[derive(Copy, Clone, Debug)] -pub(crate) struct FragmentBarrier { - /// [`CommandTag::FragmentBarrier`]. - pub(crate) tag: u32, - /// GPU address of the tiling queue's completion stamp word. - pub(crate) stamp_va: u64, - /// Timestamp of the tiling kick. - pub(crate) tiling_kick: u64, - /// QID of the tiling queue. - pub(crate) tiling_qid: u32, - /// Tiling stamp value waited for. - pub(crate) wait: u32, - pub(crate) unk_1c: [u8; 8], - /// Event of the wait: the tiling QID. - pub(crate) event: u32, - /// Fragment stamp value. - pub(crate) stamp_self: u32, - /// Current object ID of the fragment stage. - pub(crate) fragment_id: u64, - pub(crate) unk_34: u32, - pub(crate) unk_38: [u8; 0x10], -} - -static_assert!(size_of::() == 0x48); -static_assert!(core::mem::offset_of!(FragmentBarrier, wait) == 0x18); -static_assert!(core::mem::offset_of!(FragmentBarrier, event) == 0x24); -static_assert!(core::mem::offset_of!(FragmentBarrier, fragment_id) == 0x2c); - -impl FragmentBarrier { - /// The barrier of a pass whose tiling stage, kicked at `tiling_kick` on `tiling_qid`, - /// signals `stamp_value` at `stamp_va`. - pub(crate) fn new( - stamp_va: u64, - tiling_kick: KickTimestamp, - tiling_qid: u32, - stamp_value: u32, - fragment_id: u64, - ) -> Self { - Self { - tag: CommandTag::FragmentBarrier as u32, - stamp_va, - tiling_kick: tiling_kick.get(), - tiling_qid, - wait: stamp_value, - unk_1c: [0; 8], - event: tiling_qid, - stamp_self: stamp_value, - fragment_id, - unk_34: 1, - unk_38: [0; 0x10], - } - } -} - /// The 25G83 firmware's buffer-manager initialization (tag 5), before the first tiling /// descriptor that uses a parameter buffer. The block count is read from the manager. #[repr(C, packed)] diff --git a/drivers/gpu/drm/asahi/g17/fw/variant.rs b/drivers/gpu/drm/asahi/g17/fw/variant.rs index 60b46f628eb8a..6b1a6af2d2cfc 100644 --- a/drivers/gpu/drm/asahi/g17/fw/variant.rs +++ b/drivers/gpu/drm/asahi/g17/fw/variant.rs @@ -61,7 +61,7 @@ pub(crate) struct Variant { /// outstanding (`fragment_busy`). pub(crate) fragment_busy: bool, /// Render item rings carry lead commands: InitBufferManager before the first tiling pass - /// of a buffer manager, and FragmentBarrier before every fragment descriptor. + /// of a buffer manager. pub(crate) lead_commands: bool, /// Register programs of the render descriptors. pub(crate) programs: super::render::RegisterPrograms, diff --git a/drivers/gpu/drm/asahi/g17/queue/render.rs b/drivers/gpu/drm/asahi/g17/queue/render.rs index 41a2a4bfbbbd9..1d0a584f171ae 100644 --- a/drivers/gpu/drm/asahi/g17/queue/render.rs +++ b/drivers/gpu/drm/asahi/g17/queue/render.rs @@ -596,7 +596,7 @@ impl Pair { let leads = match self.variant.lead_commands { true => { let init_manager = !self.memory.manager.initialized(); - self.memory.write_leads(ordinal, kicks[0], init_manager)? + self.memory.write_leads(ordinal, init_manager)? } false => [false; 2], }; diff --git a/drivers/gpu/drm/asahi/g17/queue/render/memory.rs b/drivers/gpu/drm/asahi/g17/queue/render/memory.rs index 884537b99b6d8..90a11ca977684 100644 --- a/drivers/gpu/drm/asahi/g17/queue/render/memory.rs +++ b/drivers/gpu/drm/asahi/g17/queue/render/memory.rs @@ -31,8 +31,8 @@ pub(super) const ANNOUNCE: [usize; 2] = [0x17000, 0x1b000]; pub(super) const JOB_LIST: usize = 0xa000; pub(super) const CONFIG: [usize; 2] = [0, 0x4000]; pub(super) const CONFIG_STRIDE: usize = 0x180; -/// The lead command of a stage (InitBM before tiling, the barrier before fragment), after the -/// stage's configuration record in its slot ([`crate::g17::fw::Variant::lead_commands`]). +/// The lead command of a stage (InitBM before tiling), after the stage's configuration record +/// in its slot ([`crate::g17::fw::Variant::lead_commands`]). pub(super) const LEAD: usize = 0xc0; const DEPTH: usize = queues::RENDER_DEPTH as usize; /// Offsets of the priority pair and the policy word in a queue record. @@ -534,7 +534,6 @@ const _: () = { assert!(abi::FRAGMENT_SLOT_SIZE * RECORDS <= FRAGMENT_BYTES); assert!(RECORDS * CONFIG_STRIDE <= 0x4000); assert!(size_of::() <= LEAD); - assert!(LEAD + size_of::() <= CONFIG_STRIDE); assert!(LEAD + size_of::() <= CONFIG_STRIDE); }; @@ -648,14 +647,8 @@ impl Memory { } /// Writes the lead commands of `ordinal` from its linked descriptors: InitBM before the - /// tiling descriptor when `init_manager`, and the fragment barrier. Returns which stages - /// have one. - pub(super) fn write_leads( - &mut self, - ordinal: u64, - tiling_kick: fw::kick::KickTimestamp, - init_manager: bool, - ) -> Result<[bool; 2]> { + /// tiling descriptor when `init_manager`. Returns which stages have one. + pub(super) fn write_leads(&mut self, ordinal: u64, init_manager: bool) -> Result<[bool; 2]> { if !self.owns(ordinal) { return Err(EIO); } @@ -664,23 +657,10 @@ impl Memory { .descriptors .pointer(Graph::offset(0, ordinal), size_of::())? .cast::(); - let fragment = self - .graph - .descriptors - .pointer(Graph::offset(1, ordinal), size_of::())? - .cast::(); - // SAFETY: Both views were checked during preparation and the ordinal still owns them; - // only integer fields are read. - let (ta, fragment_id) = unsafe { (&*ta, (*fragment).descriptor.current_id) }; + // SAFETY: The view was checked during preparation and the ordinal still owns it; only + // integer fields are read. + let ta = unsafe { &*ta }; let slot = ordinal as usize % RECORDS * CONFIG_STRIDE + LEAD; - let barrier = queue::FragmentBarrier::new( - ta.stamp_va, - tiling_kick, - ta.qid, - ta.stamp_value, - fragment_id, - ); - self.graph.config.write(CONFIG[1] + slot, barrier)?; if init_manager { let init = queue::InitBufferManager::new( ta.context_id, @@ -690,7 +670,7 @@ impl Memory { ); self.graph.config.write(CONFIG[0] + slot, init)?; } - Ok([init_manager, true]) + Ok([init_manager, false]) } } From d769aaf5b389a4d6ebd4687cc50413b83634cee8 Mon Sep 17 00:00:00 2001 From: Ryan <81541646+RyanTheTide@users.noreply.github.com> Date: Wed, 28 Oct 2026 01:28:13 +1100 Subject: [PATCH 178/184] usb: typec: tipd: reconcile firmware-entered J616s DP mode Publish the entered partner DisplayPort alternate mode, consume SPMI events latched before IRQ registration, expose pin E, and provide the connector fwnode fallback. The tether route gate follow-up belongs with #451 and is included in the batch3 candidate. Assisted-by: LLM --- drivers/usb/typec/tipd/core.c | 46 ++++++++++++++++++++++++++++--- drivers/usb/typec/tipd/spmi.c | 9 ++++-- drivers/usb/typec/tipd/tps6598x.h | 1 + 3 files changed, 50 insertions(+), 6 deletions(-) diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index aa630b95dafe9..dd6f7b99e698a 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -290,6 +290,11 @@ static int tps6598x_connect(struct tps6598x *tps, u32 status) static void tps6598x_drop_partner(struct tps6598x *tps, u32 status) { + if (tps->partner_altmode_dp) { + typec_altmode_update_active(tps->partner_altmode_dp, false); + typec_unregister_altmode(tps->partner_altmode_dp); + tps->partner_altmode_dp = NULL; + } if (!IS_ERR(tps->partner)) typec_unregister_partner(tps->partner); tps->partner = NULL; @@ -1171,6 +1176,11 @@ static void cd321x_update_work(struct work_struct *work) /* Process partner disconnection or change */ if (!new_connected || partner_changed) { + if (tps->partner_altmode_dp) { + typec_altmode_update_active(tps->partner_altmode_dp, false); + typec_unregister_altmode(tps->partner_altmode_dp); + tps->partner_altmode_dp = NULL; + } if (!IS_ERR(tps->partner)) typec_unregister_partner(tps->partner); tps->partner = NULL; @@ -1245,6 +1255,29 @@ static void cd321x_update_work(struct work_struct *work) typec_partner_set_identity(tps->partner); } + /* The CD321x firmware owns VDM discovery and Enter Mode. Publish the + * discovered DP mode only after it reports an entered DP connection; + * keep Linux's DP Alt Mode driver from issuing a second Enter Mode. + */ + if (dp_connected && !tps->partner_altmode_dp) { + struct typec_altmode_desc desc = { + .svid = USB_TYPEC_DP_SID, + .mode = USB_TYPEC_DP_MODE, + .vdo = le32_to_cpu(st.dp_sid_status.mode_data), + .mode_selection = true, + }; + + tps->partner_altmode_dp = typec_partner_register_altmode(tps->partner, + &desc); + if (IS_ERR(tps->partner_altmode_dp)) { + ret = PTR_ERR(tps->partner_altmode_dp); + tps->partner_altmode_dp = NULL; + goto retry; + } + } + if (tps->partner_altmode_dp) + typec_altmode_update_active(tps->partner_altmode_dp, dp_connected); + /* Update the TypeC MUX/PHY state */ if (dp_route_was_active && dp_connected && !was_disconnected && !dp_mode_changed && st.dp_sid_event) { @@ -1997,7 +2030,8 @@ static int cd321x_register_port_altmodes(struct cd321x *cd321x) memset(&desc, 0, sizeof(desc)); desc.svid = USB_TYPEC_DP_SID; desc.mode = USB_TYPEC_DP_MODE; - desc.vdo = DP_CONF_SET_PIN_ASSIGN(BIT(DP_PIN_ASSIGN_C) | BIT(DP_PIN_ASSIGN_D)); + desc.vdo = DP_CONF_SET_PIN_ASSIGN(BIT(DP_PIN_ASSIGN_C) | + BIT(DP_PIN_ASSIGN_D) | BIT(DP_PIN_ASSIGN_E)); desc.vdo |= DP_CAP_DFP_D; amode = typec_port_register_altmode(cd321x->tps.port, &desc); if (IS_ERR(amode)) @@ -2111,6 +2145,7 @@ EXPORT_SYMBOL_IF_KUNIT(cd321x_init_work); static int cd321x_register_port(struct tps6598x *tps, struct fwnode_handle *fwnode) { + struct fwnode_handle *connector_fwnode; struct cd321x *cd321x = container_of(tps, struct cd321x, tps); int ret; @@ -2130,9 +2165,12 @@ cd321x_register_port(struct tps6598x *tps, struct fwnode_handle *fwnode) goto err_unregister_altmodes; } - cd321x->connector_fwnode = fwnode_find_reference(fwnode, "displayport", 0); - if (IS_ERR(cd321x->connector_fwnode)) - cd321x->connector_fwnode = NULL; + if (fwnode_property_present(fwnode, "displayport")) + connector_fwnode = fwnode_find_reference(fwnode, "displayport", 0); + else + connector_fwnode = fwnode_handle_get(fwnode); + if (!IS_ERR_OR_NULL(connector_fwnode)) + cd321x->connector_fwnode = connector_fwnode; cd321x->tbt_switch = cd321x_tbt_switch_get(tps->dev, fwnode); if (IS_ERR(cd321x->tbt_switch)) { diff --git a/drivers/usb/typec/tipd/spmi.c b/drivers/usb/typec/tipd/spmi.c index ba89b4b57a255..4f0ceca25ee7f 100644 --- a/drivers/usb/typec/tipd/spmi.c +++ b/drivers/usb/typec/tipd/spmi.c @@ -318,9 +318,14 @@ static int sn201202x_probe(struct spmi_device *device) return ret; ret = tipd_init(tps); - if (ret) + if (ret) { spmi_command_sleep(device); - return ret; + return ret; + } + + /* Events can latch before the IRQ is registered. Consume that state now. */ + tps->data->irq_handler(tps->irq, tps); + return 0; } static void sn201202x_remove(struct spmi_device *device) diff --git a/drivers/usb/typec/tipd/tps6598x.h b/drivers/usb/typec/tipd/tps6598x.h index caac3cb771bd4..f6d51724a1f5d 100644 --- a/drivers/usb/typec/tipd/tps6598x.h +++ b/drivers/usb/typec/tipd/tps6598x.h @@ -371,6 +371,7 @@ struct tps6598x { const struct tipd_data *data; struct tipd_debugfs *debugfs; + struct typec_altmode *partner_altmode_dp; }; /* TPS_REG_USB4_STATUS */ From cb4fb174d50dda2e0f740b11c1b9a13a3678c5b2 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Wed, 28 Oct 2026 08:45:53 +1100 Subject: [PATCH 179/184] drm/apple: clear the desktop 26.6 peer allocation as one field group dcp_peer_hints_26_6() clears the 32-byte allocation block of the display hints (primary and peer endpoint IDs, extra pipe count, flags and the reserved bytes) with memset(&r->primary_id, 0, 32). FORTIFY_SOURCE sees a write of 32 bytes into the 4-byte primary_id field and fails the build with -Werror (__write_overflow_field), so dcp.o does not compile with the Fedora-based release configuration. Group the allocation fields with struct_group_attr(), keeping the packed layout, and clear the group by its size. The offsets are unchanged and a new static_assert pins the group at 32 bytes. Fixes: e3e7131681e2 ("drm/apple: drive a display wider than one pipe with two desktop 26.6 pipes") Assisted-by: LLM Signed-off-by: Ryan Murray (cherry picked from commit e2f5382def10e2b927edcd79a55120c9b1cbac4d) --- drivers/gpu/drm/apple/dcp-peer-v26_6.h | 11 ++++++++--- drivers/gpu/drm/apple/dcp-peer-v26_6.inc | 6 +++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp-peer-v26_6.h b/drivers/gpu/drm/apple/dcp-peer-v26_6.h index df83045cfa73e..3b88d5e040303 100644 --- a/drivers/gpu/drm/apple/dcp-peer-v26_6.h +++ b/drivers/gpu/drm/apple/dcp-peer-v26_6.h @@ -4,6 +4,7 @@ #include #include +#include #include struct dcp_peer_tiling { @@ -78,9 +79,12 @@ static inline bool dcp_peer_tiling_set(struct dcp_peer_tiling *own, struct dcp_display_hints { u8 hints[0x6c]; u8 padding[4]; - __le32 primary_id, peer_id; - u8 extra_pipes, flags; - u8 allocation_reserved[22]; + /* The allocation the AP fills in, or clears when it allocates no peer. */ + struct_group_attr(allocation, __packed, + __le32 primary_id, peer_id; + u8 extra_pipes, flags; + u8 allocation_reserved[22]; + ); __le32 status; u8 tail[12]; } __packed; @@ -88,6 +92,7 @@ struct dcp_display_hints { static_assert(sizeof(struct dcp_display_hints) == 160); static_assert(offsetof(struct dcp_display_hints, primary_id) == 0x70); static_assert(offsetof(struct dcp_display_hints, status) == 0x90); +static_assert(sizeof_field(struct dcp_display_hints, allocation) == 32); /* The widest mode one desktop 26.6 DISPEXT pipe drives (firmware: "max width: 6720"). */ #define DCP_DESKTOP_PIPE_MAX_WIDTH 6720 diff --git a/drivers/gpu/drm/apple/dcp-peer-v26_6.inc b/drivers/gpu/drm/apple/dcp-peer-v26_6.inc index b8038db010943..29c4b6e697ebc 100644 --- a/drivers/gpu/drm/apple/dcp-peer-v26_6.inc +++ b/drivers/gpu/drm/apple/dcp-peer-v26_6.inc @@ -259,7 +259,7 @@ int dcp_peer_hints_26_6(struct apple_dcp *dcp, struct dptx_port *port, width = get_unaligned_le32(r->hints + 0x38); if (!(r->hints[0] & 1) || !width || width <= DCP_DESKTOP_PIPE_MAX_WIDTH) { - memset(&r->primary_id, 0, 32); + memset(&r->allocation, 0, sizeof(r->allocation)); return 0; } @@ -277,7 +277,7 @@ int dcp_peer_hints_26_6(struct apple_dcp *dcp, struct dptx_port *port, (dcp_peer_reject_26_6(dcp, dcp->peer_reserved) & ~BIT(1))) dcp_peer_drop_locked(dcp, dcp->peer_reserved); if (dcp->peer_owner) { - memset(&r->primary_id, 0, 32); + memset(&r->allocation, 0, sizeof(r->allocation)); return 0; } peer = dcp->peer_reserved; @@ -301,7 +301,7 @@ int dcp_peer_hints_26_6(struct apple_dcp *dcp, struct dptx_port *port, dev_info(dcp->dev, "no idle peer pipe for a %u px display\n", width); WRITE_ONCE(dcp->peer_starved, true); dcp_fabric_hybrid_kick(dcp, 0); - memset(&r->primary_id, 0, 32); + memset(&r->allocation, 0, sizeof(r->allocation)); return 0; } WRITE_ONCE(dcp->peer_starved, false); From ca1a1e42eceeb3139c88a3e60e9a710e042caf98 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Wed, 28 Oct 2026 08:24:31 +1100 Subject: [PATCH 180/184] selftests: apple-dcp: follow the hybrid hand-over in the route fixtures The actual-DCP route fixtures compile the real dcp_typec_route_set(), dcp_typec_route_deactivate() and dcp_typec_route_available(). The J616s series added hybrid hand-over state to them. Deactivation now clears peer_starved and typec_narrow, releases a desktop 26.6 peer and restores the fixed output through its own fixed_xbar. A port leaving DP clears typec_yielded and dcp_hybrid_fails and hands every yielded hybrid on the port's routes back to HDMI. Availability asks dcp_typec_hybrid_first(). The mock struct apple_dcp and port had none of this, so seven units no longer compiled. Add the fields and a counter. Extract the real dcp_typec_hybrid_first() over the production defaults (module parameter -1, no DT opt-in), so the policy is as before. Stub the peer release to assert, since no scenario is on desktop 26.6. Let the single-port list walk yield the port's one route when it walks routes. Probe now records the fixed output's crossbar in fixed_xbar as well as xbar (7b990950a349), and deactivation restores the fixed route through fixed_xbar. The mux-release-stages fixed-restore case set only xbar, so set both as probe does. Its assertions are unchanged: the restore is retried once after its failure and the deselect stays balanced. Fixes: 7b990950a349 ("drm/apple: fabric: keep the hybrid pipeline's HDMI crossbar apart from its Type-C route's") Fixes: 0d43a7f471ac ("drm/apple: fabric: hand the hybrid between an HDMI and a two-pipe Type-C display") Assisted-by: LLM Signed-off-by: Ryan Murray (cherry picked from commit e549591ec26d877731f574c98b82b8fd1052cd8d) --- .../drivers/gpu/apple-dcp/accounting.py | 2 +- .../drivers/gpu/apple-dcp/transaction.py | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/accounting.py b/tools/testing/selftests/drivers/gpu/apple-dcp/accounting.py index ce2791cc3a379..9c74ec637be24 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/accounting.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/accounting.py @@ -103,7 +103,7 @@ def resources(ROOT): puts("PASS successful DCP finalizer is not repeated after a later provider failure");return 0; } if(argv[1][0]=='h')hw_failures=1; - if(argv[1][0]=='f') {display.fixed_connector_type=2;display.hdmi_hpd=(void *)1;gpio_level=1;display.xbar=&fixed_xbar;fixed_restore_failures=1;} + if(argv[1][0]=='f') {display.fixed_connector_type=2;display.hdmi_hpd=(void *)1;gpio_level=1;display.xbar=display.fixed_xbar=&fixed_xbar;fixed_restore_failures=1;} assert(typec_mux_set(&mux,&safe)==-EIO&&xbar.lock==1&&port.owner==&route&&port.safe_pending&&!port.safe_finish_ready); bool selected_after_error=route.selected; int ret=typec_mux_set(&mux,&safe); diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/transaction.py b/tools/testing/selftests/drivers/gpu/apple-dcp/transaction.py index 5dea64a19054e..6c40adf5d3a85 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/transaction.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/transaction.py @@ -40,13 +40,21 @@ def check(name, unit, cases=((),), negative=False, flags=()): bool external_power_attempted,dptx_tunnel,tb_clock_ok,fixed_route_selected; int tb_lock; unsigned typec_generation,dptx_dfp_port,dptx_phy,fixed_dptx_phy,fixed_mux_index; void *tb_dpin_set_active,*tb_dpin_ctx,*phy,*fixed_phy; - struct mux_control *xbar; + struct mux_control *xbar,*fixed_xbar; + /* desktop 26.x hybrid hand-over and dual-pipe state; fw_compat is never desktop here */ + bool peer_starved,typec_narrow,hdmi_yielded; struct apple_dcp_typec_route *active_typec_route,*typec_routes; struct connector *connector,*fixed_connector;int connector_type,fixed_connector_type; struct mux_control *direct_xbar_up; bool external_native, ext_dead; int fw_compat; unsigned long long tb_generation; int modes_lock; unsigned long long modes_generation; bool modes_admitted,modes_provisional; struct { int iomfb_method_profile; } hw; ''',1) + # The hybrid hand-over walks a port's routes; the only route is this port's. + actual=actual.replace('struct apple_dcp_typec_port { int link;', 'struct apple_dcp_typec_port { int link,routes; bool typec_yielded;',1) + actual=actual.replace('#define list_for_each_entry(p,h,m) for((void)(h),(p)=sole_port();(p);(p)=NULL)', + '#define list_for_each_entry(p,h,m) for((void)(h),(p)=_Generic((p),struct apple_dcp_typec_route *:sole_route(),default:sole_port());(p);(p)=NULL)',1) + actual=actual.replace('static struct apple_dcp_typec_port *sole_port(void) { return &port; }', + 'static struct apple_dcp_typec_port *sole_port(void) { return &port; }\nstatic struct apple_dcp_typec_route * __attribute__((unused)) sole_route(void) { return &route; }',1) actual=actual.replace('bool applied_valid,safe_pending,dp_wanted,dp_hpd,hpd;', 'struct typec_altmode *applied_alt; unsigned long applied_mode; unsigned applied_status,applied_conf; bool applied_valid,safe_pending,dp_wanted,dp_hpd,hpd;',1) actual=actual.replace('void *xbar; bool deactivate_xbar_done; int deactivate_error; bool selected,tunnel;', 'struct mux_control *xbar,*active_xbar; void *phy; unsigned dptx_phy,tunnel_dpin,mux_index;unsigned long long tunnel_generation;bool deactivate_xbar_done; int deactivate_error;bool xbar_up,selected,tunnel;',1) extra=r''' @@ -70,6 +78,10 @@ def check(name, unit, cases=((),), negative=False, flags=()): static int mux_control_deselect(struct mux_control *m) { (void)m;event('C');xbar_calls++;return xbar_error; } static int mux_control_select(struct mux_control *m,unsigned n) { (void)m;(void)n;return 0; } static void dpavservep_detach(struct apple_dcp *d) { (void)d; } + /* Desktop 26.6 dual-pipe peers: no scenario here is on that firmware. */ + static void __attribute__((unused)) dcp_peer_release_26_6(struct apple_dcp *d) { (void)d;assert(!"desktop 26.6 peer release"); } + /* failed takes of the hybrid, reset when a Type-C display leaves DP */ + static unsigned int __attribute__((unused)) dcp_hybrid_fails; static void dcp_firmware_metadata_connection(struct apple_dcp *d,unsigned n,bool c) { (void)d;(void)n;(void)c; } static bool dcp_typec_keep_order(void) { return false; } static struct apple_dcp_typec_route * __attribute__((unused)) dcp_typec_lowest_free(struct apple_dcp_typec_port *p) { (void)p;return &route; } @@ -115,6 +127,11 @@ def check(name, unit, cases=((),), negative=False, flags=()): '''+available # Route-follow: the fixed output, or a failed T6030 external processor, keeps the route busy. available+=apple_function(ROOT,'dcp_typec_route_busy') + # Hybrid-first policy: the real helper over the module parameter's and the + # device tree's defaults (-1: from the DT; no apple,typec-hybrid-first here). + hybrid_first=apple_function(ROOT,'dcp_typec_hybrid_first') + if hybrid_first: + available='static int typec_hybrid_first=-1;\n static bool typec_hybrid_first_dt;\n '+hybrid_first+available if available: actual=actual.replace('struct apple_dcp { int external_lock,external_error;','struct apple_dcp { int external_lock,external_error;\n struct dcp_fabric_presence hdmi_presence; int hdmi_presence_lock; unsigned nr_typec_routes;',1) available='''#define atomic_read(p) (*(p)) From f139b700f150f0a8182fc037c7c7dc4c87eb96e1 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Wed, 28 Oct 2026 08:24:31 +1100 Subject: [PATCH 181/184] selftests: apple-dcp: follow adaptive sync on desktop 26.6 Desktop 26.6 now takes adaptive sync with a mode set. 562eb4c4087d removed dcp_mode_vrr_supported(), which the publication fixture extracted unconditionally. 6477da65cee5 made dcp_crtc_atomic_check() turn a VRR_ENABLED change on that firmware into a mode set, through drm_atomic_get_old_crtc_state() and crtc_state->mode_changed, which the VRR admission fixture did not model. So publication-hpd failed to build its unit and vrr-rejection did not compile. Extract the VRR filter only where the source still has it. Give the VRR fixture the desktop firmware value, mode_changed and an old-state lookup that counts its calls. The existing 72 admission cases run unchanged and now also check that no other firmware looks at the old state or forces a mode set. Where the source has the desktop path, 12 more cases pin it: an enabled CRTC whose VRR_ENABLED differs from its old state gets mode_changed, and nothing else does (a disabled CRTC, an unchanged VRR_ENABLED, no old state). Fixes: 562eb4c4087d ("drm/apple: enable adaptive sync on desktop 26.6 external displays") Fixes: 6477da65cee5 ("drm/apple: enable adaptive sync on the desktop 26.6 internal panel") Assisted-by: LLM Signed-off-by: Ryan Murray (cherry picked from commit 6822701ad2722a56da72ac27cfb0e6561b901029) --- .../drivers/gpu/apple-dcp/lifecycle.py | 47 +++++++++++++++++-- .../selftests/drivers/gpu/apple-dcp/rate.py | 9 ++-- 2 files changed, 46 insertions(+), 10 deletions(-) diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py b/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py index 440b5202f670a..24f95a4453053 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py @@ -1672,7 +1672,8 @@ def vrr(root): struct apple_connector { bool connected; }; /* The native T6030 14.7 backend is never selected in these scenarios. */ enum dcp_firmware_version { DCP_FIRMWARE_UNKNOWN, DCP_FIRMWARE_V_12_3, DCP_FIRMWARE_V_13_5, - DCP_FIRMWARE_V_14_7, DCP_FIRMWARE_V_14_7_TEMPLATE, DCP_FIRMWARE_V_26_6 }; + DCP_FIRMWARE_V_14_7, DCP_FIRMWARE_V_14_7_TEMPLATE, DCP_FIRMWARE_V_26_6, + DCP_FIRMWARE_V_26_6_DESKTOP }; struct apple_dcp { bool crashed, external, external_native, quiescing; unsigned int nr_typec_routes; @@ -1685,14 +1686,19 @@ def vrr(root): struct platform_device { struct apple_dcp *data; }; struct drm_crtc { struct platform_device *dcp; void *primary; }; struct drm_display_mode { int unused; }; -struct drm_crtc_state { bool enable, vrr_enabled, modeset, color_mgmt_changed; struct drm_display_mode mode; void *ctm, *degamma_lut, *gamma_lut; unsigned plane_mask; }; +struct drm_crtc_state { bool enable, vrr_enabled, modeset, mode_changed, color_mgmt_changed; struct drm_display_mode mode; void *ctm, *degamma_lut, *gamma_lut; unsigned plane_mask; }; /* mainline 7.2 renamed struct drm_atomic_state to struct drm_atomic_commit */ -struct drm_atomic_commit { struct drm_crtc_state crtc; }; +struct drm_atomic_commit { struct drm_crtc_state crtc, old_crtc; bool no_old_crtc; }; #define to_apple_crtc(x) (x) #define platform_get_drvdata(x) ((x)->data) static struct drm_crtc_state *drm_atomic_get_new_crtc_state( struct drm_atomic_commit *s, struct drm_crtc *c) { (void)c; return &s->crtc; } +/* Only the desktop 26.6 firmware compares against the old CRTC state. */ +static unsigned int old_state_lookups; +static struct drm_crtc_state * __attribute__((unused)) drm_atomic_get_old_crtc_state( + struct drm_atomic_commit *s, struct drm_crtc *c) +{ (void)c; old_state_lookups++; return s->no_old_crtc ? NULL : &s->old_crtc; } static bool drm_atomic_crtc_needs_modeset(struct drm_crtc_state *s) { return s->modeset; } static int __attribute__((unused)) iomfb_v14_7_atomic_check(struct apple_dcp *d, @@ -1753,14 +1759,45 @@ def vrr(root): } cases++; } - dcp.crashed = true; + /* No other firmware turns a VRR change into a mode set. */ + assert(!old_state_lookups && !state.crtc.mode_changed); +DESKTOP_VRR dcp.crashed = true; state.crtc.vrr_enabled = false; assert(dcp_crtc_atomic_check(&crtc, &state) == -EINVAL); printf("PASS: %u production atomic VRR admission cases and crashed-device rejection\n", cases); return 0; } """ - return prefix + check + suffix + desktop = '' + if 'DCP_FIRMWARE_V_26_6_DESKTOP' in check: + # Desktop 26.6 applies adaptive sync with a mode set: a VRR_ENABLED + # change of an enabled CRTC must become one, nothing else may. + desktop = r""" dcp.fw_compat = DCP_FIRMWARE_V_26_6_DESKTOP; + dcp.hw.iomfb_method_profile = 0; + dcp.mode_state.valid = true; + dcp.connector = &connector; + connector.connected = true; + state.crtc.modeset = false; + for (unsigned int enable = 0; enable < 2; enable++) + for (unsigned int old_vrr = 0; old_vrr < 3; old_vrr++) + for (unsigned int vrr = 0; vrr < 2; vrr++) { + state.crtc.enable = enable; + state.crtc.vrr_enabled = vrr; + state.crtc.mode_changed = false; + state.no_old_crtc = old_vrr == 2; + state.old_crtc.vrr_enabled = old_vrr == 1; + assert(dcp_crtc_atomic_check(&crtc, &state) == 0); + bool expected = enable && old_vrr != 2 && (old_vrr == 1) != vrr; + if (state.crtc.mode_changed != expected) { + fprintf(stderr, "desktop enable=%u old_vrr=%u vrr=%u: mode_changed=%d expected %d\n", + enable, old_vrr, vrr, state.crtc.mode_changed, expected); + return 1; + } + cases++; + } + dcp.fw_compat = DCP_FIRMWARE_UNKNOWN; +""" + return prefix + check + suffix.replace('DESKTOP_VRR', desktop, 1) def power_reply(root): diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py b/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py index 466688aff3638..c307c9e51ff28 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py @@ -694,12 +694,9 @@ def publication(root): #define DCP_FIRMWARE_V_14_7 (DCP_FIRMWARE_H17P + 1) #define DCP_FIRMWARE_V_14_7_TEMPLATE (DCP_FIRMWARE_H17P + 2) #endif -/* Nor the desktop 26.6 firmware, the one that withholds VRR modes. */ +/* Nor the desktop 26.6 firmware. */ #define DCP_FIRMWARE_V_26_6_DESKTOP (DCP_FIRMWARE_H17P + 3) #define DIV_ROUND_UP(n,d) (((n)+(d)-1)/(d)) -/* This fixture selects an external H17P sink, never desktop adaptive sync. */ -static bool dcp_desktop_vrr_supported(struct apple_dcp *d) -{ assert(d->fw_compat != DCP_FIRMWARE_V_26_6_DESKTOP); return false; } static void dcp_update_connector_dimensions(struct apple_dcp *d,struct drm_connector *c) { (void)c;assert(d->fw_compat != DCP_FIRMWARE_V_26_6_DESKTOP); } '''+firmware_lineage(root)+internal_inline(root,'dcp_fw_is_14_7')+r'''/* Off T6030 machines the direct crossbar output is not clocked separately. */ @@ -723,7 +720,9 @@ def publication(root): + ''.join(apple_function(root, name) for name in ('dcp_queue_hotplug', 'dcp_modes_for_connector', 'apple_connector_invalidate_edid', 'apple_connector_edid_set_live')) + mode_helpers) - publication = block(iomfb,'static void dcp_clear_connector_edid(')+'\n'+block(iomfb,'static void dcp_consume_edid_invalidation(')+mode_helpers+block(iomfb,'static bool dcp_mode_vrr_supported(')+'\n'+block(iomfb,'int dcp_get_modes(')+block(iomfb,'bool lookup_mode(') + # The desktop 26.6 VRR filter is gone once that firmware takes adaptive sync. + vrr_filter = block(iomfb,'static bool dcp_mode_vrr_supported(')+'\n' if 'static bool dcp_mode_vrr_supported(' in iomfb else '' + publication = block(iomfb,'static void dcp_clear_connector_edid(')+'\n'+block(iomfb,'static void dcp_consume_edid_invalidation(')+mode_helpers+vrr_filter+block(iomfb,'int dcp_get_modes(')+block(iomfb,re.search(r'^[^\n;(]*\blookup_mode\(',iomfb,re.M).start()) # Drive malformed replacements through the same chunk/end ingress as firmware. # The dictionary cache is a boundary substitute and takes ownership of the blob. receipt = r''' From 3fb694ddf0ded4d39c649d6b01fd8155a739372b Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Wed, 28 Oct 2026 12:31:40 +1100 Subject: [PATCH 182/184] selftests: apple-dcp: follow #451's desktop adaptive sync in batch5 The batch5 merge takes #451's desktop 26.6 adaptive sync and removes the J616s series' parameter sequence (dcp_modeset_sync(), its fixed-refresh fallback on a rejected VRR mode set) and its admission gate (dcp_desktop_vrr_check(), dcp_desktop_vrr_supported()). Retire panel-adaptive-fallback, which drove only that removed sequence; vrr-rejection now pins #451's VRR_ENABLED-to-mode-set admission. Drop the stubs other fixtures kept for the removed functions (desktop-unknown-modeset-recovery, vrr-rejection), which no longer compile as unused. In the publication fixture, take lookup_mode() by its definition: the J616s takeover check starts a line with a lookup_mode() call, which the line-start pattern matched first. panel-adaptive-watchdog is unchanged: the fixed-refresh latch after an adaptive swap times out remains. Assisted-by: LLM Signed-off-by: Ryan Murray --- .../drivers/gpu/apple-dcp/desktop.py | 5 +- .../drivers/gpu/apple-dcp/display.py | 122 ------------------ .../drivers/gpu/apple-dcp/lifecycle.py | 3 - .../selftests/drivers/gpu/apple-dcp/rate.py | 2 +- .../selftests/drivers/gpu/apple-dcp/run.py | 1 - 5 files changed, 2 insertions(+), 131 deletions(-) diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py index 0b00a6a81d33f..b38fb5e951997 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py @@ -329,12 +329,9 @@ def modeset_unknown(root): template = (root / 'drivers/gpu/drm/apple/iomfb_template.c').read_text() unit = modeset_unmap(root) helper = block(template, 'static bool dcp_modeset_callback_failed(') - # Keep the real modeset completion and its callback-error check. The - # adaptive retry boundary must never be reached after an unknown callback. + # Keep the real modeset completion and its callback-error check. helper += r''' struct dcp_modeset_cookie {struct dcp_wait_cookie wait;bool vrr;}; -static void dcp_modeset_fixed_fallback(struct apple_dcp *d,struct dcp_modeset_cookie *m) -{(void)d;(void)m;assert(false);} ''' + block(template, 'static void dcp_modeset_actual_done(') unit = unit.replace('int main(void)', helper + '\nint main(void)') tests = r''' diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/display.py b/tools/testing/selftests/drivers/gpu/apple-dcp/display.py index 1236325213994..83f29c1ad7be1 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/display.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/display.py @@ -195,128 +195,6 @@ def refresh(root): ''' -def adaptive(root): - source=(root/'drivers/gpu/drm/apple/iomfb_template.c').read_text() - funcs='\n'.join(block(source, marker) for marker in ( - 'static bool dcp_modeset_callback_failed(', - 'static void dcp_modeset_fixed_fallback(', - 'static void dcp_modeset_actual_done(', - 'static void dcp_modeset_after_actual_info(', - 'static void dcp_modeset_after_bootstrap(', - 'static void dcp_modeset_after_bootstrap_info(', - 'static void dcp_modeset_select(', - 'static void dcp_modeset_sync_done(', - 'static void dcp_modeset_sync(')) - cookie=block(source,'struct dcp_modeset_cookie {')+';\n' - main=(root/'drivers/gpu/drm/apple/iomfb.c').read_text() - gate=block(main,'bool dcp_desktop_vrr_supported(') + block(main,'int dcp_desktop_vrr_check(') - return r''' -#include -#include -#include -#include -#include -typedef uint32_t u32; -typedef uint64_t u64; -#define READ_ONCE(x) (x) -#define WRITE_ONCE(x,v) ((x)=(v)) -#define DCP_FIRMWARE_V_26_6_DESKTOP 5 -#define IOMFBPARAM_ADAPTIVE_SYNC 14 -#define dev_warn(...) ((void)0) -struct dcp_wait_cookie { u32 status; bool done,replied; }; -struct dcp_set_digital_out_mode_req {u32 color_mode_id,timing_mode_id;}; -struct dcp_set_parameter_dcp { u32 param; u64 value[4]; u32 count; }; -struct apple_dcp {void *dev;int fw_compat;bool main_display,has_panel;struct {bool has_vrr;} panel;struct {bool vrr_failed,modeset_callback_error;} *desktop;}; -struct drm_display_mode {int id;}; -struct drm_crtc_state {bool vrr_enabled;struct drm_display_mode mode;}; -struct dcp_display_mode {bool vrr;}; -static bool lookup_mode(struct apple_dcp *d,struct drm_display_mode *m,struct dcp_display_mode *out) -{out->vrr=m->id==120;return m->id==60 || m->id==120;} -static bool dcp_has_panel(struct apple_dcp *d) {return d->has_panel;} -static u32 get_unaligned_le32(const void *p) {u32 v;memcpy(&v,p,4);return v;} -typedef void (*callback)(struct apple_dcp *,void *,void *); -static callback pending; -static void *cookie_pending; -static unsigned kind, parameters,infos,modes; -static struct dcp_set_parameter_dcp parameter; -static struct dcp_set_digital_out_mode_req requested; -static void dcp_modeset_select(struct apple_dcp *,void *,void *); -static void dcp_modeset_sync(struct apple_dcp *,void *); -static void dcp_set_parameter_dcp(struct apple_dcp *d,bool oob, - struct dcp_set_parameter_dcp *p,callback cb,void *c) -{assert(!pending && !oob);parameter=*p;pending=cb;cookie_pending=c;kind=1;parameters++;} -static void dcp_get_mode_info(struct apple_dcp *d,const struct dcp_set_digital_out_mode_req *m,callback cb,void *c) -{assert(!pending);requested=*m;pending=cb;cookie_pending=c;kind=2;infos++;} -static void dcp_set_digital_out_mode(struct apple_dcp *d,bool oob, - struct dcp_set_digital_out_mode_req *m,callback cb,void *c) -{assert(!pending && !oob);requested=*m;pending=cb;cookie_pending=c;kind=3;modes++;} -static void complete_set_digital_out_mode(struct apple_dcp *d,void *p,void *c) -{struct dcp_wait_cookie *w=c;w->done=true;w->replied=!!p;if(p)w->status=get_unaligned_le32(p);} -''' + cookie + gate + funcs + r''' -static void reply(struct apple_dcp *d,unsigned expected,u32 *status) -{assert(kind==expected && pending);callback cb=pending;void *c=cookie_pending;pending=NULL;cb(d,status,c);} -static void initialize(struct dcp_modeset_cookie *m) -{memset(m,0,sizeof(*m));m->vrr=true;m->min_vrr=24U<<16;m->actual=(struct dcp_set_digital_out_mode_req){11,12};m->bootstrap=(struct dcp_set_digital_out_mode_req){13,12};} -static void selected(struct apple_dcp *d,struct dcp_modeset_cookie *m) -{u32 zero=0;reply(d,2,&zero);assert(requested.color_mode_id==11);reply(d,3,&zero);assert(m->wait.done && m->wait.replied && !m->wait.status);} -int main(void) -{ - struct apple_dcp d={.fw_compat=DCP_FIRMWARE_V_26_6_DESKTOP,.main_display=true,.has_panel=true}; - __typeof__(*d.desktop) desktop={0};d.desktop=&desktop; - struct drm_crtc_state state={.vrr_enabled=true,.mode={120}}; - assert(dcp_desktop_vrr_check(&d,&state)==-EINVAL); - assert(!dcp_desktop_vrr_supported(&d));d.panel.has_vrr=true; - assert(!dcp_desktop_vrr_check(&d,&state)); - state.mode.id=60;assert(dcp_desktop_vrr_check(&d,&state)==-EINVAL); - state.vrr_enabled=false;assert(!dcp_desktop_vrr_check(&d,&state)); - state.vrr_enabled=true;state.mode.id=120; - assert(dcp_desktop_vrr_supported(&d)); - d.main_display=false;assert(!dcp_desktop_vrr_supported(&d));d.main_display=true; - d.has_panel=false;assert(!dcp_desktop_vrr_supported(&d));d.has_panel=true; - d.fw_compat=3;assert(!dcp_desktop_vrr_supported(&d));d.fw_compat=DCP_FIRMWARE_V_26_6_DESKTOP; - struct dcp_modeset_cookie m;u32 zero=0,error=1; - initialize(&m);dcp_modeset_sync(&d,&m); - assert(parameter.param==14 && parameter.count==3 && parameter.value[0]==24U<<16); - assert(!parameter.value[1] && !parameter.value[2] && parameter.value[3]==0xaaaaaaaaaaaaaaaaULL); - reply(&d,1,&zero);selected(&d,&m);assert(m.vrr && !desktop.vrr_failed); - /* Rejected enable is cleared before a fixed timing is selected. */ - initialize(&m);dcp_modeset_sync(&d,&m);reply(&d,1,&error); - assert(desktop.vrr_failed && !dcp_desktop_vrr_supported(&d) && !m.vrr && !parameter.value[0]); - assert(!dcp_desktop_vrr_check(&d,&state)); /* Recovery re-applies fixed refresh. */ - reply(&d,1,&zero);selected(&d,&m); - /* A rejected modeset gets one fixed-refresh retry. */ - desktop.vrr_failed=false;initialize(&m);dcp_modeset_sync(&d,&m); - reply(&d,1,&zero);reply(&d,2,&zero);reply(&d,3,&error); - assert(!m.vrr && desktop.vrr_failed && kind==1 && !parameter.value[0]); - reply(&d,1,&zero);selected(&d,&m); - /* Failed disable and absent replies must stop, without claiming success. */ - initialize(&m);dcp_modeset_sync(&d,&m);reply(&d,1,&error);reply(&d,1,&error); - assert(m.wait.done && m.wait.status && !pending); - initialize(&m);dcp_modeset_sync(&d,&m);reply(&d,1,NULL); - assert(m.wait.done && !m.wait.replied && !pending); - /* Bootstrap keeps the DCP's actual color/timing identities. */ - desktop.vrr_failed=false;initialize(&m);m.use_bootstrap=true; - dcp_modeset_sync(&d,&m);reply(&d,1,&zero); - assert(requested.color_mode_id==13 && requested.timing_mode_id==12); - reply(&d,2,&zero);reply(&d,3,&zero);selected(&d,&m); - /* Unsupported callbacks at every chained RPC stop after its real ACK. */ - for(unsigned phase=0;phase<5;phase++) { - desktop.vrr_failed=false;desktop.modeset_callback_error=false; - initialize(&m);m.use_bootstrap=true;dcp_modeset_sync(&d,&m); - if(phase>0)reply(&d,1,&zero); - if(phase>1)reply(&d,2,&zero); - if(phase>2)reply(&d,3,&zero); - if(phase>3)reply(&d,2,&zero); - desktop.modeset_callback_error=true; - reply(&d,kind,&zero); - assert(m.wait.done && !m.wait.replied && !pending && !desktop.vrr_failed); - } - assert(parameters && infos && modes); - return 0; -} -''' - - def takeover(root): prefix=backlight(root).split('static void completed(')[0] prefix=prefix.replace('static inline ', 'static inline __attribute__((unused)) ') diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py b/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py index 24f95a4453053..c8d1c230fa329 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py @@ -1719,9 +1719,6 @@ def vrr(root): { (void)blob; assert(!"gamma LUT without a colour-management change"); return 0; } static unsigned int __attribute__((unused)) dcp_gamma_lut_size(struct platform_device *p) { (void)p; assert(!"gamma LUT without a colour-management change"); return 0; } -/* Desktop admission is covered by panel-adaptive-fallback. */ -static int dcp_desktop_vrr_check(struct apple_dcp *d, struct drm_crtc_state *s) -{ (void)d; (void)s; return 0; } /* Route-follow is for Type-C displays; this DCP has no Type-C routes. */ static bool __attribute__((unused)) typec_follow_crtc = true; static int __attribute__((unused)) dcp_typec_follow_check(struct apple_dcp *d, struct drm_crtc *c, diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py b/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py index c307c9e51ff28..86e7a1fd44e61 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py @@ -722,7 +722,7 @@ def publication(root): + mode_helpers) # The desktop 26.6 VRR filter is gone once that firmware takes adaptive sync. vrr_filter = block(iomfb,'static bool dcp_mode_vrr_supported(')+'\n' if 'static bool dcp_mode_vrr_supported(' in iomfb else '' - publication = block(iomfb,'static void dcp_clear_connector_edid(')+'\n'+block(iomfb,'static void dcp_consume_edid_invalidation(')+mode_helpers+vrr_filter+block(iomfb,'int dcp_get_modes(')+block(iomfb,re.search(r'^[^\n;(]*\blookup_mode\(',iomfb,re.M).start()) + publication = block(iomfb,'static void dcp_clear_connector_edid(')+'\n'+block(iomfb,'static void dcp_consume_edid_invalidation(')+mode_helpers+vrr_filter+block(iomfb,'int dcp_get_modes(')+block(iomfb,'bool lookup_mode(') # Drive malformed replacements through the same chunk/end ingress as firmware. # The dictionary cache is a boundary substitute and takes ownership of the blob. receipt = r''' diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/run.py b/tools/testing/selftests/drivers/gpu/apple-dcp/run.py index b78b93def55b2..efc79d1345f7a 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/run.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/run.py @@ -70,7 +70,6 @@ def main(): ('display', 'desktop-powerlog-framing', desktop.powerlog_framing, ((),), ()), ('display', 'desktop-brightness-keys-reports', desktop.keys_and_reports, ((),), ()), ('display', 'panel-refresh-range', display.refresh, ((),), ()), - ('display', 'panel-adaptive-fallback', display.adaptive, ((),), ()), ('display', 'panel-adaptive-watchdog', display.watchdog, ((),), ()), ('m4', 'm4-wire-golden', lambda r: m4.golden_check(r, output), (), ()), ('m4', 'm4-protocol', m4.protocol, ((),), ('-Wno-unused-function', '-Wno-unused-const-variable')), From 8f421b0716acfeeab173c4391109b2d8ffadac85 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Wed, 28 Oct 2026 12:50:29 +1100 Subject: [PATCH 183/184] drm/apple: Derive an EDID for T6041 external displays The external display processors of the M4 Pro/Max (apple,t6041-dcpext, desktop 26.6 IOMFB) announce no dcpav-service-epic, so the EDID read through the DCPAV service never runs and every connector they drive has an empty EDID. Userspace then names the monitor "Unknown". The firmware still exports the TimingElements and DisplayAttributes of each sink it drives, including the sink's ProductAttributes. Build the base EDID block from them as the T8140 (H17P) external path already does: enable the firmware metadata for the desktop external processors, check the property extent and parse the sink identity from DisplayAttributes, and read the IsPreferred, IsInterlaced, SyncPolarity and PixelRepetition keys of each timing so the preferred timing can be described. The published modes keep their flags; only the EDID's detailed timing takes the polarity. These processors have no cable generation of their own (their HDMI output has no Type-C session), so count sinks instead: the first is the one found at boot, and each cable attach or detach, Type-C route change or HDMI unplug starts a new one, which an earlier sink's metadata can never match. A firmware HPD loss alone does not drop it, as the link can bounce without the sink being described again. The H17P external, the other DCPs and the internal panels are unchanged. The firmware 27 path of t6050/dcp gets its EDID through the DCPAV service and its display product query only identifies an Apple display, so there was nothing there to port to 26.6. Assisted-by: LLM Signed-off-by: Ryan Murray (cherry picked from commit 221e2ac5125ba243ccfae1a2751c11063e1dd851) --- drivers/gpu/drm/apple/dcp-internal.h | 7 ++ drivers/gpu/drm/apple/dcp.c | 53 ++++++++- drivers/gpu/drm/apple/iomfb.c | 1 + drivers/gpu/drm/apple/iomfb_template.c | 7 +- drivers/gpu/drm/apple/parser-test.c | 104 ++++++++++++++++++ drivers/gpu/drm/apple/parser.c | 25 +++-- drivers/gpu/drm/apple/parser.h | 8 ++ .../selftests/drivers/gpu/apple-dcp/rate.py | 5 +- 8 files changed, 197 insertions(+), 13 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index ded53559c0882..da06e5e21eab5 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -217,6 +217,12 @@ struct apple_dcp_hw_data { * (the T8122 dcpext), see dcp_fw_is_14_7(). */ bool fw_14_7_endpoints; + /* + * An external processor with no DCPAV service to read the EDID + * through, whose sinks' EDID is built from their firmware metadata + * (desktop 26.x), see dcp_sink_metadata(). + */ + bool sink_metadata_edid; }; enum dcp_test_point { @@ -808,6 +814,7 @@ static inline bool dcp_fail_stop(const struct apple_dcp *dcp) iomfb_is_m4(dcp); } +bool dcp_firmware_metadata_enabled(struct apple_dcp *dcp); void dcp_firmware_metadata_connection(struct apple_dcp *dcp, u64 generation, bool connected); void dcp_firmware_metadata_hotplug(struct apple_dcp *dcp, bool connected); void dcp_firmware_metadata_announced(struct apple_dcp *dcp); diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 44daeb982ba5c..b6af560ffc50a 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -556,9 +556,39 @@ void dcp_drm_crtc_page_flip(struct apple_dcp *dcp, ktime_t now) spin_unlock_irqrestore(&crtc->base.dev->event_lock, flags); } -static bool dcp_firmware_metadata_enabled(struct apple_dcp *dcp) +/* + * A desktop 26.x external processor has no dcpav-service-epic to read the + * sink's EDID through, but exports the TimingElements and DisplayAttributes + * of every sink it drives, on HDMI and Type-C alike. + */ +static bool dcp_sink_metadata(struct apple_dcp *dcp) { - return dcp->hw.iomfb_method_profile == DCP_IOMFB_METHODS_H17P_EXTERNAL; + return dcp->hw.sink_metadata_edid; +} + +bool dcp_firmware_metadata_enabled(struct apple_dcp *dcp) +{ + return dcp->hw.iomfb_method_profile == DCP_IOMFB_METHODS_H17P_EXTERNAL || + dcp_sink_metadata(dcp); +} + +/* + * Such metadata belongs to the sink the firmware reports, as these + * processors have no cable generation of their own: each sink change starts + * a new connection, which the metadata of an earlier sink can never match. + */ +static void dcp_desktop_metadata_new_sink(struct apple_dcp *dcp) +{ + unsigned long flags; + + spin_lock_irqsave(&dcp->metadata.lock, flags); + dcp->metadata.attributes_valid = false; + dcp->metadata.timing_valid = false; + dcp->metadata.epoch = (struct dcp_metadata_epoch) { + .generation = dcp->metadata.epoch.generation + 1, + .connected = true, + }; + spin_unlock_irqrestore(&dcp->metadata.lock, flags); } void dcp_firmware_metadata_connection(struct apple_dcp *dcp, u64 generation, bool connected) @@ -567,6 +597,10 @@ void dcp_firmware_metadata_connection(struct apple_dcp *dcp, u64 generation, boo if (!dcp_firmware_metadata_enabled(dcp)) return; + if (dcp_sink_metadata(dcp)) { + dcp_desktop_metadata_new_sink(dcp); + return; + } spin_lock_irqsave(&dcp->metadata.lock, flags); if (generation != dcp->metadata.epoch.generation || !connected) { dcp->rate_proof.connection_sequence++; @@ -639,6 +673,12 @@ void dcp_firmware_metadata_hotplug(struct apple_dcp *dcp, bool connected) if (!dcp_firmware_metadata_enabled(dcp)) return; + /* + * A desktop 26.x link can bounce without the firmware describing its + * sink again: the cable and HDMI HPD paths report a sink change. + */ + if (dcp_sink_metadata(dcp)) + return; spin_lock_irqsave(&dcp->metadata.lock, flags); if (connected) { dcp_firmware_metadata_present_locked(dcp); @@ -729,6 +769,8 @@ void dcp_firmware_metadata_timing(struct apple_dcp *dcp) !dcp->modes[i].metadata_timing_valid) continue; dcp->metadata.preferred = dcp->modes[i].mode; + /* Desktop 26.x publishes its modes without their polarity. */ + dcp->metadata.preferred.flags |= dcp->modes[i].metadata_sync_flags; dcp->metadata.timing_ticket = dcp->chunks.metadata_ticket; dcp->metadata.timing_valid = true; break; @@ -2890,6 +2932,9 @@ static void disconnected_hpd_event(struct apple_connector *con) struct apple_dcp *dcp = platform_get_drvdata(pdev); WRITE_ONCE(dcp->ext_backlight, false); + /* Nothing else reports an HDMI unplug to the firmware metadata. */ + if (dcp_sink_metadata(dcp)) + dcp_desktop_metadata_new_sink(dcp); } /* May run with DRM modeset locks held; defer property/cache cleanup. */ @@ -5536,6 +5581,9 @@ static int dcp_platform_probe_pipe(struct platform_device *pdev) mutex_init(&dcp->dcpavserv.lock); INIT_WORK(&dcp->dcpavserv.close_wq, dcpavserv_close_work); dcp->hw = *(struct apple_dcp_hw_data *)of_device_get_match_data(dev); + /* The display such a processor finds at boot is its first sink. */ + if (dcp_sink_metadata(dcp)) + dcp_desktop_metadata_new_sink(dcp); if (dcp->hw.iomfb_method_profile == DCP_IOMFB_METHODS_H17P_EXTERNAL) { if (!dcp->hw.firmware_clock || @@ -6435,6 +6483,7 @@ static const struct apple_dcp_hw_data apple_dcp_hw_t6041 = { static const struct apple_dcp_hw_data apple_dcp_hw_t6041_dcpext = { .num_dptx_ports = 2, + .sink_metadata_edid = true, }; static const struct of_device_id of_match[] = { diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 1b2f30b2260d1..0d266130531ed 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -1740,6 +1740,7 @@ int dcp_modes_replace(struct apple_dcp *dcp, struct dcp_parse_ctx *ctx, ret = parse_array_count(ctx, &entries); if (ret) return ret; + ctx->metadata_timing = dcp_firmware_metadata_enabled(dcp); } ctx->zero_vrr_range_fallback = dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP; modes = enumerate_modes(ctx, &nr_modes, dcp->width_mm, dcp->height_mm, diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index ef72f496531d3..9d248cccf72da 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -1264,7 +1264,7 @@ static bool dcpep_process_chunks(struct apple_dcp *dcp, } /* Validate serialized extent outside the metadata spinlock. */ - if (dcp->hw.iomfb_method_profile == DCP_IOMFB_METHODS_H17P_EXTERNAL) + if (dcp_firmware_metadata_enabled(dcp)) dcp->chunks.payload_complete = dcp->chunks.contiguous && dcp_property_complete(dcp->chunks.data, dcp->chunks.received, dcp->chunks.length); @@ -1329,10 +1329,13 @@ static bool dcpep_process_chunks(struct apple_dcp *dcp, return false; } - if (dcp->hw.iomfb_method_profile == DCP_IOMFB_METHODS_H17P_EXTERNAL) { + if (dcp_firmware_metadata_enabled(dcp)) { struct dcp_display_metadata metadata; ret = parse_display_metadata(&metadata_ctx, &metadata); + if (ret) + dev_dbg(dcp->dev, "DisplayAttributes without sink identity: %d\n", + ret); dcp_firmware_metadata_attributes(dcp, ret ? NULL : &metadata); } else if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && dcp_has_panel(dcp)) { diff --git a/drivers/gpu/drm/apple/parser-test.c b/drivers/gpu/drm/apple/parser-test.c index dca33251ad5ed..dc807a2013a43 100644 --- a/drivers/gpu/drm/apple/parser-test.c +++ b/drivers/gpu/drm/apple/parser-test.c @@ -8,6 +8,7 @@ #include #include "dcp.h" +#include #include #include "connector.h" @@ -1531,6 +1532,108 @@ static void parser_panel_j616s_publication(struct kunit *test) dcp_modes_release(dcp); } +static void blob_timing_dimension(struct parser_blob *blob, const char *key, + s64 active, s64 total, s64 porch, s64 sync, s64 rate) +{ + blob_key(blob, key); + blob_tag(blob, 1, 7); + blob_int(blob, "Active", active); + blob_int(blob, "Total", total); + blob_int(blob, "FrontPorch", porch); + blob_int(blob, "SyncWidth", sync); + blob_int(blob, "PreciseSyncRate", rate); + blob_int(blob, "SyncPolarity", 1); + blob_int(blob, "PixelRepetition", 0); +} + +/* A desktop 26.x sink timing: the keys the firmware-derived EDID needs. */ +static struct parser_blob *make_timing_blob(struct kunit *test) +{ + struct parser_blob *blob = kunit_kzalloc(test, sizeof(*blob), GFP_KERNEL); + + if (!blob) + return NULL; + put_unaligned_le32(0xd3, blob->data); + blob->size = 4; + blob_tag(blob, 2, 1); + blob_tag(blob, 1, 8); + blob_bool(blob, "IsVirtual", false); + blob_int(blob, "ID", 7); + blob_int(blob, "Score", 10); + blob_timing_dimension(blob, "HorizontalAttributes", 1920, 2200, 88, 44, + (675U << 16) / 10); + blob_timing_dimension(blob, "VerticalAttributes", 1080, 1125, 4, 5, 60 << 16); + blob_key(blob, "ColorModes"); + blob_tag(blob, 2, 1); + blob_color(blob, 100, 10, 0, DCP_COLOR_FORMAT_RGB); + blob_bool(blob, "IsPreferred", true); + blob_bool(blob, "IsInterlaced", false); + return blob; +} + +static void parser_desktop_metadata_timing(struct kunit *test) +{ + const struct dcp_display_metadata metadata = { + .width_cm = 60, .height_cm = 33, + .chromaticity = { { 44608, 20288 }, { 16960, 40448 }, { 9344, 5184 } }, + .white = { 20544, 21568 }, .gamma = 144179, + .product = 2895, .serial = 305419896, .week = 7, .year = 2013, + .manufacturer = "ACR", .name = "QG270 S3", .serial_text = "ANON000000001", + }; + struct parser_blob *blob = make_timing_blob(test); + struct drm_display_mode preferred; + struct dcp_display_mode *modes; + const struct drm_edid *edid; + const struct edid *raw; + struct dcp_parse_ctx ctx; + unsigned int count; + + KUNIT_ASSERT_NOT_NULL(test, blob); + + /* Other DCPs keep their modes and give no metadata timing. */ + KUNIT_ASSERT_EQ(test, parse(blob->data, blob->size, &ctx), 0); + ctx.dcp = &parser_dcp; + modes = enumerate_modes(&ctx, &count, 600, 330, 0, false, false); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, modes); + KUNIT_ASSERT_EQ(test, count, 1U); + KUNIT_EXPECT_TRUE(test, modes[0].preferred); + KUNIT_EXPECT_FALSE(test, modes[0].metadata_timing_valid); + KUNIT_EXPECT_EQ(test, modes[0].metadata_sync_flags, 0U); + KUNIT_EXPECT_EQ(test, modes[0].mode.flags, 0U); + kfree(modes); + + KUNIT_ASSERT_EQ(test, parse(blob->data, blob->size, &ctx), 0); + ctx.dcp = &parser_dcp; + ctx.metadata_timing = true; + modes = enumerate_modes(&ctx, &count, 600, 330, 0, false, false); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, modes); + KUNIT_ASSERT_EQ(test, count, 1U); + KUNIT_EXPECT_TRUE(test, modes[0].preferred); + KUNIT_EXPECT_TRUE(test, modes[0].mode.type & DRM_MODE_TYPE_PREFERRED); + KUNIT_EXPECT_TRUE(test, modes[0].metadata_timing_valid); + KUNIT_EXPECT_EQ(test, modes[0].metadata_sync_flags, + (u32)(DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC)); + /* The published mode is unchanged: only the EDID timing takes the polarity. */ + KUNIT_EXPECT_EQ(test, modes[0].mode.flags, 0U); + KUNIT_EXPECT_EQ(test, modes[0].mode.clock, 148500); + + preferred = modes[0].mode; + preferred.flags |= modes[0].metadata_sync_flags; + kfree(modes); + edid = dcp_metadata_edid(&metadata, &preferred); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, edid); + raw = drm_edid_raw(edid); + KUNIT_EXPECT_EQ(test, be16_to_cpu(raw->product_id.manufacturer_name), + (u16)(('A' - '@') << 10 | ('C' - '@') << 5 | ('R' - '@'))); + KUNIT_EXPECT_EQ(test, le16_to_cpu(raw->product_id.product_code), 2895); + KUNIT_EXPECT_EQ(test, raw->detailed_timings[1].data.other_data.type, + EDID_DETAIL_MONITOR_NAME); + KUNIT_EXPECT_MEMEQ(test, raw->detailed_timings[1].data.other_data.data.str.str, + "QG270 S3\n", 9); + KUNIT_EXPECT_EQ(test, le16_to_cpu(raw->detailed_timings[0].pixel_clock), 14850); + drm_edid_free(edid); +} + static struct kunit_case parser_cases[] = { KUNIT_CASE(parser_panel_of_dimensions), KUNIT_CASE(parser_panel_j616s_publication), @@ -1566,6 +1669,7 @@ static struct kunit_case parser_cases[] = { KUNIT_CASE(parser_dimensions_lifetime), KUNIT_CASE(parser_catalog_release), KUNIT_CASE(parser_property_bounds), + KUNIT_CASE(parser_desktop_metadata_timing), {} }; diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index 33344a5f8b10b..e5a8c8e6cc700 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -734,6 +734,7 @@ static int parse_mode(struct dcp_parse_ctx *handle, bool preferred = false, interlaced = false; bool have_preferred = false, have_interlaced = false; bool is_preferred = false; + bool timing_keys = external_sdr || handle->metadata_timing; struct drm_display_mode *mode = &out->mode; *out = (struct dcp_display_mode) { @@ -775,11 +776,11 @@ static int parse_mode(struct dcp_parse_ctx *handle, else if (!strcmp(key, "HorizontalAttributes")) { duplicate_scope |= have_horiz; have_horiz = true; - ret = parse_dimension(it.handle, &horiz, external_sdr || internal); + ret = parse_dimension(it.handle, &horiz, timing_keys || internal); } else if (!strcmp(key, "VerticalAttributes")) { duplicate_scope |= have_vert; have_vert = true; - ret = parse_dimension(it.handle, &vert, external_sdr || internal); + ret = parse_dimension(it.handle, &vert, timing_keys || internal); } else if (!strcmp(key, "MinimumVariableRefreshRate")) { vrr_ambiguous |= !!(vrr_seen & 1); vrr_seen |= 1; @@ -798,13 +799,16 @@ static int parse_mode(struct dcp_parse_ctx *handle, duplicate_scope |= have_virtual; have_virtual = true; ret = parse_bool(it.handle, &is_virtual); - } else if (external_sdr && !strcmp(key, "IsPreferred")) { + } else if (timing_keys && !strcmp(key, "IsPreferred")) { if (have_preferred) ret = -EINVAL; else ret = parse_bool(it.handle, &preferred); have_preferred = true; - } else if (external_sdr && !strcmp(key, "IsInterlaced")) { + /* Outside the external SDR path it also ranks the timing. */ + if (!external_sdr) + is_preferred = preferred; + } else if (timing_keys && !strcmp(key, "IsInterlaced")) { if (have_interlaced) ret = -EINVAL; else @@ -914,11 +918,16 @@ static int parse_mode(struct dcp_parse_ctx *handle, .height_mm = height_mm, }; - if ((external_sdr || internal) && horiz.sync_polarity >= 0 && + /* The internal panel also publishes its polarity with its modes (J616s series). */ + if ((timing_keys || internal) && horiz.sync_polarity >= 0 && vert.sync_polarity >= 0) - mode->flags = (horiz.sync_polarity ? DRM_MODE_FLAG_PHSYNC : DRM_MODE_FLAG_NHSYNC) | - (vert.sync_polarity ? DRM_MODE_FLAG_PVSYNC : DRM_MODE_FLAG_NVSYNC); - out->metadata_timing_valid = external_sdr && !out->automatic_rgb8_quirk && + out->metadata_sync_flags = + (horiz.sync_polarity ? DRM_MODE_FLAG_PHSYNC : DRM_MODE_FLAG_NHSYNC) | + (vert.sync_polarity ? DRM_MODE_FLAG_PVSYNC : DRM_MODE_FLAG_NVSYNC); + /* The external SDR path and the internal panel publish it with their modes. */ + if (external_sdr || internal) + mode->flags = out->metadata_sync_flags; + out->metadata_timing_valid = timing_keys && !out->automatic_rgb8_quirk && have_preferred && preferred && have_interlaced && !interlaced && horiz.pixel_repetition == 0 && vert.pixel_repetition == 0 && horiz.sync_polarity >= 0 && vert.sync_polarity >= 0; diff --git a/drivers/gpu/drm/apple/parser.h b/drivers/gpu/drm/apple/parser.h index 9b419ce83621b..5e573371e0072 100644 --- a/drivers/gpu/drm/apple/parser.h +++ b/drivers/gpu/drm/apple/parser.h @@ -27,6 +27,12 @@ struct dcp_parse_ctx { bool ambiguous_180hz; /* Desktop 26.6 uses zero placeholders for an unreported VRR range. */ bool zero_vrr_range_fallback; + /* + * A desktop 26.x external processor: read the sink timing keys that + * describe the preferred timing for the firmware-derived EDID, without + * the H17P external color policy. + */ + bool metadata_timing; #if IS_ENABLED(CONFIG_DRM_APPLE_DCP_T6030_AUDIO) /* * T6030 external processor audio elements (DCP firmware 14.7, external @@ -132,6 +138,8 @@ struct dcp_display_mode { bool preferred; bool metadata_timing_valid; bool automatic_rgb8_quirk; + /* The sync polarity flags of the timing, if it reports them */ + u32 metadata_sync_flags; /* * The best virtual color element of the timing, which the desktop * 26.x firmware selects first to bootstrap its generic pipe (valid if diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py b/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py index 86e7a1fd44e61..24335a890408b 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py @@ -371,7 +371,10 @@ def case(value, automatic=True, internal=False, external=True, admitted=True): callback_types = '\n'.join(block(dptx, marker)+' __packed;' for marker in ('struct dptxport_apcall_link_rate {', 'struct dptxport_apcall_set_active_lane_count {')) callback = block(dptx, 'static int dptxport_call_link_config(') dcp = (base/'dcp.c').read_text() - connection = '\n'.join(block(dcp, marker) for marker in ( + # The modelled DCP is the H17P external one, never a desktop 26.x processor. + connection = 'static bool dcp_sink_metadata(struct apple_dcp *d) { (void)d; return false; }\n' + \ + 'static void dcp_desktop_metadata_new_sink(struct apple_dcp *d) { (void)d; assert(0); }\n' + \ + '\n'.join(block(dcp, marker) for marker in ( 'void dcp_firmware_metadata_connection(', 'static void dcp_firmware_metadata_present_locked(', 'static void dcp_announce_kick(', 'void dcp_firmware_metadata_announced(', 'void dcp_firmware_metadata_hotplug(')) From ec622e2b4acf49dc15b4c793044f1dda96680671 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Wed, 28 Oct 2026 15:05:46 +1100 Subject: [PATCH 184/184] selftests: apple-dcp: follow the J616s DP Alt Mode reconcile in tipd fixtures #519 makes the CD321x update publish the DisplayPort mode the firmware entered as a partner altmode, and drop it with the partner. The probe now takes the connector from a "displayport" reference when the port has one. The tipd host fixtures did not know either, so 24 of them stopped building. - lifecycle: carry partner_altmode_dp and the DP mode VDO, and count partner altmode registration against removal. - quiesce and the transaction fixtures built on it: carry partner_altmode_dp. They never enter DP on the partner, so the altmode helpers assert they are not reached. - pm: report a "displayport" reference, which the probe fixtures already resolve, and assert the port-node fallback is not taken. No assertion is relaxed. apple-dcp host selftests: 80/80. Assisted-by: LLM Signed-off-by: Ryan Murray Co-Authored-By: Claude Opus 5.5 --- .../drivers/gpu/apple-dcp/lifecycle.py | 19 +++++++++++++++++-- .../selftests/drivers/gpu/apple-dcp/pm.py | 2 ++ .../drivers/gpu/apple-dcp/quiesce.py | 5 ++++- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py b/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py index 9907b055ab75d..f27c5b243d32f 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py @@ -192,8 +192,9 @@ def function(file, name): struct cd321x_status { u32 status, data_status, status_changed, data_status_changed, pwr_status; struct usb_pd_identity partner_identity; bool dp_sid_event,dp_sid_hpd_low,dp_sid_irq; - struct { u32 status_rx,configure; } dp_sid_status; }; -struct tps6598x { void *partner, *dev, *port, *psy; int lock; u32 data_status; enum usb_role *role_sw; }; + struct { u32 status_rx,configure,mode_data; } dp_sid_status; }; +struct tps6598x { void *partner, *dev, *port, *psy; int lock; u32 data_status; enum usb_role *role_sw; + void *partner_altmode_dp; }; struct cd321x { struct tps6598x tps; struct cd321x_status update_status; struct cd321x_pm_state pm; struct delayed_work update_work, resume_work; struct typec_mux_state state; bool state_valid, display_route_active, cur_partner_is_pd; @@ -236,6 +237,20 @@ def function(file, name): static void *typec_register_partner(void *p, struct typec_partner_desc *d) { (void)p; (void)d; return (void *)1; } static void typec_partner_set_identity(void *p) { (void)p; } +#ifndef USB_TYPEC_DP_SID +#define USB_TYPEC_DP_SID 0xff01 +#endif +#ifndef USB_TYPEC_DP_MODE +#define USB_TYPEC_DP_MODE 1 +#endif +struct typec_altmode_desc { unsigned short svid; unsigned char mode; u32 vdo; bool mode_selection; }; +/* The partner DP altmode the firmware entered: registered and dropped in pairs. */ +static int partner_altmodes; +static void *typec_partner_register_altmode(void *p, const struct typec_altmode_desc *d) +{ assert(p && d->svid == USB_TYPEC_DP_SID); partner_altmodes++; return &partner_altmodes; } +static void typec_altmode_update_active(void *a, bool active) { assert(a); (void)active; } +static void __attribute__((unused)) typec_unregister_altmode(void *a) +{ if (a == &partner_altmodes) { assert(partner_altmodes > 0); partner_altmodes--; } } #define typec_set_pwr_opmode(...) ((void)0) #define typec_set_pwr_role(...) ((void)0) #define typec_set_vconn_role(...) ((void)0) diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/pm.py b/tools/testing/selftests/drivers/gpu/apple-dcp/pm.py index abf1afd88e24c..3c2f2b8df2bd5 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/pm.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/pm.py @@ -513,6 +513,8 @@ def function(text, name): static struct fwnode_handle *fwnode_find_reference(struct fwnode_handle *f,const char *s,int n) { (void)f;(void)s;(void)n;connector.refs++;return &connector; } +static bool fwnode_property_present(struct fwnode_handle *f,const char *s) { (void)f;(void)s;return true; } +static struct fwnode_handle *fwnode_handle_get(struct fwnode_handle *f) { assert(!"connector is a displayport reference");return f; } static void __attribute__((unused)) fwnode_handle_put(struct fwnode_handle *f) { if(f){assert(f->refs>0);f->refs--;} } diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/quiesce.py b/tools/testing/selftests/drivers/gpu/apple-dcp/quiesce.py index 30eb0a183d6b7..aadd700a40097 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/quiesce.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/quiesce.py @@ -93,7 +93,7 @@ def make_unit(source, parent=False, reference=None): struct tps6598x; struct tipd_data { REMOVE_RETURN (*remove)(struct tps6598x *); void (*unregister_port)(struct tps6598x *); int (*reset)(struct tps6598x *); }; struct typec_port { struct typec_mux *mux; }; -struct tps6598x { bool remove_safe_prepared; struct typec_mux *mux; void *partner,*dev,*psy,*role_sw,*reset; int irq,wq_poll; struct typec_port *port; struct tipd_data *data; }; +struct tps6598x { bool remove_safe_prepared; struct typec_mux *mux; void *partner,*dev,*psy,*role_sw,*reset,*partner_altmode_dp; int irq,wq_poll; struct typec_port *port; struct tipd_data *data; }; struct cd321x { struct tps6598x tps; struct typec_mux *mux; void *tbt_switch; }; struct typec_thunderbolt_switch_data { int state; }; static void cd321x_stop_work(struct cd321x *c) { (void)c;event('W'); } @@ -101,6 +101,9 @@ def make_unit(source, parent=False, reference=None): static int usb_role_switch_set_role(void *p,int r) { (void)p;assert(r==USB_ROLE_NONE);if(ordinary_port&&!role_error){int ret=ordinary_power_off();if(ret)return ret;controller_active=false;phy_off=true;}event('R');role_calls++;return role_error; } static enum usb_role __attribute__((unused)) usb_role_switch_get_role(void *p) { (void)p;return controller_active?USB_ROLE_HOST:USB_ROLE_NONE; } static void typec_unregister_partner(void *p) { (void)p;event('P'); } +/* These fixtures never enter DP on the partner, so its altmode stays NULL. */ +static void typec_altmode_update_active(void *a,bool v) { (void)a;(void)v;assert(!"no partner DP altmode here"); } +static void typec_unregister_altmode(void *a) { (void)a;assert(!"no partner DP altmode here"); } #define typec_set_pwr_opmode(...) ((void)0) #define typec_set_pwr_role(...) ((void)0) #define typec_set_vconn_role(...) ((void)0)