From nobody Mon Jun 8 19:54:59 2026 Received: from smtp-relay-internal-0.canonical.com (smtp-relay-internal-0.canonical.com [185.125.188.122]) (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 2FBF33B810D for ; Wed, 27 May 2026 07:07:36 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.125.188.122 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779865658; cv=none; b=shitz6p+2Q59eD2Bvdgf5xZ0XxIb2E1mPTZ52S2uPPk8PNFA1UxmEGJUGZIRIxwoMBxLCXHaVGmQFyuUrJAtYCc2aZ7ENkeIPk3iU+uyqfE0eiybFhKODwAa1xuTLhEF/0LFiod407VgBiAiOmGOp0PxLxeHOlF7x+/DXTOiqB4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779865658; c=relaxed/simple; bh=2eE85DAOOzZkxQWu0AgDKUBoWCkafUlWcRaJYyt8LOo=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=BF9JfdGO9Cfa7L1ZI3MvH7g/aS4ocNllHmd3HYa6c3nq3/1zCjO9J6xuLvSWiYEFwpwpHpUDW00XobsrMaydiHZIJRgAoIJUpZt+cIEPFXb2U0GREpz4doKrbWg3lb6cRfL+Ca1bL7xfCNvVy7chAFMn5nzJ9DBwlQY/AcXEQAc= 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=sQGFJN7p; arc=none smtp.client-ip=185.125.188.122 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="sQGFJN7p" Received: from mail-pl1-f199.google.com (mail-pl1-f199.google.com [209.85.214.199]) (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-0.canonical.com (Postfix) with ESMTPS id C05E33F4EE for ; Wed, 27 May 2026 07:07:34 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=canonical.com; s=20251003; t=1779865654; bh=YnIU/WDlLavcpBXaBFNNf33TA4YXxB62T7YSUrvnnZ4=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=sQGFJN7pTrSNC43jhr8dii206BGGBBDJam1z3thC0CdYLdH/VRmw/wa1wYNPImNUT mtFIS1Y49TUJyrdLe4HgVmH4kNyMZYT5uDVgdFGh6IJsKxgpycT0Ud6FieJ+GD0PHG aVLcc04hgj6bpcb0VPYj5b5WE3JtED9WrbmVSdhMBhGOdREC8zBy1GULwuvxwl9P2v i0U7lUz1doxXJb/ec9H78HecrL1G9Iwl2L8q/aKsK7VOuFuggh4V3bddJBMrAcZOog y2ZMx0k/8CLTFeKO9CYVHxZN5dZO55r9eLwGLnNIoWPhlkcDQ6aGeNBq/GH2p1dw77 2cuTuqLyGcBUKpSTC+ecrYoar5ueYDk8UP8uy5n0eDUso85k3/q7AeJlFVnomkZy8R 6fpGp0XYb55agZ5SSMRPM+ANi3/yi4G/e4PElzmqM0veqEATiTf/SyFb0O3Gc6cUp2 XVb+Tbvcy58OewdM3m8fdtcdztb43U59PVy0NHQmYOsexu8z9DxCdVxSOcFtzfojfN 9ds8KfBtxpsMhzSQJhLFn4ugv+AG9SawsJFcSZYKjxZntQ3FCqWXKq9Q+rqIwicMCS medh7jDMPKJXxa2TXMnYdJmGaGiLXbfXFU8d1rpLyWVA0mKaeYEmPyKQEpDnvsRKl8 +UTVz7uiq27JgRqpHM1uSDVk= Received: by mail-pl1-f199.google.com with SMTP id d9443c01a7336-2bd40d4e551so29401585ad.2 for ; Wed, 27 May 2026 00:07:34 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1779865653; x=1780470453; 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=RL3A/DWhJ1bxzagj3aJAXjzrKf9AkBxwPnQsaoTAZkalgWU2NJQSTBKzekSyjdGepB ZaiPeKTSFBmuFIdwkM4xa16aHNHyK2jPXpmXgwCywaszMNI9kJxfK0fFeERFG3bwH13Q WU2/Ziv6ZdsWuGMqA8XMS+zLuwayMTdIyCQq3FCmmxiTnuUoZr2pmaAtdgQA8bt3NKcp 7KNPsNGW/XDssSgTxFOOUxuY/4WXrxr37n5BHMLVR0VQat6fMTjM5BbTJ61ug9LSQM/g OFEYNgCK+HBDcSM4C2GIFg9pTATIylOSyOFEYrf8eDvHg6KzfB3S/tLgmiexe9MnlKi2 8cmA== X-Forwarded-Encrypted: i=1; AFNElJ8gDWoBk/9X6S7uC3SAOOwtjE4F8kAd/qydMpT6OT5diL7tr5wp+fAVKxKWhmT+L9Oe+JiXOJfUQGBN81A=@vger.kernel.org X-Gm-Message-State: AOJu0YweSiLhBz1t/5Wh69Lvg5ve5TVb5V3KkAe82JgrgyUzBBsKJZM8 W6h5emWA4kGOsROkUnmFlCMOkuSgCYuf14RnrIELRl+I82XV1YMIyFUX5KJJ/k1lhkVyJajHMop dsNM0gfo1Gp9FQPsG0ypiM1qOryTC21zjKj15m5Bj6r3AwCn29tf1jYgIcm844PoZZrkje9TEy6 wplzd6dQ== X-Gm-Gg: Acq92OHWc6CWL5t+eZ86ZIB8mND6/xCZdUgn4hiPN5nSsGUSNKV/0P07FLzffKWPbnc WnVXRXmxjAjTIK6I+DTEHyKXOcBNu2pGxaFX6TLJovDpThJO88WZJT2EAflxVw1LD2Nz+mQoQnJ BBsgJuICjgYmsIRKocajGJR58GQ6uF92LuPvHmmSqIpwfROgBYxMJqU5QR9oCz7nPE55x1GVBd+ SoukcllPSMQ+f4Cu5OEU5gIiH/vQDTqzU83H8m0oHI4sTWv2P8l3R2xjzyIEDKPBlfv1ucHZP5i dQX9hnIWUv4m89eFRGo4i4KaZExgrnNO8AyToWyLB27/sqyjBqYGMNUKBrpoyopetzG4Lg+6Ipk bZiIzAspQhMsVO0WdU0UvEiJZ2T5E1Tz+gDZ7vcT49bqGSpnyCYtabdIVL5+x481b0ygk+vsxEf 7eBGm/horUIoj8r8K/ZG+RicJ5Um750OWrSHtAw2Lixb6wyhx71LamRywr91BOqZqI8sqG4VjMV dnzFtGJ4Z8OTtS0qhU= X-Received: by 2002:a17:903:110d:b0:2ae:6220:1539 with SMTP id d9443c01a7336-2beb06697b1mr128604995ad.6.1779865653331; Wed, 27 May 2026 00:07:33 -0700 (PDT) X-Received: by 2002:a17:903:110d:b0:2ae:6220:1539 with SMTP id d9443c01a7336-2beb06697b1mr128604865ad.6.1779865652940; Wed, 27 May 2026 00:07:32 -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-2beb5695f05sm184029595ad.6.2026.05.27.00.07.31 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 27 May 2026 00:07:32 -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 v2 1/1] thunderbolt: Defer DP tunnel teardown until display driver is ready Date: Wed, 27 May 2026 15:06:30 +0800 Message-Id: <20260527070630.175538-2-an.wu@canonical.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260527070630.175538-1-an.wu@canonical.com> References: <20260527070630.175538-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