From nobody Mon Feb 9 03:46:54 2026 Received: from mail-wm1-f41.google.com (mail-wm1-f41.google.com [209.85.128.41]) (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 77E213A4F2F for ; Wed, 21 Jan 2026 06:09:05 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.41 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1768975749; cv=none; b=L+zwx4+ojDd9xlTuiDqXpFMr71ZSzfmpI7den98VCpSykjmR7tXRBmOx4KuWJSv8uH7ru7NX7JGPswiLG4rIhuZnjUlUvMNg2HZ5YGpWsGBjXez1StI7U/YYi81NmfV7HHqcWzres5pNf2oT1NIq9halowCfC4HLKMHjmr8sRug= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1768975749; c=relaxed/simple; bh=ggqOxQ+04DchXWqW64trK+O9ihGAYkkljmwiZ9snQMo=; h=From:To:Subject:Date:Message-ID:MIME-Version; b=AAeVS3MCQU5+7YPAot4c7VwwIi3ZPP6GWrwlVwSBk6itIFFuya9+DM8tYXkfF6eZrmueM/EBDzneHElJRBQk3qWBlHmhHSk+P9csmv0h9RVgysbY7bDtzTA6g9sxfd+HyG+GEnK8sa+amiiYVqJ58JVIpLAqAoLr3/o0gk55P7E= 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=Q4Xq/O/Q; arc=none smtp.client-ip=209.85.128.41 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="Q4Xq/O/Q" Received: by mail-wm1-f41.google.com with SMTP id 5b1f17b1804b1-47f5c2283b6so40718935e9.1 for ; Tue, 20 Jan 2026 22:09:04 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1768975743; x=1769580543; 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=hIoIuTeKvMzAiFV5gAIrAc6+7LNMHy4t9dDGrPfdKH0=; b=Q4Xq/O/QCoBBSjqVRXpfJ22rwd3i/9b2iXAFTXrj6CA51nLct91CyYPVepCkaiHtwT g94VP0RithyiiXHRuc3cBDv+1BqWdzGnXjl7tJOOxrhOt6r1rCtNT6wx8J/DCleItKYB ds/i7T1T0uJy3x3aTOniqE4/1i905jH43LABIEDurfiXtgnhNNH+saeZjNGqHt8KYWhu ebWAv6As+M26nw50hULrW0Y+7RKuhH+e975VoTpOQEIX4oovqiHEThIGYtPCccjuT+fU +GUsYB3cH1f5GkM3arKtZ1YtPxotq7n+unCFI2zPuP2z4p9D5FtUGMKxV5dVRUlJaHy1 mNHA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1768975743; x=1769580543; 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=hIoIuTeKvMzAiFV5gAIrAc6+7LNMHy4t9dDGrPfdKH0=; b=C5iemnUAXQiLVycPYCWlpub+jGZaVjwRjHHHVQFB2jl3NnkRJTPuSAErksuItW7oIk ITQ1QS5xs0EkG03LuSVNXn2xWEx3hKZ7x4cNcKcJFiPr/VrRqznNeLBODIrc/FPYW5bM FMhX4DdrDbipqHrlZVR/j+0Ey/b9Vgc9IrrjZactMg2pwbx8pcZoPvGf26Of4Gy4qF1g 4oBAR87apO8Zw0FoBT7OAffNfUPu9DhLFLWf4fYYtEpWgUmudY+fUU1oIQnA/cCrDf1p GAcYTyKBsQM6XMsm17KlJZEJcXMxXesvT31ryUOcpnTtIFbxT5PmH/n6PQV7ExND3Dfn vzlg== X-Forwarded-Encrypted: i=1; AJvYcCXfwXMSwVUgG0FpFa8OFxXAVrSCfEYVzrAOq1nOI14bkNKYRYG0GSyeWWewPalwI2icviqK/nHwK34K+MM=@vger.kernel.org X-Gm-Message-State: AOJu0Yx/zRmRcYz6zw1b5f+6gHuvTvc7O2vsx9OyeDy7V6kaGIDmcDci IC7+hDXAgh3jgxexPRPY8TI71IUg8asYMxCF/FrjMj6efARgw/0+3vcy X-Gm-Gg: AZuq6aLMGkrQApm26ItUFcNT3AxuEwku5zwDPSucIH/uzBlX/+/KkpZP60Mn61Y9+Tu W8wSMq9diPFuqcNwuiXWGudl4SZpzjZ9Q6Q23SByZXccOGjDPrEexjCgC/00VvRkrvnGSwBZmH7 rzBPhirr1HUWpbRabV8+mOpFWBrsmkl05+SMJhuLAM8WN7AScmjamyJx1qVyzgg4KbSnXGXBObU DdIfUmsjDFqcgkoRLUkdgIlVFkXIT1prwXbQsFwb0KC7AfHSAU2HQUW74+L1eEdnMetRY+3K94+ Jiu+lzjPKlMNBjl8wg8EoiP8uCTF7aTTfa2RnbsKW1sKVizVSrVQcVJmMjHZkPq3+qb89FXw9Gu +mPsZvpsDOEehKoKCa2o+zYf9HmQC9QgTSzuTo1N/mBjTCWmazoHnja1WBuxgBJiKQkxHqw81kf lmK2vvgpIcubv1W2Fj9FXiAzGJbjzYupEuD4KIYQRKw0B397Ljs+0= X-Received: by 2002:a5d:4652:0:b0:435:97fc:6f1c with SMTP id ffacd0b85a97d-43597fc6f6emr2466754f8f.25.1768975742480; Tue, 20 Jan 2026 22:09:02 -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-43569921da2sm34071988f8f.1.2026.01.20.22.09.00 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 20 Jan 2026 22:09:01 -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 v2] thunderbolt: Fix PCIe device enumeration with delayed rescan Date: Wed, 21 Jan 2026 14:08:57 +0800 Message-ID: <20260121060857.237777-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..a3761be6eeea4 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -11,6 +11,7 @@ #include #include #include +#include =20 #include "tb.h" #include "tb_regs.h" @@ -18,6 +19,7 @@ =20 #define TB_TIMEOUT 100 /* ms */ #define TB_RELEASE_BW_TIMEOUT 10000 /* ms */ +#define TB_PCIEHP_ENUMERATION_DELAY 300 /* ms */ =20 /* * How many time bandwidth allocation request from graphics driver is @@ -83,6 +85,16 @@ struct tb_hotplug_event { int retry; }; =20 +/* 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); =20 +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"); =20 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(sizeof(*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; } =20 --=20 2.51.0