From dfdc4ef1dcae50cea3120e663552d3c20a279c49 Mon Sep 17 00:00:00 2001 From: Kieran Graham Date: Wed, 14 Oct 2026 23:15:12 +0100 Subject: [PATCH 3/6] drm/apple: defer established-link fault recovery to a worker Retain local recovery RFC 3 with the parent fabric split, binding drain and work-enabled gates. Drain fault work on park, unplug, provider retirement, suspend and teardown. Keep bounded retries and reset after successful swaps. Ported from release 56 onto omacom/linux-aurora 6f6367775. Assisted-by: Codex:gpt-6.1-sol --- drivers/gpu/drm/apple/dcp-fabric.c | 27 ++++++++++++- drivers/gpu/drm/apple/dcp-fabric.h | 2 + drivers/gpu/drm/apple/dcp-internal.h | 3 ++ drivers/gpu/drm/apple/dcp.c | 60 ++++++++++++++++++++++++++++ drivers/gpu/drm/apple/dcp.h | 1 + drivers/gpu/drm/apple/dptxep.c | 3 +- 6 files changed, 94 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp-fabric.c b/drivers/gpu/drm/apple/dcp-fabric.c index 739c09d0a..a5eb7e8b4 100644 --- a/drivers/gpu/drm/apple/dcp-fabric.c +++ b/drivers/gpu/drm/apple/dcp-fabric.c @@ -195,6 +195,30 @@ static bool dcp_typec_port_follows(struct apple_dcp_typec_port *port) return !list_empty(&port->routes); } +/* False asks the freezable fault worker to retry after fabric contention. */ +bool dcp_fabric_retrain_link_fault(struct apple_dcp *dcp) +{ + struct apple_connector *connector; + unsigned long flags; + bool pending; + + if (!mutex_trylock(&dcp_typec_fabric_lock)) + return false; + spin_lock_irqsave(&dcp->mode_state.lock, flags); + pending = dcp->link_fault_retrains && dcp->mode_state.valid && + !dcp->mode_state.changing; + spin_unlock_irqrestore(&dcp->mode_state.lock, flags); + connector = dcp->connector; + if (pending && !dcp->crashed && dcp_is_typec_output(dcp) && + !READ_ONCE(dcp->tb_retiring) && READ_ONCE(dcp->typec_work_enabled) && + READ_ONCE(dcp->typec_cable_connected) && + !READ_ONCE(dcp->typec_crtc_off) && connector && + READ_ONCE(connector->dcp) == to_platform_device(dcp->dev)) + dcp_retrain_oob(connector); + mutex_unlock(&dcp_typec_fabric_lock); + return true; +} + /* Is a routed port's possible_crtcs narrowed to the pipeline driving it? */ static bool dcp_typec_narrows(struct apple_dcp *dcp) { @@ -1775,9 +1799,10 @@ static void dcp_tb_drain_wait(void *data) lockdep_assert_not_held(&dcp_typec_fabric_lock); lockdep_assert_not_held(&dcp->tb_lock); - /* Queue admission checks tb_retiring. Neither worker takes fabric_lock. */ + /* Admission checks tb_retiring; fabric_lock is dropped while draining. */ cancel_delayed_work_sync(&dcp->typec_reconnect_wq); cancel_delayed_work_sync(&dcp->placeholder_edid_wq); + cancel_delayed_work_sync(&dcp->link_fault_retrain_wq); } static void dcp_tb_drain_lock(void *data) diff --git a/drivers/gpu/drm/apple/dcp-fabric.h b/drivers/gpu/drm/apple/dcp-fabric.h index f09fbc392..ed1711901 100644 --- a/drivers/gpu/drm/apple/dcp-fabric.h +++ b/drivers/gpu/drm/apple/dcp-fabric.h @@ -88,6 +88,8 @@ int dcp_tunnel_set_rate(struct apple_dcp *dcp, struct phy *phy, u32 unit, u32 link_rate); int dcp_tunnel_dpin_activate(struct apple_dcp *dcp, u32 unit, bool active); +bool dcp_fabric_retrain_link_fault(struct apple_dcp *dcp); + /* DCP probe, resume and endpoint teardown hooks. */ int dcp_register_typec_routes(struct apple_dcp *dcp); void dcp_typec_routes_disable(struct apple_dcp *dcp); diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index eec54f326..10b908cde 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -386,6 +386,9 @@ struct apple_dcp { /* IOMFB reports its video interface ready after DPTX link training. */ struct completion typec_iomfb_hpd_ready; struct delayed_work typec_reconnect_wq; + struct delayed_work link_fault_retrain_wq; + /* mode_state.lock protects the bounded firmware-fault retry budget. */ + unsigned int link_fault_retrains; u32 typec_reconnect_tries; struct dcp_fabric_session typec_reconnect_session; /* hpd_mutex */ bool typec_work_enabled; /* hpd_mutex; IRQ recovery admission */ diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 48bf44d56..122cd1d00 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -237,6 +237,54 @@ static void dcp_delayed_vblank(struct work_struct *work) #define DCP_SWAP_WATCHDOG_MS 1000 #define DCP_SWAP_WATCHDOG_RETRAINS 5 +#define DCP_LINK_FAULT_DELAY_MS 1000 +#define DCP_LINK_FAULT_RETRAINS 3 + +static void dcp_link_fault_retrain_work(struct work_struct *work) +{ + struct apple_dcp *dcp = + container_of(to_delayed_work(work), struct apple_dcp, + link_fault_retrain_wq); + /* The fabric helper serializes ownership against route teardown. */ + if (!dcp_fabric_retrain_link_fault(dcp)) + queue_delayed_work(system_freezable_wq, + &dcp->link_fault_retrain_wq, + msecs_to_jiffies(DCP_LINK_FAULT_DELAY_MS)); +} + +void dcp_report_link_fault(struct apple_dcp *dcp) +{ + unsigned long flags; + + if (!dcp_is_typec_output(dcp) || dcp->crashed || + READ_ONCE(dcp->tb_retiring) || !READ_ONCE(dcp->typec_work_enabled) || + !READ_ONCE(dcp->typec_cable_connected) || + READ_ONCE(dcp->typec_crtc_off)) + return; + + /* Initial training reports transient faults; recover established links. */ + spin_lock_irqsave(&dcp->mode_state.lock, flags); + if (dcp->mode_state.valid && !dcp->mode_state.changing && + dcp->link_fault_retrains < DCP_LINK_FAULT_RETRAINS && + queue_delayed_work(system_freezable_wq, + &dcp->link_fault_retrain_wq, + msecs_to_jiffies(DCP_LINK_FAULT_DELAY_MS))) + dcp->link_fault_retrains++; + spin_unlock_irqrestore(&dcp->mode_state.lock, flags); +} + +static void dcp_link_fault_reset(struct apple_dcp *dcp) +{ + unsigned long flags; + + /* A completed swap proves that transient link faults resolved themselves. */ + cancel_delayed_work(&dcp->link_fault_retrain_wq); + spin_lock_irqsave(&dcp->mode_state.lock, flags); + dcp->link_fault_retrains = 0; + spin_unlock_irqrestore(&dcp->mode_state.lock, flags); +} + + /* * DCP drops swaps without completing them while an external pipe is not * enabled, for instance after a modeset that raced a Type-C sink which had @@ -269,6 +317,7 @@ void dcp_swap_watchdog_arm(struct apple_dcp *dcp) void dcp_swap_watchdog_complete(struct apple_dcp *dcp) { cancel_delayed_work(&dcp->swap_watchdog_wq); + dcp_link_fault_reset(dcp); dcp->swap_watchdog_retrains = 0; } @@ -1103,6 +1152,7 @@ int dcp_dptx_connect_oob(struct platform_device *pdev, u32 port) if (dcp_is_typec_output(dcp)) { cancel_delayed_work_sync(&dcp->placeholder_edid_wq); + dcp_link_fault_reset(dcp); guard(mutex)(&dcp->hpd_mutex); dcp->typec_generation++; WRITE_ONCE(dcp->typec_cable_connected, true); @@ -1139,6 +1189,7 @@ int dcp_dptx_disconnect_oob(struct platform_device *pdev, u32 port) reinit_completion(&dcp->typec_iomfb_hpd_ready); cancel_delayed_work_sync(&dcp->typec_reconnect_wq); cancel_delayed_work_sync(&dcp->placeholder_edid_wq); + cancel_delayed_work_sync(&dcp->link_fault_retrain_wq); } return dcp_dptx_disconnect_drained(dcp, port); @@ -1174,6 +1225,7 @@ int dcp_dptx_recover_irq(struct apple_dcp *dcp) session = dcp_session_locked(dcp); cancel_delayed_work(&dcp->typec_reconnect_wq); cancel_delayed_work(&dcp->placeholder_edid_wq); + cancel_delayed_work(&dcp->link_fault_retrain_wq); if (dcp->avep) av_service_disconnect(dcp); ret = dptxport_set_hpd(dcp->dptxport[0].service, false); @@ -1218,6 +1270,7 @@ int dcp_dptx_park(struct apple_dcp *dcp) /* Session checks around reconnect waits reject the revoked generation. */ cancel_delayed_work(&dcp->typec_reconnect_wq); cancel_delayed_work(&dcp->placeholder_edid_wq); + cancel_delayed_work(&dcp->link_fault_retrain_wq); if (dcp->avep) av_service_disconnect(dcp); if (READ_ONCE(dcp->split.active) && dcp->dptxport[1].enabled && @@ -1512,6 +1565,7 @@ void dcp_poweron(struct platform_device *pdev) int ret; if (dcp_is_typec_output(dcp)) { + dcp_link_fault_reset(dcp); wait_for_typec_hpd = READ_ONCE(dcp->typec_crtc_off) && READ_ONCE(dcp->typec_cable_connected); WRITE_ONCE(dcp->typec_crtc_off, false); @@ -1589,6 +1643,7 @@ void dcp_poweroff(struct platform_device *pdev) WRITE_ONCE(dcp->typec_crtc_off, true); /* dcp_poweron() reconnects the link on DPMS wake. */ cancel_delayed_work(&dcp->typec_reconnect_wq); + cancel_delayed_work(&dcp->link_fault_retrain_wq); } _dcp_poweroff(dcp); @@ -1968,6 +2023,7 @@ static void dcp_disable_typec_work(struct apple_dcp *dcp, bool release_cable) /* Block new enqueues as well as draining users of the AFK endpoints. */ disable_delayed_work_sync(&dcp->typec_reconnect_wq); disable_delayed_work_sync(&dcp->placeholder_edid_wq); + disable_delayed_work_sync(&dcp->link_fault_retrain_wq); disable_delayed_work_sync(&dcp->hdmi_settle_wq); disable_delayed_work_sync(&dcp->hdmi_recheck_wq); dcp_hdmi_disable(dcp); @@ -1977,6 +2033,7 @@ static void dcp_enable_typec_work(struct apple_dcp *dcp) { enable_delayed_work(&dcp->typec_reconnect_wq); enable_delayed_work(&dcp->placeholder_edid_wq); + enable_delayed_work(&dcp->link_fault_retrain_wq); enable_delayed_work(&dcp->hdmi_settle_wq); enable_delayed_work(&dcp->hdmi_recheck_wq); dcp_hdmi_enable(dcp); @@ -2353,9 +2410,12 @@ static int dcp_platform_probe(struct platform_device *pdev) dcp_typec_reconnect_work); INIT_DELAYED_WORK(&dcp->placeholder_edid_wq, dcp_placeholder_edid_work); + INIT_DELAYED_WORK(&dcp->link_fault_retrain_wq, + dcp_link_fault_retrain_work); /* Balanced by enable at successful component bind. */ disable_delayed_work(&dcp->typec_reconnect_wq); disable_delayed_work(&dcp->placeholder_edid_wq); + disable_delayed_work(&dcp->link_fault_retrain_wq); /* * A native external pipe's bind enables these; firmware callbacks may * come before it. The 14.7 panel never uses them, but suspend cancels diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h index 4cad0290b..5fec30b66 100644 --- a/drivers/gpu/drm/apple/dcp.h +++ b/drivers/gpu/drm/apple/dcp.h @@ -68,6 +68,7 @@ bool dcp_crtc_mode_fixup(struct drm_crtc *crtc, struct drm_display_mode *adjusted_mode); void dcp_set_dimensions(struct apple_dcp *dcp, u64 generation); void dcp_send_message(struct apple_dcp *dcp, u8 endpoint, u64 message); +void dcp_report_link_fault(struct apple_dcp *dcp); int dcp_dptx_connect_oob(struct platform_device *pdev, u32 port); bool dcp_t6030_ext_native(const struct device_node *np); diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c index 538077f8b..fcd9101d7 100644 --- a/drivers/gpu/drm/apple/dptxep.c +++ b/drivers/gpu/drm/apple/dptxep.c @@ -810,10 +810,11 @@ static int dptxport_call(struct apple_epic_service *service, u32 idx, case DPTX_APCALL_DEVICE_NOT_RESPONDING: case DPTX_APCALL_DEVICE_BUSY_TIMEOUT: case DPTX_APCALL_DEVICE_NOT_STARTED: - dev_warn(service->ep->dcp->dev, + dev_warn_ratelimited(service->ep->dcp->dev, "DPTXPort: firmware reports link fault %u on target %u:%u\n", idx, service->ep->dcp->dptx_die, service->ep->dcp->dptx_phy); + dcp_report_link_fault(service->ep->dcp); memcpy(reply, data, min(reply_size, data_size)); return 0; case DPTX_APCALL_SET_TILED_DISPLAY_HINTS: -- 2.56.0