From nobody Mon Jun 8 19:43:04 2026 Received: from smtp-relay-internal-1.canonical.com (smtp-relay-internal-1.canonical.com [185.125.188.123]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 256593B4EA4 for ; Wed, 27 May 2026 06:49:50 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.125.188.123 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779864593; cv=none; b=d6sOcP7dxX5HVAbWFk/NSxNyPf7Iysta1eJeri/Pslh58n1STwnBPmh91+olL58aB9wNAddr0ZL6QHtO5idUwmU4On3MIDjn1aEmg9kwL4GqIu0xBxDosWL/lAOcqkJ2gGNkKZ6oGoQ7MS/NpObfs/HQIxHJX+C+iltReSAGAC8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779864593; c=relaxed/simple; bh=2eE85DAOOzZkxQWu0AgDKUBoWCkafUlWcRaJYyt8LOo=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=UiNTJtsGr40jxl/5OfCYzhXKAtALuGEKLGBrCiUnmTOS+8kOn/JTdyHesHhSLf0qA5827zmbj5BlKcW2ueoDZpBw0KyR4r1Qhr3MBvdFZD7Uo6mTpomx5keu6tu1yJ7c5bxc3RRnHtrj0HArmV0fuDp8Y3lSVjZXaAk0o6B/JP8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=canonical.com; spf=pass smtp.mailfrom=canonical.com; dkim=pass (4096-bit key) header.d=canonical.com header.i=@canonical.com header.b=PPQOkH4c; arc=none smtp.client-ip=185.125.188.123 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=canonical.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=canonical.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (4096-bit key) header.d=canonical.com header.i=@canonical.com header.b="PPQOkH4c" Received: from mail-pl1-f200.google.com (mail-pl1-f200.google.com [209.85.214.200]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by smtp-relay-internal-1.canonical.com (Postfix) with ESMTPS id 953353F951 for ; Wed, 27 May 2026 06:41:39 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=canonical.com; s=20251003; t=1779864099; bh=YnIU/WDlLavcpBXaBFNNf33TA4YXxB62T7YSUrvnnZ4=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=PPQOkH4cv+4b+ZQ8qN+jdXFBo0BvVAyB2XWoqM3b6aLrG1iO+5O3himCQ/VPpLbdz d2Gws4w6XDkignW5QpHd3VScZ8pE0bWSiWKaZMqc2lRH9NQaHYnxUIs/YmjMVGbskv L03jQqHABfbI7L2Bbsi5Gf1moRe8s65XJ2HUscYebcRtePZuvbcMErKS9lYZOIhE9w suMV/7ICUfN0UFGG3s5Wwqu5JYQ038B1FneqdZ5F+eoeLMLPxPEp2avbmNbeznP0al jJnDos+omv3VMU2bow4EHdQl6lVIfuwCAdhsd8RBeJAM2forlaJSgesXZOICGKCA/7 VPwQEzYBpZXXydU6LH86pdO79Lk+j73XayMEPqZUzJs/Hzpzb2ZET7T3iws6MA1Dxq GpSvuvIJ1LGUmP7knw+1XQYbOCCNgZKYsYT9vvpIOsosukD2UB0+rxXmVQ5hinR6qU OppLQ0ZnXD5Rkw+jazw3fZUANg8HoPJrJizQLml11aa4NO5IKbgwaqu6NOSBtnd4mf QcLwUNJTKOd9Uj3SNKY/gn2C8T7x5rOrJ93wfK/+MD1D9ig8o8wJLL8E222u2EzVvB ZDr00QQdo9Iz3qBQ+PvErLrD91uOl0JosXqQkADg6O/jv/1qjQJc72i6YI9BK4Y4Km 04wsoHrXmgdIPZSBbCQtoQMc= Received: by mail-pl1-f200.google.com with SMTP id d9443c01a7336-2bcc0a30d60so27157185ad.1 for ; Tue, 26 May 2026 23:41:39 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1779864098; x=1780468898; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=YnIU/WDlLavcpBXaBFNNf33TA4YXxB62T7YSUrvnnZ4=; b=OupjG31V7qPaJk4h/a67zbxoIKUy+H24OUwk2BPhGmypMz8W0zVl/oNtiNFkd5CD9h 0gT4B2+P9GyYd0Bzx81A1GD1E9K9Tk96KJMsbLk35n66s1R9hp1dlQoBrtYqsk88URg+ IkT/OKLo8BZrAB19YNdalhzD0NrHI0e3YXj3Y7yE7FtmmKVVZS/c1vp+N7CjJpqoEAG5 VM9r3ZFWtqJG0hg3nlagkQ1lCeXLz6kU1D2+23cDyp8VT7M0EjB11NqPwj/4pvN0xtWb 83EtuvjE3seJrCgyrW973QuOEf6n92I7etyXbSKCE7ZkalF8+Z9aVjWCktiazu8WVohz W26g== X-Forwarded-Encrypted: i=1; AFNElJ8mx21Uz9f4DmZKEESlVfAtpEtgfFia6Vk34W8mfM4Sj+pgWP6Inkhtv/NRM/k3mKDBa6uSeCW4oqtVyzc=@vger.kernel.org X-Gm-Message-State: AOJu0YzXgxrTr94j0vWP1hMoBMx4ud1eNWGXyXBLRaX0J1ncjhNBYpur Ct+5gmlOWtkqCuvDDgBAVi31oL8n1K1pOeZQjBAnOD2R9ER2ZL7i8gCrG+jbfXRKF7uSN+qCDR2 hQ/qWloqUo0ZgbMCZzdspbdHGeCHMgHEdcc2kqGsNW/1svjIivZ1r3o9o62QbDuwKMYxi6pY67o MNcEiWFQ== X-Gm-Gg: Acq92OFOkkEl0JckpQQtcq1rF9hil8UAui2YI2erXDAenEOvOLLtT+mcKClmXRp+c5u xohR0aoR2i1VBlY5p5K4n2pIb6WIKoC5yVklb6yfV37rqEB9eMiVEDDvToj/Zxpbp2Mt8fLg+2M B6scOBh5aNnHwg8/4TTLkbgtiX25NZPfVVxNUbzMWDpe+hCkA+GV/ORuh3H6GdLHmoma57bBnPH c90UCIdCXekM4c2AM5y/lrrAp5AiPiE5hqU1kmDXcwr5OiGgxEbYlRVrBW7AOFjB+yVfGdanX7q chr/iWTKLqEAFgYpi+JwzXbfmy9inyek9+Xc2gOAAVo5TdJmektw9KO1B6vftwNBb8HN/M94zUc K4C334gbo4atYN7fEQsCmkSnDAckAhsnqrsY3xms9car5k7Rmt/r7L2YcXmYi0xmFcMLbc6GR5i Mhk25AjnFLqzn/6Tw/3F7T2Vy0eDP9HTDYefJ5ZKOk1kOguYU2/lgPs1neG5+8tZUNcF8Q8iryC KovhUbWliXtcOmENZQ= X-Received: by 2002:a17:902:d581:b0:2b0:606b:6fc5 with SMTP id d9443c01a7336-2beb066e847mr145240635ad.3.1779864098142; Tue, 26 May 2026 23:41:38 -0700 (PDT) X-Received: by 2002:a17:902:d581:b0:2b0:606b:6fc5 with SMTP id d9443c01a7336-2beb066e847mr145240405ad.3.1779864097720; Tue, 26 May 2026 23:41:37 -0700 (PDT) Received: from an-XPS-15-9520.buildd (2001-b011-2008-658f-4fd7-7372-71f3-d446.dynamic-ip6.hinet.net. [2001:b011:2008:658f:4fd7:7372:71f3:d446]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2beb58fef85sm141361335ad.75.2026.05.26.23.41.36 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 26 May 2026 23:41:37 -0700 (PDT) From: ChunAn Wu To: mika.westerberg@linux.intel.com Cc: andreas.noever@gmail.com, michael.jamet@intel.com, YehezkelShB@gmail.com, linux-usb@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH] thunderbolt: Defer DP tunnel teardown until display driver is ready Date: Wed, 27 May 2026 14:41:21 +0800 Message-Id: <20260527064121.173952-2-an.wu@canonical.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260527064121.173952-1-an.wu@canonical.com> References: <20260527064121.173952-1-an.wu@canonical.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" When the Thunderbolt driver loads early (e.g., from initramfs) and discovers a BIOS-established DisplayPort tunnel, it starts asynchronous DPRX polling which checks if the GPU driver has read DPCD from the connected monitor within a 12-second timeout (TB_DPRX_TIMEOUT). On systems with Full Disk Encryption (FDE/LUKS), the GPU driver (i915, xe, amdgpu, etc.) resides on the encrypted root filesystem and cannot load until the user enters the passphrase. This creates a driver load ordering issue where the DPRX timeout fires before the GPU driver has had a chance to initialize, causing the Thunderbolt driver to permanently tear down the DP tunnel and remove the DP IN adapter from available resources. Recovery requires a physical re-plug of the dock. Fix this by deferring the DP tunnel teardown when no PCI display driver has bound yet. Register a PCI bus notifier that watches for display class (PCI_BASE_CLASS_DISPLAY) driver bind events. When the DPRX timeout fires: - If no display driver is bound: tear down the tunnel but keep the DP IN adapter in the available resources list, allowing a retry. - If a display driver is already bound: proceed with the existing behavior of permanently removing the DP IN resource. When a display driver eventually binds, the notifier triggers a DP tunnel retry via a scheduled work item, re-establishing the connection. This approach requires no changes to GPU drivers and handles all GPU vendors (Intel, AMD, NVIDIA) through the generic PCI base class check (0x03xx covers VGA, XGA, 3D, and other display controllers). It also handles the FDE case gracefully since the defer and retry can span an unbounded passphrase wait. Tested on Dell Pro Max 14 MC14250 with Dell SD25TB5 Thunderbolt 5 Dock and LUKS full disk encryption. Simulated a 58-second delay between TB and GPU driver loading -- display came up successfully after display driver bound. Signed-off-by: ChunAn Wu --- drivers/thunderbolt/tb.c | 96 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 88 insertions(+), 8 deletions(-) diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index 95d84612e06e..48e0b540fbec 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -62,6 +62,9 @@ MODULE_PARM_DESC(asym_threshold, * @remove_work: Work used to remove any unplugged routers after * runtime resume * @groups: Bandwidth groups used in this domain. + * @pci_nb: PCI bus notifier to detect when a display driver binds + * @display_bound: Set when a PCI display driver has bound + * @display_retry_work: Work to retry DP tunneling after display driver bi= nds */ struct tb_cm { struct list_head tunnel_list; @@ -69,6 +72,9 @@ struct tb_cm { bool hotplug_active; struct delayed_work remove_work; struct tb_bandwidth_group groups[MAX_GROUPS]; + struct notifier_block pci_nb; + bool display_bound; + struct work_struct display_retry_work; }; =20 static inline struct tb *tcm_to_tb(struct tb_cm *tcm) @@ -1914,6 +1920,58 @@ static struct tb_port *tb_find_dp_out(struct tb *tb,= struct tb_port *in) return NULL; } =20 +static void tb_tunnel_dp(struct tb *tb); + +/* + * Check if any PCI display class (0x03xx) device has a driver bound. + * Used to decide whether to defer DPRX polling at boot. + */ +static bool tb_is_display_driver_bound(void) +{ + struct pci_dev *pdev =3D NULL; + + while ((pdev =3D pci_get_base_class(PCI_BASE_CLASS_DISPLAY, pdev))) { + if (pdev->driver) { + pci_dev_put(pdev); + return true; + } + } + return false; +} + +static void tb_display_retry_work_fn(struct work_struct *work) +{ + struct tb_cm *tcm =3D container_of(work, struct tb_cm, display_retry_work= ); + struct tb *tb =3D tcm_to_tb(tcm); + + mutex_lock(&tb->lock); + tb_dbg(tb, "display driver bound, retrying DP tunneling\n"); + tb_tunnel_dp(tb); + mutex_unlock(&tb->lock); +} + +static int tb_pci_notifier_fn(struct notifier_block *nb, unsigned long act= ion, + void *data) +{ + struct tb_cm *tcm =3D container_of(nb, struct tb_cm, pci_nb); + struct device *dev =3D data; + struct pci_dev *pdev; + + if (action !=3D BUS_NOTIFY_BOUND_DRIVER) + return NOTIFY_OK; + + pdev =3D to_pci_dev(dev); + if ((pdev->class >> 16) !=3D PCI_BASE_CLASS_DISPLAY) + return NOTIFY_OK; + + if (!tcm->display_bound) { + tcm->display_bound =3D true; + schedule_work(&tcm->display_retry_work); + } + + return NOTIFY_OK; +} + static void tb_dp_tunnel_active(struct tb_tunnel *tunnel, void *data) { struct tb_port *in =3D tunnel->src_port; @@ -1955,6 +2013,7 @@ static void tb_dp_tunnel_active(struct tb_tunnel *tun= nel, void *data) } } else { struct tb_port *in =3D tunnel->src_port; + struct tb_cm *tcm =3D tb_priv(tb); =20 /* * This tunnel failed to establish. This means DPRX @@ -1963,16 +2022,26 @@ static void tb_dp_tunnel_active(struct tb_tunnel *t= unnel, void *data) * loaded or not all DP cables where connected to the * discrete router. * - * In both cases we remove the DP IN adapter from the - * available resources as it is not usable. This will - * also tear down the tunnel and try to re-use the - * released DP OUT. + * If no display driver has bound yet (common during boot + * with FDE/LUKS where the GPU driver loads late from + * the encrypted root filesystem), tear down the tunnel + * but keep the DP IN resource available. The PCI bus + * notifier will trigger a retry once a display driver + * binds. * - * It will be added back only if there is hotplug for - * the DP IN again. + * Otherwise, remove the DP IN adapter from available + * resources as it is not usable. It will be added back + * only if there is hotplug for the DP IN again. */ - tb_tunnel_warn(tunnel, "not active, tearing down\n"); - tb_dp_resource_unavailable(tb, in, "DPRX negotiation failed"); + if (!tcm->display_bound && !tb_is_display_driver_bound()) { + tb_tunnel_warn(tunnel, + "not active, deferring until display driver loads\n"); + tb_deactivate_and_free_tunnel(tunnel); + } else { + tb_tunnel_warn(tunnel, "not active, tearing down\n"); + tb_dp_resource_unavailable(tb, in, + "DPRX negotiation failed"); + } } mutex_unlock(&tb->lock); =20 @@ -2984,6 +3053,9 @@ static void tb_deinit(struct tb *tb) struct tb_cm *tcm =3D tb_priv(tb); int i; =20 + bus_unregister_notifier(&pci_bus_type, &tcm->pci_nb); + cancel_work_sync(&tcm->display_retry_work); + /* Cancel all the release bandwidth workers */ for (i =3D 0; i < ARRAY_SIZE(tcm->groups); i++) cancel_delayed_work_sync(&tcm->groups[i].release_work); @@ -3410,8 +3482,16 @@ struct tb *tb_probe(struct tb_nhi *nhi) INIT_LIST_HEAD(&tcm->tunnel_list); INIT_LIST_HEAD(&tcm->dp_resources); INIT_DELAYED_WORK(&tcm->remove_work, tb_remove_work); + INIT_WORK(&tcm->display_retry_work, tb_display_retry_work_fn); tb_init_bandwidth_groups(tcm); =20 + /* Check if a display driver is already bound (e.g. hotplug after boot) */ + tcm->display_bound =3D tb_is_display_driver_bound(); + + /* Watch for display driver binding to defer DPRX until GPU is ready */ + tcm->pci_nb.notifier_call =3D tb_pci_notifier_fn; + bus_register_notifier(&pci_bus_type, &tcm->pci_nb); + tb_dbg(tb, "using software connection manager\n"); =20 /* --=20 2.34.1