From nobody Mon Feb 9 09:08:20 2026 Received: from mail-wm1-f42.google.com (mail-wm1-f42.google.com [209.85.128.42]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 92BF63D348B for ; Wed, 21 Jan 2026 05:27:52 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.42 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1768973275; cv=none; b=TuvalH3hR215SM1gCPfPoMZsr6q8vrTulVl75nuQC2coVcLg1+lx+Dikc2MqhEbHFXm9xnn4XONHYWnjX5eIfrYFoX26KpQ4wgRGx7F28K9OmVAQy8raG3x0SbbuhdN55AN3/jGkLvdnufiDCuFL9OAi2G6EJPHuPtW+WRuSyVY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1768973275; c=relaxed/simple; bh=4+qGIBYQiQgwnyRyeazcR24Uq0nZOk1tznx+Iw9+aZk=; h=From:To:Subject:Date:Message-ID:MIME-Version; b=bjEiqWn9lc78Ridi+g0/6/FSS2VqW2QgNKuVIsMMG20DVO/iXNu6tgpuARTwF9h1eQzZpg7yh/DCL6MNUHbf3N86Jwi5F081mOCdgxza+vk5IEu1Gwu7PU/zGcINF2YjavGH2iZHBG7f5PyCofPbAhI4zLAKL/NnrEE+XUtuXa0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=fail (p=reject dis=none) header.from=canonical.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=H2reOrOv; arc=none smtp.client-ip=209.85.128.42 Authentication-Results: smtp.subspace.kernel.org; dmarc=fail (p=reject dis=none) header.from=canonical.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="H2reOrOv" Received: by mail-wm1-f42.google.com with SMTP id 5b1f17b1804b1-4801c314c84so39979135e9.0 for ; Tue, 20 Jan 2026 21:27:52 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1768973271; x=1769578071; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:message-id:date:subject:to :from:sender:from:to:cc:subject:date:message-id:reply-to; bh=qIheEWlWP7khFkpwzE1LVxSC8InQJjkFPFcM+Ug8BTY=; b=H2reOrOvSquxwzOjwW9c0TPzrue7Zs6dYnC9c2LA6K34gUALQmC6TsYP4i6zF2a3y0 u9QNKzLghhprpFijd68zU9TgWRRhVUSptrIOnIb82wmxHVwwbMI5Jw50nVfxT9k6UMB8 b9PHE/gKAA2RpT09s60a3zYaG4ROsvkJwrZox1eaQsbGsXgeBesxcobkah0KAVJ/3W87 gRSOZqHQV/LdBG6yarIFaSU0VPjY3j06NXMIL7K4M3+wh7CW1XSQCaa+9EgUOmG2EqY4 Igi0lZSWyLFes4WJzo9qBm+u2CyhmeYAbGi6nDgBkRpI+3+7/h78B0I7mFmXjnUZbfwI EMXQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1768973271; x=1769578071; h=content-transfer-encoding:mime-version:message-id:date:subject:to :from:sender:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=qIheEWlWP7khFkpwzE1LVxSC8InQJjkFPFcM+Ug8BTY=; b=qysyxSaA+ZazD35AQ7M8CudxoOdHCh9E9e5uvxHT3H9J/evc7HUt1gRazqX6Isu4pg djwMTm0Q6wgbLBJQhM0HNYfFmEWkaIF4tag5LC+QIrdleEGfgScviHJYBHmjWIq6bMgY C3BCuiuKjT2z/BAjoSpGRfaRWg0G+ECkmMBC0n+jfxfBjjGZ9V/OLdSnreaZoc/M6ZXs HHYVEK9U99tM+x7zOm6JjReBCf0ZFte8mF/0EZXoKtNhnQOcPPEBvS91xGB92WmNANYq h1uFaaPo46X0g5iYvOlY0qpDbLi//n4nDGShxWGhxXPLupcHS39EhyjcK+MZirYlQ7An 4Upg== X-Forwarded-Encrypted: i=1; AJvYcCU/Ioscyfz6uOz4caw3HDCE5ngKqnQKBzoQOJuFbq0xtCJ7wjVnQ/9RbBAxf2UWNAmAR6ScRg/oaxKpr78=@vger.kernel.org X-Gm-Message-State: AOJu0YytWmDtEe1V0hcb7iacwGlsZ3gMxflb4cIKvNJrSSWjdD/jhWLf Wc091slpMtShKApRIATI0l1sWaJT5dIRQGlQfemOlAF/B2D210IqH8BUwcsIzio6 X-Gm-Gg: AZuq6aKTywZHF4/6mR2NWlm94RatHROz5JW9FpUYbn+1sD4nhkl78t0swOr3nmcQ8TI cyQLPhzItAOmbDbfey05C95zP5xZRLp/Oy6Eu+ZrZxWwsUmpMic5crxruoZ62BlTbE5GQ9EAYra LxLnai7piVjIwKZiqj+IgzcAVSMb2iY6XyD7lYnWOohZmG6Rsh/8xuZb+VkEjj79toQAx1Fy5FM GbUefXKCzpNWkZoPdkO9oreZUDPLjVc8iH2OwvpLKm9U7YhVGuqQ5deWSVYjKA5JzapZv0aLcUP k2NuStNpW+zo2R611pnMiXnuwcHadIFGhr80pXtEnnccvb9oQfYNBY6m6DuzUs5QURFls+xZt4f RfFo89mrLfCjOsPFGepR4LGGbkChEdfo5gNHQ2abr8sPTSQY+tQkVaNd3IYfkkXoKwDmeA1E7Bf 13LqiY3ct6oi9xtrIOj84aljbnA2cl/U6qAjt+gJkH X-Received: by 2002:a05:600c:1914:b0:47e:e72b:1fce with SMTP id 5b1f17b1804b1-4801e35868emr206208905e9.37.1768973270340; Tue, 20 Jan 2026 21:27:50 -0800 (PST) Received: from localhost (211-75-139-220.hinet-ip.hinet.net. [211.75.139.220]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-4356992201csm34580052f8f.2.2026.01.20.21.27.48 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 20 Jan 2026 21:27:49 -0800 (PST) Sender: AceLan Kao From: "Chia-Lin Kao (AceLan)" To: Andreas Noever , Mika Westerberg , Yehezkel Bernat , linux-usb@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH] thunderbolt: Fix PCIe device enumeration with delayed rescan Date: Wed, 21 Jan 2026 13:27:44 +0800 Message-ID: <20260121052744.233517-1-acelan.kao@canonical.com> X-Mailer: git-send-email 2.51.0 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" PCIe devices behind Thunderbolt tunnels may fail to enumerate when spurious hotplug events prevent pciehp from detecting link-up. Root cause: Spurious unplug events occur immediately after tunnel activation: [ 932.438] thunderbolt: acking hot unplug event on 702:2 [ 932.852] thunderbolt: PCIe Up path activation complete [ 932.855] thunderbolt: hotplug event for upstream port 702:2 (unplug: 0) [ 932.855] thunderbolt: hotplug event for upstream port 702:2 (unplug: 1) These events disrupt pciehp timing, causing device enumeration to fail ~70% of the time on affected hardware. Manual PCI rescan succeeds, proving devices are present and functional on the bus. Solution: Schedule delayed work (300ms) after tunnel activation to: 1. Check if pciehp successfully enumerated devices (device count increased) 2. If not, trigger pci_rescan_bus() to discover devices manually 3. Log results for observability The delayed work approach is non-blocking and only rescans when actually needed, avoiding overhead on systems where pciehp works correctly. Signed-off-by: Chia-Lin Kao (AceLan) --- Logs: https://people.canonical.com/~acelan/bugs/tbt_storage/ merged.out.bad: Plugged-in TBT storage, but eventually fails to enumerate merged.out.good: Plugged-in TBT storage, and successfully enumerates merged.out.patched: Plugged-in TBT storage, it should fail without this patch, but it works now --- drivers/thunderbolt/tb.c | 95 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index 293fc9f258a5c..1cfc9a265c453 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -11,6 +11,7 @@ #include #include #include +#include #include "tb.h" #include "tb_regs.h" @@ -18,6 +19,7 @@ #define TB_TIMEOUT 100 /* ms */ #define TB_RELEASE_BW_TIMEOUT 10000 /* ms */ +#define TB_PCIEHP_ENUMERATION_DELAY 300 /* ms */ /* * How many time bandwidth allocation request from graphics driver is @@ -83,6 +85,16 @@ struct tb_hotplug_event { int retry; }; +/* Delayed work to verify PCIe enumeration after tunnel activation */ +struct tb_pci_rescan_work { + struct delayed_work work; + struct tb *tb; + struct pci_bus *bus; + int devices_before; + u64 route; + u8 port; +}; + static void tb_scan_port(struct tb_port *port); static void tb_handle_hotplug(struct work_struct *work); static void tb_dp_resource_unavailable(struct tb *tb, struct tb_port *port, @@ -90,6 +102,60 @@ static void tb_dp_resource_unavailable(struct tb *tb, s= truct tb_port *port, static void tb_queue_dp_bandwidth_request(struct tb *tb, u64 route, u8 por= t, int retry, unsigned long delay); +static void tb_pci_rescan_work_fn(struct work_struct *work) +{ + struct tb_pci_rescan_work *rescan_work =3D + container_of(work, typeof(*rescan_work), work.work); + struct tb *tb =3D rescan_work->tb; + struct pci_bus *bus =3D rescan_work->bus; + int devices_after =3D 0; + struct pci_dev *dev; + struct tb_switch *sw; + struct tb_port *port; + + mutex_lock(&tb->lock); + + sw =3D tb_switch_find_by_route(tb, rescan_work->route); + if (!sw) { + tb_dbg(tb, "Switch at route %llx disappeared, skipping rescan\n", + rescan_work->route); + goto out_unlock; + } + + port =3D &sw->ports[rescan_work->port]; + + pci_lock_rescan_remove(); + for_each_pci_dev(dev) + devices_after++; + pci_unlock_rescan_remove(); + + if (devices_after > rescan_work->devices_before) { + tb_port_dbg(port, "pciehp enumerated %d new device(s)\n", + devices_after - rescan_work->devices_before); + } else { + tb_port_info(port, "pciehp failed to enumerate devices, triggering resca= n\n"); + + pci_lock_rescan_remove(); + pci_rescan_bus(bus); + + devices_after =3D 0; + for_each_pci_dev(dev) + devices_after++; + pci_unlock_rescan_remove(); + + if (devices_after > rescan_work->devices_before) + tb_port_info(port, "rescan found %d new device(s)\n", + devices_after - rescan_work->devices_before); + else + tb_port_warn(port, "no devices found even after rescan\n"); + } + + tb_switch_put(sw); +out_unlock: + mutex_unlock(&tb->lock); + kfree(rescan_work); +} + static void tb_queue_hotplug(struct tb *tb, u64 route, u8 port, bool unplu= g) { struct tb_hotplug_event *ev; @@ -2400,6 +2466,35 @@ static int tb_tunnel_pci(struct tb *tb, struct tb_sw= itch *sw) tb_sw_warn(sw, "failed to connect xHCI\n"); list_add_tail(&tunnel->list, &tcm->tunnel_list); + + /* Verify pciehp enumeration; trigger rescan if needed */ + if (tb->nhi && tb->nhi->pdev && tb->nhi->pdev->bus) { + struct pci_bus *bus =3D tb->nhi->pdev->bus; + struct pci_bus *scan_bus =3D bus->parent ? bus->parent : bus; + struct tb_pci_rescan_work *rescan_work; + struct pci_dev *dev; + int devices_before =3D 0; + + pci_lock_rescan_remove(); + for_each_pci_dev(dev) + devices_before++; + pci_unlock_rescan_remove(); + + rescan_work =3D kmalloc_obj(rescan_work, GFP_KERNEL); + if (!rescan_work) + return 0; + + rescan_work->tb =3D tb; + rescan_work->bus =3D scan_bus; + rescan_work->devices_before =3D devices_before; + rescan_work->route =3D tb_route(sw); + rescan_work->port =3D up->port; + + INIT_DELAYED_WORK(&rescan_work->work, tb_pci_rescan_work_fn); + queue_delayed_work(tb->wq, &rescan_work->work, + msecs_to_jiffies(TB_PCIEHP_ENUMERATION_DELAY)); + } + return 0; } -- 2.51.0