* [PATCH v2 1/7] thunderbolt: Prefix NHI core functions with tb_ and tb_ring_
2026-11-10 11:48 [PATCH 0/7] thunderbolt: Add support platform NHI driver Chunfeng Yun
@ 2026-11-10 11:48 ` Chunfeng Yun
2026-11-10 12:42 ` Konrad Dybcio
2026-11-10 11:48 ` [PATCH v2 2/7] thunderbolt: Move ICM mailbox functions to icm.c and internalize interrupt work Chunfeng Yun
` (5 subsequent siblings)
6 siblings, 1 reply; 15+ messages in thread
From: Chunfeng Yun @ 2026-11-10 11:48 UTC (permalink / raw)
To: Mika Westerberg
Cc: Andreas Noever, Konrad Dybcio, Sven Peter, Yehezkel Bernat,
Matthias Brugger, Greg Kroah-Hartman, AngeloGioacchino Del Regno,
linux-kernel, linux-usb, linux-arm-kernel, linux-mediatek,
Chunfeng Yun
In preparation for modularizing PCIe NHI support and allowing PCIe
and platform NHI drivers to be built as separate modules atop the
Thunderbolt core framework, several NHI functions and structures will
need to be exported.
Currently, several of these functions lack standard subsystem prefixes,
risking namespace pollution or collisions across the flat kernel symbol
namespace:
- ring_msix() lacks any subsystem prefix and diverges from all other
exported ring functions in the driver, which use the "tb_ring_" prefix.
- nhi_probe(), nhi_shutdown(), and nhi_pm_ops lack the standard "tb_"
prefix used across the Thunderbolt public interface (such as struct
tb_nhi and struct tb_nhi_ops).
- nhi_msi() is named after PCI MSI, even though the ISR simply schedules
the interrupt work and can serve as a generic ISR for non-PCIe platform
controllers as well.
Rename these functions and symbols to conform to Thunderbolt subsystem
naming conventions before exporting them:
- ring_msix -> tb_ring_msix
- nhi_msi -> tb_nhi_isr
- nhi_probe -> tb_nhi_probe
- nhi_shutdown -> tb_nhi_shutdown
- nhi_pm_ops -> tb_nhi_pm_ops
Signed-off-by: Chunfeng Yun <chunfeng.yun@mediatek.com>
---
new patch
---
drivers/thunderbolt/nhi.c | 14 +++++++-------
drivers/thunderbolt/nhi.h | 10 +++++-----
drivers/thunderbolt/pci.c | 10 +++++-----
3 files changed, 17 insertions(+), 17 deletions(-)
diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c
index e99a3fcc4a29..c574c336f25b 100644
--- a/drivers/thunderbolt/nhi.c
+++ b/drivers/thunderbolt/nhi.c
@@ -535,7 +535,7 @@ static void ring_clear_msix(const struct tb_ring *ring)
4 * (ring->nhi->hop_count / 32));
}
-irqreturn_t ring_msix(int irq, void *data)
+irqreturn_t tb_ring_msix(int irq, void *data)
{
struct tb_ring *ring = data;
@@ -935,7 +935,7 @@ void tb_ring_free(struct tb_ring *ring)
/*
* ring->work can no longer be scheduled (it is scheduled only
- * by nhi_interrupt_work, ring_stop and ring_msix). Wait for it
+ * by nhi_interrupt_work, ring_stop and tb_ring_msix). Wait for it
* to finish before freeing the ring.
*/
flush_work(&ring->work);
@@ -1072,7 +1072,7 @@ void nhi_interrupt_work(struct work_struct *work)
spin_unlock_irq(&nhi->lock);
}
-irqreturn_t nhi_msi(int irq, void *data)
+irqreturn_t tb_nhi_isr(int irq, void *data)
{
struct tb_nhi *nhi = data;
schedule_work(&nhi->interrupt_work);
@@ -1216,7 +1216,7 @@ static int nhi_runtime_resume(struct device *dev)
return tb_domain_runtime_resume(tb);
}
-void nhi_shutdown(struct tb_nhi *nhi)
+void tb_nhi_shutdown(struct tb_nhi *nhi)
{
int i;
@@ -1306,7 +1306,7 @@ static struct tb *nhi_select_cm(struct tb_nhi *nhi)
return tb;
}
-int nhi_probe(struct tb_nhi *nhi)
+int tb_nhi_probe(struct tb_nhi *nhi)
{
struct device *dev = nhi->dev;
struct tb *tb;
@@ -1371,7 +1371,7 @@ int nhi_probe(struct tb_nhi *nhi)
*/
tb_domain_put(tb);
wait_for_completion(&nhi->domain_released);
- nhi_shutdown(nhi);
+ tb_nhi_shutdown(nhi);
return dev_err_probe(dev, res, "failed to add domain\n");
}
dev_set_drvdata(dev, tb);
@@ -1391,7 +1391,7 @@ int nhi_probe(struct tb_nhi *nhi)
* the tunnels asap. A corresponding pci quirk blocks the downstream bridges
* resume_noirq until we are done.
*/
-const struct dev_pm_ops nhi_pm_ops = {
+const struct dev_pm_ops tb_nhi_pm_ops = {
.suspend_noirq = nhi_suspend_noirq,
.resume_noirq = nhi_resume_noirq,
.freeze_noirq = nhi_freeze_noirq, /*
diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h
index 88af9ab9fde7..a36c62c49a5c 100644
--- a/drivers/thunderbolt/nhi.h
+++ b/drivers/thunderbolt/nhi.h
@@ -32,11 +32,11 @@ enum nhi_fw_mode nhi_mailbox_mode(struct tb_nhi *nhi);
void nhi_enable_int_throttling(struct tb_nhi *nhi);
void nhi_disable_interrupts(struct tb_nhi *nhi);
void nhi_interrupt_work(struct work_struct *work);
-irqreturn_t nhi_msi(int irq, void *data);
-irqreturn_t ring_msix(int irq, void *data);
-int nhi_probe(struct tb_nhi *nhi);
-void nhi_shutdown(struct tb_nhi *nhi);
-extern const struct dev_pm_ops nhi_pm_ops;
+irqreturn_t tb_nhi_isr(int irq, void *data);
+irqreturn_t tb_ring_msix(int irq, void *data);
+int tb_nhi_probe(struct tb_nhi *nhi);
+void tb_nhi_shutdown(struct tb_nhi *nhi);
+extern const struct dev_pm_ops tb_nhi_pm_ops;
/**
* struct tb_nhi_ops - NHI specific optional operations
diff --git a/drivers/thunderbolt/pci.c b/drivers/thunderbolt/pci.c
index 4408229d8ac7..2f0abf4d27a3 100644
--- a/drivers/thunderbolt/pci.c
+++ b/drivers/thunderbolt/pci.c
@@ -212,7 +212,7 @@ static int nhi_pci_init_msi(struct tb_nhi *nhi)
if (irq < 0)
return irq;
- res = devm_request_irq(&pdev->dev, irq, nhi_msi,
+ res = devm_request_irq(&pdev->dev, irq, tb_nhi_isr,
IRQF_NO_SUSPEND, "thunderbolt", nhi);
if (res)
return res;
@@ -281,7 +281,7 @@ static int nhi_pci_ring_request_msix(struct tb_ring *ring, bool no_suspend)
ring->irq = ret;
irqflags = no_suspend ? IRQF_NO_SUSPEND : 0;
- ret = request_irq(ring->irq, ring_msix, irqflags, "thunderbolt", ring);
+ ret = request_irq(ring->irq, tb_ring_msix, irqflags, "thunderbolt", ring);
if (ret)
goto err_ida_remove;
@@ -588,7 +588,7 @@ static int nhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
pci_set_master(pdev);
- return nhi_probe(&nhi_pci->nhi);
+ return tb_nhi_probe(&nhi_pci->nhi);
}
static void nhi_pci_do_remove(struct pci_dev *pdev, bool reset)
@@ -610,7 +610,7 @@ static void nhi_pci_do_remove(struct pci_dev *pdev, bool reset)
tb_domain_remove(tb);
wait_for_completion(&nhi->domain_released);
- nhi_shutdown(nhi);
+ tb_nhi_shutdown(nhi);
}
static void nhi_pci_remove(struct pci_dev *pdev)
@@ -724,7 +724,7 @@ static struct pci_driver nhi_driver = {
.probe = nhi_pci_probe,
.remove = nhi_pci_remove,
.shutdown = nhi_pci_shutdown,
- .driver.pm = &nhi_pm_ops,
+ .driver.pm = &tb_nhi_pm_ops,
};
static int __init nhi_init(void)
--
2.45.2
^ permalink raw reply [flat|nested] 15+ messages in thread* Re: [PATCH v2 1/7] thunderbolt: Prefix NHI core functions with tb_ and tb_ring_
2026-11-10 11:48 ` [PATCH v2 1/7] thunderbolt: Prefix NHI core functions with tb_ and tb_ring_ Chunfeng Yun
@ 2026-11-10 12:42 ` Konrad Dybcio
0 siblings, 0 replies; 15+ messages in thread
From: Konrad Dybcio @ 2026-11-10 12:42 UTC (permalink / raw)
To: Chunfeng Yun, Mika Westerberg
Cc: Andreas Noever, Sven Peter, Yehezkel Bernat, Matthias Brugger,
Greg Kroah-Hartman, AngeloGioacchino Del Regno, linux-kernel,
linux-usb, linux-arm-kernel, linux-mediatek
On 11/10/26 12:48 PM, Chunfeng Yun wrote:
> In preparation for modularizing PCIe NHI support and allowing PCIe
> and platform NHI drivers to be built as separate modules atop the
> Thunderbolt core framework, several NHI functions and structures will
> need to be exported.
>
> Currently, several of these functions lack standard subsystem prefixes,
> risking namespace pollution or collisions across the flat kernel symbol
> namespace:
>
> - ring_msix() lacks any subsystem prefix and diverges from all other
> exported ring functions in the driver, which use the "tb_ring_" prefix.
> - nhi_probe(), nhi_shutdown(), and nhi_pm_ops lack the standard "tb_"
> prefix used across the Thunderbolt public interface (such as struct
> tb_nhi and struct tb_nhi_ops).
> - nhi_msi() is named after PCI MSI, even though the ISR simply schedules
> the interrupt work and can serve as a generic ISR for non-PCIe platform
> controllers as well.
>
> Rename these functions and symbols to conform to Thunderbolt subsystem
> naming conventions before exporting them:
>
> - ring_msix -> tb_ring_msix
Maybe nhi_ instead? `tb` is used for the software connection manager-
related functions
Konrad
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v2 2/7] thunderbolt: Move ICM mailbox functions to icm.c and internalize interrupt work
2026-11-10 11:48 [PATCH 0/7] thunderbolt: Add support platform NHI driver Chunfeng Yun
2026-11-10 11:48 ` [PATCH v2 1/7] thunderbolt: Prefix NHI core functions with tb_ and tb_ring_ Chunfeng Yun
@ 2026-11-10 11:48 ` Chunfeng Yun
2026-11-10 14:24 ` Mika Westerberg
2026-11-10 11:48 ` [PATCH v2 3/7] thunderbolt: Make PCIe NHI support opt-in Chunfeng Yun
` (4 subsequent siblings)
6 siblings, 1 reply; 15+ messages in thread
From: Chunfeng Yun @ 2026-11-10 11:48 UTC (permalink / raw)
To: Mika Westerberg
Cc: Andreas Noever, Konrad Dybcio, Sven Peter, Yehezkel Bernat,
Matthias Brugger, Greg Kroah-Hartman, AngeloGioacchino Del Regno,
linux-kernel, linux-usb, linux-arm-kernel, linux-mediatek,
Chunfeng Yun
In preparation for modularizing PCIe NHI and ICM support as a separate
driver, clean up internal helper functions so that they do not need to
be exported:
nhi_mailbox_cmd() and nhi_mailbox_mode() are only used by the Intel
firmware Connection Manager (icm.c) to exchange mailbox commands with
firmware running on Intel PCIe NHIs. The Thunderbolt core framework,
software connection manager, and non-PCIe platform controllers do
not use this mailbox interface. Move both functions into icm.c and
mark them static.
nhi_interrupt_work was previously initialized in pci.c within
nhi_pci_init_msi(), requiring it to be visible across files. Move
INIT_WORK(&nhi->interrupt_work, ...) into tb_nhi_probe() so that
the workqueue item is initialized by the NHI core and
nhi_interrupt_work() can be marked static in nhi.c.
Signed-off-by: Chunfeng Yun <chunfeng.yun@mediatek.com>
---
new patch
---
drivers/thunderbolt/icm.c | 59 ++++++++++++++++++++++++++++++++++++
drivers/thunderbolt/nhi.c | 64 ++-------------------------------------
drivers/thunderbolt/nhi.h | 3 --
drivers/thunderbolt/pci.c | 2 --
4 files changed, 62 insertions(+), 66 deletions(-)
diff --git a/drivers/thunderbolt/icm.c b/drivers/thunderbolt/icm.c
index a295a5e6cc9c..736f551cd567 100644
--- a/drivers/thunderbolt/icm.c
+++ b/drivers/thunderbolt/icm.c
@@ -45,6 +45,7 @@
#define ICM_RETRIES 3
#define ICM_APPROVE_TIMEOUT 10000 /* ms */
#define ICM_MAX_LINK 4
+#define ICM_MAILBOX_TIMEOUT 500 /* ms */
static bool start_icm;
module_param(start_icm, bool, 0444);
@@ -269,6 +270,64 @@ static int pcie2cio_write(struct icm *icm, enum tb_cfg_space cs,
return pci2cio_wait_completion(icm, 5000);
}
+/**
+ * nhi_mailbox_cmd() - Send a command through NHI mailbox
+ * @nhi: Pointer to the NHI structure
+ * @cmd: Command to send
+ * @data: Data to be send with the command
+ *
+ * Sends mailbox command to the firmware running on NHI.
+ *
+ * Return: %0 on success, negative errno otherwise.
+ */
+static int nhi_mailbox_cmd(struct tb_nhi *nhi, enum nhi_mailbox_cmd cmd, u32 data)
+{
+ ktime_t timeout;
+ u32 val;
+
+ iowrite32(data, nhi->iobase + REG_INMAIL_DATA);
+
+ val = ioread32(nhi->iobase + REG_INMAIL_CMD);
+ val &= ~(REG_INMAIL_CMD_MASK | REG_INMAIL_ERROR);
+ val |= REG_INMAIL_OP_REQUEST | cmd;
+ iowrite32(val, nhi->iobase + REG_INMAIL_CMD);
+
+ timeout = ktime_add_ms(ktime_get(), ICM_MAILBOX_TIMEOUT);
+ do {
+ val = ioread32(nhi->iobase + REG_INMAIL_CMD);
+ if (!(val & REG_INMAIL_OP_REQUEST))
+ break;
+ usleep_range(10, 20);
+ } while (ktime_before(ktime_get(), timeout));
+
+ if (val & REG_INMAIL_OP_REQUEST)
+ return -ETIMEDOUT;
+ if (val & REG_INMAIL_ERROR)
+ return -EIO;
+
+ return 0;
+}
+
+/**
+ * nhi_mailbox_mode() - Return current firmware operation mode
+ * @nhi: Pointer to the NHI structure
+ *
+ * The function reads current firmware operation mode using NHI mailbox
+ * registers and returns it to the caller.
+ *
+ * Return: &enum nhi_fw_mode.
+ */
+static enum nhi_fw_mode nhi_mailbox_mode(struct tb_nhi *nhi)
+{
+ u32 val;
+
+ val = ioread32(nhi->iobase + REG_OUTMAIL_CMD);
+ val &= REG_OUTMAIL_CMD_OPMODE_MASK;
+ val >>= REG_OUTMAIL_CMD_OPMODE_SHIFT;
+
+ return (enum nhi_fw_mode)val;
+}
+
static bool icm_match(const struct tb_cfg_request *req,
const struct ctl_pkg *pkg)
{
diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c
index c574c336f25b..1e3a58636d3d 100644
--- a/drivers/thunderbolt/nhi.c
+++ b/drivers/thunderbolt/nhi.c
@@ -35,8 +35,6 @@
*/
#define RING_E2E_RESERVED_HOPID RING_FIRST_USABLE_HOPID
-#define NHI_MAILBOX_TIMEOUT 500 /* ms */
-
static bool host_reset = true;
module_param(host_reset, bool, 0444);
MODULE_PARM_DESC(host_reset, "reset USB4 host router (default: true)");
@@ -964,65 +962,7 @@ int tb_ring_throttling(struct tb_ring *ring, unsigned int interval_nsec)
}
EXPORT_SYMBOL_GPL(tb_ring_throttling);
-/**
- * nhi_mailbox_cmd() - Send a command through NHI mailbox
- * @nhi: Pointer to the NHI structure
- * @cmd: Command to send
- * @data: Data to be send with the command
- *
- * Sends mailbox command to the firmware running on NHI.
- *
- * Return: %0 on success, negative errno otherwise.
- */
-int nhi_mailbox_cmd(struct tb_nhi *nhi, enum nhi_mailbox_cmd cmd, u32 data)
-{
- ktime_t timeout;
- u32 val;
-
- iowrite32(data, nhi->iobase + REG_INMAIL_DATA);
-
- val = ioread32(nhi->iobase + REG_INMAIL_CMD);
- val &= ~(REG_INMAIL_CMD_MASK | REG_INMAIL_ERROR);
- val |= REG_INMAIL_OP_REQUEST | cmd;
- iowrite32(val, nhi->iobase + REG_INMAIL_CMD);
-
- timeout = ktime_add_ms(ktime_get(), NHI_MAILBOX_TIMEOUT);
- do {
- val = ioread32(nhi->iobase + REG_INMAIL_CMD);
- if (!(val & REG_INMAIL_OP_REQUEST))
- break;
- usleep_range(10, 20);
- } while (ktime_before(ktime_get(), timeout));
-
- if (val & REG_INMAIL_OP_REQUEST)
- return -ETIMEDOUT;
- if (val & REG_INMAIL_ERROR)
- return -EIO;
-
- return 0;
-}
-
-/**
- * nhi_mailbox_mode() - Return current firmware operation mode
- * @nhi: Pointer to the NHI structure
- *
- * The function reads current firmware operation mode using NHI mailbox
- * registers and returns it to the caller.
- *
- * Return: &enum nhi_fw_mode.
- */
-enum nhi_fw_mode nhi_mailbox_mode(struct tb_nhi *nhi)
-{
- u32 val;
-
- val = ioread32(nhi->iobase + REG_OUTMAIL_CMD);
- val &= REG_OUTMAIL_CMD_OPMODE_MASK;
- val >>= REG_OUTMAIL_CMD_OPMODE_SHIFT;
-
- return (enum nhi_fw_mode)val;
-}
-
-void nhi_interrupt_work(struct work_struct *work)
+static void nhi_interrupt_work(struct work_struct *work)
{
struct tb_nhi *nhi = container_of(work, typeof(*nhi), interrupt_work);
int value = 0; /* Suppress uninitialized usage warning. */
@@ -1331,6 +1271,8 @@ int tb_nhi_probe(struct tb_nhi *nhi)
if (!nhi->tx_rings || !nhi->rx_rings || !nhi->interrupt_mask)
return -ENOMEM;
+ INIT_WORK(&nhi->interrupt_work, nhi_interrupt_work);
+
nhi_reset(nhi);
/* In case someone left them on. */
diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h
index a36c62c49a5c..3dd6ace6150d 100644
--- a/drivers/thunderbolt/nhi.h
+++ b/drivers/thunderbolt/nhi.h
@@ -27,11 +27,8 @@ enum nhi_mailbox_cmd {
NHI_MAILBOX_ALLOW_ALL_DEVS = 0x23,
};
-int nhi_mailbox_cmd(struct tb_nhi *nhi, enum nhi_mailbox_cmd cmd, u32 data);
-enum nhi_fw_mode nhi_mailbox_mode(struct tb_nhi *nhi);
void nhi_enable_int_throttling(struct tb_nhi *nhi);
void nhi_disable_interrupts(struct tb_nhi *nhi);
-void nhi_interrupt_work(struct work_struct *work);
irqreturn_t tb_nhi_isr(int irq, void *data);
irqreturn_t tb_ring_msix(int irq, void *data);
int tb_nhi_probe(struct tb_nhi *nhi);
diff --git a/drivers/thunderbolt/pci.c b/drivers/thunderbolt/pci.c
index 2f0abf4d27a3..275389903090 100644
--- a/drivers/thunderbolt/pci.c
+++ b/drivers/thunderbolt/pci.c
@@ -206,8 +206,6 @@ static int nhi_pci_init_msi(struct tb_nhi *nhi)
if (nvec < 0)
return nvec;
- INIT_WORK(&nhi->interrupt_work, nhi_interrupt_work);
-
irq = pci_irq_vector(pdev, 0);
if (irq < 0)
return irq;
--
2.45.2
^ permalink raw reply [flat|nested] 15+ messages in thread* Re: [PATCH v2 2/7] thunderbolt: Move ICM mailbox functions to icm.c and internalize interrupt work
2026-11-10 11:48 ` [PATCH v2 2/7] thunderbolt: Move ICM mailbox functions to icm.c and internalize interrupt work Chunfeng Yun
@ 2026-11-10 14:24 ` Mika Westerberg
0 siblings, 0 replies; 15+ messages in thread
From: Mika Westerberg @ 2026-11-10 14:24 UTC (permalink / raw)
To: Chunfeng Yun
Cc: Mika Westerberg, Andreas Noever, Konrad Dybcio, Sven Peter,
Yehezkel Bernat, Matthias Brugger, Greg Kroah-Hartman,
AngeloGioacchino Del Regno, linux-kernel, linux-usb,
linux-arm-kernel, linux-mediatek
On Tue, Nov 10, 2026 at 07:48:50PM +0800, Chunfeng Yun wrote:
> In preparation for modularizing PCIe NHI and ICM support as a separate
> driver, clean up internal helper functions so that they do not need to
> be exported:
>
> nhi_mailbox_cmd() and nhi_mailbox_mode() are only used by the Intel
> firmware Connection Manager (icm.c) to exchange mailbox commands with
> firmware running on Intel PCIe NHIs. The Thunderbolt core framework,
> software connection manager, and non-PCIe platform controllers do
> not use this mailbox interface. Move both functions into icm.c and
> mark them static.
>
> nhi_interrupt_work was previously initialized in pci.c within
> nhi_pci_init_msi(), requiring it to be visible across files. Move
> INIT_WORK(&nhi->interrupt_work, ...) into tb_nhi_probe() so that
> the workqueue item is initialized by the NHI core and
> nhi_interrupt_work() can be marked static in nhi.c.
>
> Signed-off-by: Chunfeng Yun <chunfeng.yun@mediatek.com>
> ---
> new patch
> ---
> drivers/thunderbolt/icm.c | 59 ++++++++++++++++++++++++++++++++++++
> drivers/thunderbolt/nhi.c | 64 ++-------------------------------------
> drivers/thunderbolt/nhi.h | 3 --
> drivers/thunderbolt/pci.c | 2 --
> 4 files changed, 62 insertions(+), 66 deletions(-)
>
> diff --git a/drivers/thunderbolt/icm.c b/drivers/thunderbolt/icm.c
> index a295a5e6cc9c..736f551cd567 100644
> --- a/drivers/thunderbolt/icm.c
> +++ b/drivers/thunderbolt/icm.c
> @@ -45,6 +45,7 @@
> #define ICM_RETRIES 3
> #define ICM_APPROVE_TIMEOUT 10000 /* ms */
> #define ICM_MAX_LINK 4
> +#define ICM_MAILBOX_TIMEOUT 500 /* ms */
>
> static bool start_icm;
> module_param(start_icm, bool, 0444);
> @@ -269,6 +270,64 @@ static int pcie2cio_write(struct icm *icm, enum tb_cfg_space cs,
> return pci2cio_wait_completion(icm, 5000);
> }
>
> +/**
> + * nhi_mailbox_cmd() - Send a command through NHI mailbox
> + * @nhi: Pointer to the NHI structure
> + * @cmd: Command to send
> + * @data: Data to be send with the command
> + *
> + * Sends mailbox command to the firmware running on NHI.
> + *
> + * Return: %0 on success, negative errno otherwise.
> + */
> +static int nhi_mailbox_cmd(struct tb_nhi *nhi, enum nhi_mailbox_cmd cmd, u32 data)
> +{
> + ktime_t timeout;
> + u32 val;
> +
> + iowrite32(data, nhi->iobase + REG_INMAIL_DATA);
> +
> + val = ioread32(nhi->iobase + REG_INMAIL_CMD);
> + val &= ~(REG_INMAIL_CMD_MASK | REG_INMAIL_ERROR);
> + val |= REG_INMAIL_OP_REQUEST | cmd;
> + iowrite32(val, nhi->iobase + REG_INMAIL_CMD);
> +
> + timeout = ktime_add_ms(ktime_get(), ICM_MAILBOX_TIMEOUT);
> + do {
> + val = ioread32(nhi->iobase + REG_INMAIL_CMD);
> + if (!(val & REG_INMAIL_OP_REQUEST))
> + break;
> + usleep_range(10, 20);
> + } while (ktime_before(ktime_get(), timeout));
> +
> + if (val & REG_INMAIL_OP_REQUEST)
> + return -ETIMEDOUT;
> + if (val & REG_INMAIL_ERROR)
> + return -EIO;
> +
> + return 0;
> +}
> +
> +/**
> + * nhi_mailbox_mode() - Return current firmware operation mode
> + * @nhi: Pointer to the NHI structure
> + *
> + * The function reads current firmware operation mode using NHI mailbox
> + * registers and returns it to the caller.
> + *
> + * Return: &enum nhi_fw_mode.
> + */
> +static enum nhi_fw_mode nhi_mailbox_mode(struct tb_nhi *nhi)
> +{
> + u32 val;
> +
> + val = ioread32(nhi->iobase + REG_OUTMAIL_CMD);
> + val &= REG_OUTMAIL_CMD_OPMODE_MASK;
> + val >>= REG_OUTMAIL_CMD_OPMODE_SHIFT;
> +
> + return (enum nhi_fw_mode)val;
> +}
> +
> static bool icm_match(const struct tb_cfg_request *req,
> const struct ctl_pkg *pkg)
> {
> diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c
> index c574c336f25b..1e3a58636d3d 100644
> --- a/drivers/thunderbolt/nhi.c
> +++ b/drivers/thunderbolt/nhi.c
> @@ -35,8 +35,6 @@
> */
> #define RING_E2E_RESERVED_HOPID RING_FIRST_USABLE_HOPID
>
> -#define NHI_MAILBOX_TIMEOUT 500 /* ms */
> -
> static bool host_reset = true;
> module_param(host_reset, bool, 0444);
> MODULE_PARM_DESC(host_reset, "reset USB4 host router (default: true)");
> @@ -964,65 +962,7 @@ int tb_ring_throttling(struct tb_ring *ring, unsigned int interval_nsec)
> }
> EXPORT_SYMBOL_GPL(tb_ring_throttling);
>
> -/**
> - * nhi_mailbox_cmd() - Send a command through NHI mailbox
> - * @nhi: Pointer to the NHI structure
> - * @cmd: Command to send
> - * @data: Data to be send with the command
> - *
> - * Sends mailbox command to the firmware running on NHI.
> - *
> - * Return: %0 on success, negative errno otherwise.
> - */
> -int nhi_mailbox_cmd(struct tb_nhi *nhi, enum nhi_mailbox_cmd cmd, u32 data)
> -{
> - ktime_t timeout;
> - u32 val;
> -
> - iowrite32(data, nhi->iobase + REG_INMAIL_DATA);
> -
> - val = ioread32(nhi->iobase + REG_INMAIL_CMD);
> - val &= ~(REG_INMAIL_CMD_MASK | REG_INMAIL_ERROR);
> - val |= REG_INMAIL_OP_REQUEST | cmd;
> - iowrite32(val, nhi->iobase + REG_INMAIL_CMD);
> -
> - timeout = ktime_add_ms(ktime_get(), NHI_MAILBOX_TIMEOUT);
> - do {
> - val = ioread32(nhi->iobase + REG_INMAIL_CMD);
> - if (!(val & REG_INMAIL_OP_REQUEST))
> - break;
> - usleep_range(10, 20);
> - } while (ktime_before(ktime_get(), timeout));
> -
> - if (val & REG_INMAIL_OP_REQUEST)
> - return -ETIMEDOUT;
> - if (val & REG_INMAIL_ERROR)
> - return -EIO;
> -
> - return 0;
> -}
> -
> -/**
> - * nhi_mailbox_mode() - Return current firmware operation mode
> - * @nhi: Pointer to the NHI structure
> - *
> - * The function reads current firmware operation mode using NHI mailbox
> - * registers and returns it to the caller.
> - *
> - * Return: &enum nhi_fw_mode.
> - */
> -enum nhi_fw_mode nhi_mailbox_mode(struct tb_nhi *nhi)
> -{
> - u32 val;
> -
> - val = ioread32(nhi->iobase + REG_OUTMAIL_CMD);
> - val &= REG_OUTMAIL_CMD_OPMODE_MASK;
> - val >>= REG_OUTMAIL_CMD_OPMODE_SHIFT;
> -
> - return (enum nhi_fw_mode)val;
> -}
> -
> -void nhi_interrupt_work(struct work_struct *work)
> +static void nhi_interrupt_work(struct work_struct *work)
> {
> struct tb_nhi *nhi = container_of(work, typeof(*nhi), interrupt_work);
> int value = 0; /* Suppress uninitialized usage warning. */
> @@ -1331,6 +1271,8 @@ int tb_nhi_probe(struct tb_nhi *nhi)
> if (!nhi->tx_rings || !nhi->rx_rings || !nhi->interrupt_mask)
> return -ENOMEM;
>
> + INIT_WORK(&nhi->interrupt_work, nhi_interrupt_work);
> +
This has weird indent.
> nhi_reset(nhi);
>
> /* In case someone left them on. */
> diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h
> index a36c62c49a5c..3dd6ace6150d 100644
> --- a/drivers/thunderbolt/nhi.h
> +++ b/drivers/thunderbolt/nhi.h
> @@ -27,11 +27,8 @@ enum nhi_mailbox_cmd {
> NHI_MAILBOX_ALLOW_ALL_DEVS = 0x23,
> };
>
> -int nhi_mailbox_cmd(struct tb_nhi *nhi, enum nhi_mailbox_cmd cmd, u32 data);
> -enum nhi_fw_mode nhi_mailbox_mode(struct tb_nhi *nhi);
> void nhi_enable_int_throttling(struct tb_nhi *nhi);
> void nhi_disable_interrupts(struct tb_nhi *nhi);
> -void nhi_interrupt_work(struct work_struct *work);
> irqreturn_t tb_nhi_isr(int irq, void *data);
> irqreturn_t tb_ring_msix(int irq, void *data);
> int tb_nhi_probe(struct tb_nhi *nhi);
> diff --git a/drivers/thunderbolt/pci.c b/drivers/thunderbolt/pci.c
> index 2f0abf4d27a3..275389903090 100644
> --- a/drivers/thunderbolt/pci.c
> +++ b/drivers/thunderbolt/pci.c
> @@ -206,8 +206,6 @@ static int nhi_pci_init_msi(struct tb_nhi *nhi)
> if (nvec < 0)
> return nvec;
>
> - INIT_WORK(&nhi->interrupt_work, nhi_interrupt_work);
> -
> irq = pci_irq_vector(pdev, 0);
> if (irq < 0)
> return irq;
> --
> 2.45.2
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v2 3/7] thunderbolt: Make PCIe NHI support opt-in
2026-11-10 11:48 [PATCH 0/7] thunderbolt: Add support platform NHI driver Chunfeng Yun
2026-11-10 11:48 ` [PATCH v2 1/7] thunderbolt: Prefix NHI core functions with tb_ and tb_ring_ Chunfeng Yun
2026-11-10 11:48 ` [PATCH v2 2/7] thunderbolt: Move ICM mailbox functions to icm.c and internalize interrupt work Chunfeng Yun
@ 2026-11-10 11:48 ` Chunfeng Yun
2026-11-10 11:48 ` [PATCH v2 4/7] thunderbolt: add platform NHI driver Chunfeng Yun
` (3 subsequent siblings)
6 siblings, 0 replies; 15+ messages in thread
From: Chunfeng Yun @ 2026-11-10 11:48 UTC (permalink / raw)
To: Mika Westerberg
Cc: Andreas Noever, Konrad Dybcio, Sven Peter, Yehezkel Bernat,
Matthias Brugger, Greg Kroah-Hartman, AngeloGioacchino Del Regno,
linux-kernel, linux-usb, linux-arm-kernel, linux-mediatek,
Chunfeng Yun
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1: Type: text/plain; charset="y", Size: 16800 bytes --]
From: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
Currently, the USB4/Thunderbolt subsystem directly depends on PCI,
tightly bundling the core framework with the PCIe-attached Native Host
Interface (NHI) driver (pci.c) and the Intel Connection Manager (icm.c)
into a single module (thunderbolt.o).
With support for platform/SoC-attached USB4 host controllers coming in
(such as on ARM64 systems), the core USB4 framework should no longer
have a hard dependency on PCI. Furthermore, embedded or SoC systems
without PCIe-attached host routers should be able to omit PCIe NHI
support to reduce kernel footprint.
Decouple the PCIe NHI and ICM drivers from the core framework:
1. Kconfig & Makefile:
- Remove "depends on PCI" from CONFIG_USB4.
- Introduce CONFIG_USB4_PCI to build PCIe NHI and ICM support as
a separate module (thunderbolt_pci.ko).
2. Driver Registration:
- Make the core thunderbolt module standalone by converting
tb_domain_init/exit() to postcore_initcall() and module_exit().
- Register the PCIe driver in thunderbolt_pci via module_pci_driver().
3. Connection Manager Selection:
- Add an optional .select_cm() hook to struct tb_nhi_ops so that the
PCIe driver can invoke icm_probe() for legacy/firmware-CM controllers
without hardcoding ICM calls into the core nhi.c.
4. Symbol Exports:
- Export necessary APIs required by pci and platform drivers.
Signed-off-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
Signed-off-by: Chunfeng Yun <chunfeng.yun@mediatek.com>
---
v2:
cherry-pick the patch of Konrad to fix the issues of this series, and
also do some changes:
* Renamed CONFIG_USB4_PCIE and thunderbolt_pcie.ko to CONFIG_USB4_PCI
and thunderbolt_pci.ko.
* Updated exported NHI symbols to standard tb_ / tb_ring_ prefixes
(e.g., ring_msix → tb_ring_msix, nhi_msi → tb_nhi_isr).
* Eliminated exports for nhi_mailbox_* and nhi_interrupt_work by
keeping them internal/static (handled by preparatory patches in v2).
* Added missing trailing semicolon to module_pci_driver(nhi_driver);.
* Expanded the commit message;
* Fix cherry-pick conflict caused by .reset_interface();
---
drivers/thunderbolt/Kconfig | 11 +++++++++-
drivers/thunderbolt/Makefile | 7 +++++--
drivers/thunderbolt/ctl.c | 6 +++++-
drivers/thunderbolt/domain.c | 14 +++++++++++--
drivers/thunderbolt/nhi.c | 14 ++++++-------
drivers/thunderbolt/nhi.h | 2 ++
drivers/thunderbolt/pci.c | 39 +++++++++++++----------------------
drivers/thunderbolt/switch.c | 9 ++++++++
drivers/thunderbolt/tb.h | 2 --
drivers/thunderbolt/tunnel.c | 1 +
drivers/thunderbolt/xdomain.c | 5 +++++
11 files changed, 70 insertions(+), 40 deletions(-)
diff --git a/drivers/thunderbolt/Kconfig b/drivers/thunderbolt/Kconfig
index 294b3227a545..605af440457a 100644
--- a/drivers/thunderbolt/Kconfig
+++ b/drivers/thunderbolt/Kconfig
@@ -1,7 +1,6 @@
# SPDX-License-Identifier: GPL-2.0-only
menuconfig USB4
tristate "Unified support for USB4 and Thunderbolt"
- depends on PCI
select APPLE_PROPERTIES if EFI_STUB && X86
select CRC32
select CRYPTO_LIB_SHA256
@@ -18,6 +17,16 @@ menuconfig USB4
if USB4
+config USB4_PCI
+ depends on PCI
+ tristate "Support for PCIe-attached USB4 and Thunderbolt controllers"
+ help
+ Say Y here to add support for the USB4 and Thunderbolt host
+ routers attached over the PCIe bus, commonly found on x86 PCs.
+
+ To compile this driver a module, choose M here. The module will be
+ called thunderbolt_pci.
+
config USB4_CONFIGFS
def_tristate USB4
depends on CONFIGFS_FS && !(USB4=y && CONFIGFS_FS=m)
diff --git a/drivers/thunderbolt/Makefile b/drivers/thunderbolt/Makefile
index beb054c3126b..a563bf2b0345 100644
--- a/drivers/thunderbolt/Makefile
+++ b/drivers/thunderbolt/Makefile
@@ -1,10 +1,13 @@
# SPDX-License-Identifier: GPL-2.0-only
ccflags-y := -I$(src)
obj-${CONFIG_USB4} := thunderbolt.o
-thunderbolt-objs := nhi.o ctl.o tb.o switch.o cap.o pci.o path.o tunnel.o eeprom.o
-thunderbolt-objs += domain.o dma_port.o icm.o property.o xdomain.o lc.o tmu.o usb4.o
+thunderbolt-objs := nhi.o ctl.o tb.o switch.o cap.o path.o tunnel.o eeprom.o
+thunderbolt-objs += domain.o dma_port.o property.o xdomain.o lc.o tmu.o usb4.o
thunderbolt-objs += usb4_port.o nvm.o retimer.o quirks.o clx.o
+obj-${CONFIG_USB4_PCI} += thunderbolt_pci.o
+thunderbolt_pci-y := pci.o icm.o
+
thunderbolt-${CONFIG_ACPI} += acpi.o
thunderbolt-$(CONFIG_DEBUG_FS) += debugfs.o
thunderbolt-$(CONFIG_USB4_CONFIGFS) += configfs.o
diff --git a/drivers/thunderbolt/ctl.c b/drivers/thunderbolt/ctl.c
index 965988b18608..96717a0a94b1 100644
--- a/drivers/thunderbolt/ctl.c
+++ b/drivers/thunderbolt/ctl.c
@@ -9,7 +9,6 @@
#include <linux/crc32.h>
#include <linux/delay.h>
#include <linux/slab.h>
-#include <linux/pci.h>
#include <linux/dmapool.h>
#include <linux/workqueue.h>
@@ -96,6 +95,7 @@ struct tb_cfg_request *tb_cfg_request_alloc(void)
return req;
}
+EXPORT_SYMBOL_GPL(tb_cfg_request_alloc);
/**
* tb_cfg_request_get() - Increase refcount of a request
@@ -128,6 +128,7 @@ void tb_cfg_request_put(struct tb_cfg_request *req)
kref_put(&req->kref, tb_cfg_request_destroy);
mutex_unlock(&tb_cfg_request_lock);
}
+EXPORT_SYMBOL_GPL(tb_cfg_request_put);
static int tb_cfg_request_enqueue(struct tb_ctl *ctl,
struct tb_cfg_request *req)
@@ -575,6 +576,7 @@ int tb_cfg_request(struct tb_ctl *ctl, struct tb_cfg_request *req,
tb_cfg_request_dequeue(req);
return ret;
}
+EXPORT_SYMBOL_GPL(tb_cfg_request);
/**
* tb_cfg_request_cancel() - Cancel a control request
@@ -634,6 +636,7 @@ struct tb_cfg_result tb_cfg_request_sync(struct tb_ctl *ctl,
return req->result;
}
+EXPORT_SYMBOL_GPL(tb_cfg_request_sync);
/* public interface, alloc/start/stop/free */
@@ -1009,6 +1012,7 @@ struct tb_cfg_result tb_cfg_read_raw(struct tb_ctl *ctl, void *buffer,
memcpy(buffer, &reply.data, 4 * length);
return res;
}
+EXPORT_SYMBOL_GPL(tb_cfg_read_raw);
/**
* tb_cfg_write_raw() - write from buffer into config space
diff --git a/drivers/thunderbolt/domain.c b/drivers/thunderbolt/domain.c
index 24611f05b3cd..114ddd5be25b 100644
--- a/drivers/thunderbolt/domain.c
+++ b/drivers/thunderbolt/domain.c
@@ -313,6 +313,7 @@ const struct bus_type tb_bus_type = {
.remove = tb_service_remove,
.shutdown = tb_service_shutdown,
};
+EXPORT_SYMBOL_GPL(tb_bus_type);
static void tb_domain_release(struct device *dev)
{
@@ -421,6 +422,7 @@ struct tb *tb_domain_alloc(struct tb_nhi *nhi, int timeout_msec, size_t privsize
return NULL;
}
+EXPORT_SYMBOL_GPL(tb_domain_alloc);
/**
* tb_domain_add() - Add domain to the system
@@ -514,6 +516,7 @@ void tb_domain_remove(struct tb *tb)
device_unregister(&tb->dev);
}
+EXPORT_SYMBOL_GPL(tb_domain_remove);
/**
* tb_domain_suspend_noirq() - Suspend a domain
@@ -880,8 +883,9 @@ int tb_domain_unregister_unplugged_xdomains(struct tb *tb)
return ctx.n;
}
+EXPORT_SYMBOL_GPL(tb_domain_unregister_unplugged_xdomains);
-int tb_domain_init(void)
+static int __init tb_domain_init(void)
{
int ret;
@@ -907,7 +911,7 @@ int tb_domain_init(void)
return ret;
}
-void tb_domain_exit(void)
+static void __exit tb_domain_exit(void)
{
bus_unregister(&tb_bus_type);
ida_destroy(&tb_domain_ida);
@@ -917,3 +921,9 @@ void tb_domain_exit(void)
tb_debugfs_exit();
tb_configfs_exit();
}
+
+postcore_initcall(tb_domain_init);
+module_exit(tb_domain_exit);
+
+MODULE_DESCRIPTION("Thunderbolt/USB4 core driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c
index 1e3a58636d3d..9662d1dd410f 100644
--- a/drivers/thunderbolt/nhi.c
+++ b/drivers/thunderbolt/nhi.c
@@ -546,6 +546,7 @@ irqreturn_t tb_ring_msix(int irq, void *data)
return IRQ_HANDLED;
}
+EXPORT_SYMBOL_GPL(tb_ring_msix);
static int nhi_alloc_hop(struct tb_nhi *nhi, struct tb_ring *ring)
{
@@ -1018,6 +1019,7 @@ irqreturn_t tb_nhi_isr(int irq, void *data)
schedule_work(&nhi->interrupt_work);
return IRQ_HANDLED;
}
+EXPORT_SYMBOL_GPL(tb_nhi_isr);
static int __nhi_suspend_noirq(struct device *dev, bool wakeup)
{
@@ -1175,6 +1177,7 @@ void tb_nhi_shutdown(struct tb_nhi *nhi)
if (nhi->ops->shutdown)
nhi->ops->shutdown(nhi);
}
+EXPORT_SYMBOL_GPL(tb_nhi_shutdown);
static void nhi_reset(struct tb_nhi *nhi)
{
@@ -1216,13 +1219,8 @@ static struct tb *nhi_select_cm(struct tb_nhi *nhi)
* USB4 case is simple. If we got control of any of the
* capabilities, we use software CM.
*/
- if (!tb_acpi_is_native()) {
- /*
- * Either firmware based CM is running (we did not get
- * control from the firmware) or this is pre-USB4 PC so
- * try first firmware CM and then fallback to software CM.
- */
- tb = icm_probe(nhi);
+ if (!tb_acpi_is_native() && nhi->ops->select_cm) {
+ tb = nhi->ops->select_cm(nhi);
if (tb)
return tb;
}
@@ -1327,6 +1325,7 @@ int tb_nhi_probe(struct tb_nhi *nhi)
return 0;
}
+EXPORT_SYMBOL_GPL(tb_nhi_probe);
/*
* The tunneled pci bridges are siblings of us. Use resume_noirq to reenable
@@ -1349,3 +1348,4 @@ const struct dev_pm_ops tb_nhi_pm_ops = {
.runtime_suspend = nhi_runtime_suspend,
.runtime_resume = nhi_runtime_resume,
};
+EXPORT_SYMBOL_GPL(tb_nhi_pm_ops);
diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h
index 3dd6ace6150d..65f9daf36068 100644
--- a/drivers/thunderbolt/nhi.h
+++ b/drivers/thunderbolt/nhi.h
@@ -50,6 +50,7 @@ extern const struct dev_pm_ops tb_nhi_pm_ops;
* @release_ring_irq: NHI specific interrupt release hook
* @is_present: Whether the device is currently present on the parent bus
* @init_interrupts: NHI specific interrupt initialization hook
+ * @select_cm: optional hook to select a custom CM
*/
struct tb_nhi_ops {
int (*init)(struct tb_nhi *nhi);
@@ -65,6 +66,7 @@ struct tb_nhi_ops {
void (*release_ring_irq)(struct tb_ring *ring);
bool (*is_present)(struct tb_nhi *nhi);
int (*init_interrupts)(struct tb_nhi *nhi);
+ struct tb * (*select_cm)(struct tb_nhi *nhi);
};
/*
diff --git a/drivers/thunderbolt/pci.c b/drivers/thunderbolt/pci.c
index 275389903090..9379c598a221 100644
--- a/drivers/thunderbolt/pci.c
+++ b/drivers/thunderbolt/pci.c
@@ -325,6 +325,16 @@ static bool nhi_pci_is_present(struct tb_nhi *nhi)
return pci_device_is_present(to_pci_dev(nhi->dev));
}
+static struct tb *nhi_pci_select_cm(struct tb_nhi *nhi)
+{
+ /*
+ * Either the firmware-based CM is running (we did not get
+ * control from the firmware) or this is a pre-USB4 PC, so
+ * first try using the firmware CM and then fallback to software CM.
+ */
+ return icm_probe(nhi);
+}
+
static const struct tb_nhi_ops pci_nhi_default_ops = {
.add_links = nhi_pci_add_links,
.pre_nvm_auth = nhi_pci_start_dma_port,
@@ -334,6 +344,7 @@ static const struct tb_nhi_ops pci_nhi_default_ops = {
.shutdown = nhi_pci_release_irq,
.is_present = nhi_pci_is_present,
.init_interrupts = nhi_pci_init_msi,
+ .select_cm = nhi_pci_select_cm,
};
/* Ice Lake specific NHI operations */
@@ -552,6 +563,7 @@ static const struct tb_nhi_ops icl_nhi_ops = {
.release_ring_irq = nhi_pci_ring_release_msix,
.is_present = nhi_pci_is_present,
.init_interrupts = nhi_pci_init_msi,
+ .select_cm = nhi_pci_select_cm,
};
static int nhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
@@ -713,7 +725,7 @@ static struct pci_device_id nhi_ids[] = {
};
MODULE_DEVICE_TABLE(pci, nhi_ids);
-MODULE_DESCRIPTION("Thunderbolt/USB4 core driver");
+MODULE_DESCRIPTION("PCIe-attached Thunderbolt/USB4 host interface driver");
MODULE_LICENSE("GPL");
static struct pci_driver nhi_driver = {
@@ -724,27 +736,4 @@ static struct pci_driver nhi_driver = {
.shutdown = nhi_pci_shutdown,
.driver.pm = &tb_nhi_pm_ops,
};
-
-static int __init nhi_init(void)
-{
- int ret;
-
- ret = tb_domain_init();
- if (ret)
- return ret;
-
- ret = pci_register_driver(&nhi_driver);
- if (ret)
- tb_domain_exit();
-
- return ret;
-}
-
-static void __exit nhi_unload(void)
-{
- pci_unregister_driver(&nhi_driver);
- tb_domain_exit();
-}
-
-rootfs_initcall(nhi_init);
-module_exit(nhi_unload);
+module_pci_driver(nhi_driver);
diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c
index d22d8db8f890..7ff3da79da76 100644
--- a/drivers/thunderbolt/switch.c
+++ b/drivers/thunderbolt/switch.c
@@ -2396,6 +2396,7 @@ const struct device_type tb_switch_type = {
.uevent = tb_switch_uevent,
.pm = &tb_switch_pm_ops,
};
+EXPORT_SYMBOL_GPL(tb_switch_type);
static int tb_switch_get_generation(struct tb_switch *sw)
{
@@ -2571,6 +2572,7 @@ struct tb_switch *tb_switch_alloc(struct tb *tb, struct device *parent,
return ERR_PTR(ret);
}
+EXPORT_SYMBOL_GPL(tb_switch_alloc);
/**
* tb_switch_alloc_safe_mode() - allocate a switch that is in safe mode
@@ -2610,6 +2612,7 @@ tb_switch_alloc_safe_mode(struct tb *tb, struct device *parent, u64 route)
return sw;
}
+EXPORT_SYMBOL_GPL(tb_switch_alloc_safe_mode);
/**
* tb_switch_configure() - Uploads configuration to the switch
@@ -3438,6 +3441,7 @@ int tb_switch_add(struct tb_switch *sw)
return ret;
}
+EXPORT_SYMBOL_GPL(tb_switch_add);
/**
* tb_switch_remove() - Remove and release a switch
@@ -3483,6 +3487,7 @@ void tb_switch_remove(struct tb_switch *sw)
dev_info(&sw->dev, "device disconnected\n");
device_unregister(&sw->dev);
}
+EXPORT_SYMBOL_GPL(tb_switch_remove);
/**
* tb_sw_set_unplugged() - set is_unplugged on switch and downstream switches
@@ -3828,6 +3833,7 @@ struct tb_switch *tb_switch_find_by_link_depth(struct tb *tb, u8 link, u8 depth)
return NULL;
}
+EXPORT_SYMBOL_GPL(tb_switch_find_by_link_depth);
/**
* tb_switch_find_by_uuid() - Find switch by UUID
@@ -3854,6 +3860,7 @@ struct tb_switch *tb_switch_find_by_uuid(struct tb *tb, const uuid_t *uuid)
return NULL;
}
+EXPORT_SYMBOL_GPL(tb_switch_find_by_uuid);
/**
* tb_switch_find_by_route() - Find switch by route string
@@ -3883,6 +3890,7 @@ struct tb_switch *tb_switch_find_by_route(struct tb *tb, u64 route)
return NULL;
}
+EXPORT_SYMBOL_GPL(tb_switch_find_by_route);
/**
* tb_switch_find_port() - return the first port of @type on @sw or NULL
@@ -3903,6 +3911,7 @@ struct tb_port *tb_switch_find_port(struct tb_switch *sw,
return NULL;
}
+EXPORT_SYMBOL_GPL(tb_switch_find_port);
/*
* Can be used for read/write a specified PCIe bridge for any Thunderbolt 3
diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h
index e86845762e22..6dfdc47e05d2 100644
--- a/drivers/thunderbolt/tb.h
+++ b/drivers/thunderbolt/tb.h
@@ -768,8 +768,6 @@ extern const struct device_type tb_retimer_type;
extern const struct device_type tb_switch_type;
extern const struct device_type usb4_port_device_type;
-int tb_domain_init(void);
-void tb_domain_exit(void);
int tb_xdomain_init(void);
void tb_xdomain_exit(void);
diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c
index 2b911372001c..c92f181f181d 100644
--- a/drivers/thunderbolt/tunnel.c
+++ b/drivers/thunderbolt/tunnel.c
@@ -274,6 +274,7 @@ void tb_tunnel_event(struct tb *tb, enum tb_tunnel_event event,
kfree(envp[1]);
kfree(envp[0]);
}
+EXPORT_SYMBOL_GPL(tb_tunnel_event);
static inline void tb_tunnel_set_active(struct tb_tunnel *tunnel, bool active)
{
diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c
index b187a60663f5..af82b86a0348 100644
--- a/drivers/thunderbolt/xdomain.c
+++ b/drivers/thunderbolt/xdomain.c
@@ -86,6 +86,7 @@ bool tb_is_xdomain_enabled(void)
{
return tb_xdomain_enabled && tb_acpi_is_xdomain_allowed();
}
+EXPORT_SYMBOL_GPL(tb_is_xdomain_enabled);
static bool tb_xdomain_match(const struct tb_cfg_request *req,
const struct ctl_pkg *pkg)
@@ -2196,6 +2197,7 @@ struct tb_xdomain *tb_xdomain_alloc(struct tb *tb, struct device *parent,
return NULL;
}
+EXPORT_SYMBOL_GPL(tb_xdomain_alloc);
/**
* tb_xdomain_add() - Add XDomain to the bus
@@ -2211,6 +2213,7 @@ void tb_xdomain_add(struct tb_xdomain *xd)
/* Start exchanging properties with the other host */
start_handshake(xd);
}
+EXPORT_SYMBOL_GPL(tb_xdomain_add);
static int unregister_service(struct device *dev, void *data)
{
@@ -2270,6 +2273,7 @@ void tb_xdomain_unregister(struct tb_xdomain *xd)
dev_info(&xd->dev, "host disconnected\n");
device_unregister(&xd->dev);
}
+EXPORT_SYMBOL_GPL(tb_xdomain_remove);
/**
* tb_xdomain_lane_bonding_enable() - Enable lane bonding on XDomain
@@ -2593,6 +2597,7 @@ struct tb_xdomain *tb_xdomain_find_by_link_depth(struct tb *tb, u8 link,
xd = switch_find_xdomain(tb->root_switch, &lookup);
return tb_xdomain_get(xd);
}
+EXPORT_SYMBOL_GPL(tb_xdomain_find_by_link_depth);
/**
* tb_xdomain_find_by_route() - Find an XDomain by route string
--
2.45.2
^ permalink raw reply [flat|nested] 15+ messages in thread* [PATCH v2 4/7] thunderbolt: add platform NHI driver
2026-11-10 11:48 [PATCH 0/7] thunderbolt: Add support platform NHI driver Chunfeng Yun
` (2 preceding siblings ...)
2026-11-10 11:48 ` [PATCH v2 3/7] thunderbolt: Make PCIe NHI support opt-in Chunfeng Yun
@ 2026-11-10 11:48 ` Chunfeng Yun
2026-11-10 12:43 ` Konrad Dybcio
2026-11-10 14:37 ` Mika Westerberg
2026-11-10 11:48 ` [PATCH v2 5/7] thunderbolt: increase the lane adapter link-up timeout value Chunfeng Yun
` (2 subsequent siblings)
6 siblings, 2 replies; 15+ messages in thread
From: Chunfeng Yun @ 2026-11-10 11:48 UTC (permalink / raw)
To: Mika Westerberg
Cc: Andreas Noever, Konrad Dybcio, Sven Peter, Yehezkel Bernat,
Matthias Brugger, Greg Kroah-Hartman, AngeloGioacchino Del Regno,
linux-kernel, linux-usb, linux-arm-kernel, linux-mediatek,
Chunfeng Yun
Historically, the Thunderbolt/USB4 Native Host Interface (NHI)
controller has been exposed as a PCI device handled by
drivers/thunderbolt/pci.c. However, some SoCs (such as MediaTek
mt8901/mt8992) integrate the NHI as a memory-mapped platform device
described via Device Tree or ACPI.
To support platform-attached NHI controllers:
Introduce platform.c to register a platform driver matching
"usb4-nhi-platform" (OF) and "ACPI0015" (ACPI);
Implement platform tb_nhi_ops hooks for pm, interrupts related;
Support an optional dedicated wake interrupt: on integrated SoCs,
the MAC/PHY reside in a power domain whose clocks and power can be
gated during sleep, preventing the primary NHI interrupt from
acting as a wakeup source; so add an optional dedicated wakeup
interrupt which comes from the always-on power domain.
Clean up the unused nhi_enable_int_throttling() prototype.
Signed-off-by: Chunfeng Yun <chunfeng.yun@mediatek.com>
---
v2 changes:
* Added CONFIG_USB4_PLATFORM to build the platform driver as a separate
module (thunderbolt_platform.ko), instead of statically embedding it
into thunderbolt.o.
* Renamed plat.c to platform.c, renamed internal functions from nhi_plat_*
to nhi_platform_*, and updated the Device Tree compatible string from
"thunderbolt-platform" to "usb4-nhi-platform".
* Removed domain lifecycle changes (domain.c) and PCI driver cleanups
(pci.c) from this patch, handled by previous patches.
* Squashed the optional dedicated wake IRQ support (previously a separate
patch in v1) directly into this patch.
* Replaced the custom local ISR with the shared tb_nhi_isr(), adopted
the renamed tb_nhi_probe() / tb_nhi_shutdown() / tb_nhi_pm_ops
interfaces, and added -EPROBE_DEFER handling for the wake IRQ.
---
drivers/thunderbolt/Kconfig | 9 ++
drivers/thunderbolt/Makefile | 3 +
drivers/thunderbolt/nhi.h | 1 -
drivers/thunderbolt/pci.c | 1 +
drivers/thunderbolt/platform.c | 172 +++++++++++++++++++++++++++++++++
include/linux/thunderbolt.h | 4 +
6 files changed, 189 insertions(+), 1 deletion(-)
create mode 100644 drivers/thunderbolt/platform.c
diff --git a/drivers/thunderbolt/Kconfig b/drivers/thunderbolt/Kconfig
index 605af440457a..846ab51df7ab 100644
--- a/drivers/thunderbolt/Kconfig
+++ b/drivers/thunderbolt/Kconfig
@@ -27,6 +27,15 @@ config USB4_PCI
To compile this driver a module, choose M here. The module will be
called thunderbolt_pci.
+config USB4_PLATFORM
+ tristate "Support for platform USB4 and Thunderbolt controllers"
+ help
+ Say Y here to add support the USB4 and Thunderbolt host routers
+ for a platform device on such as ARM64 SoCs.
+
+ To compile this driver a module, choose M here. The module will be
+ called thunderbolt_platform.
+
config USB4_CONFIGFS
def_tristate USB4
depends on CONFIGFS_FS && !(USB4=y && CONFIGFS_FS=m)
diff --git a/drivers/thunderbolt/Makefile b/drivers/thunderbolt/Makefile
index a563bf2b0345..334fa4ebdfc8 100644
--- a/drivers/thunderbolt/Makefile
+++ b/drivers/thunderbolt/Makefile
@@ -8,6 +8,9 @@ thunderbolt-objs += usb4_port.o nvm.o retimer.o quirks.o clx.o
obj-${CONFIG_USB4_PCI} += thunderbolt_pci.o
thunderbolt_pci-y := pci.o icm.o
+obj-${CONFIG_USB4_PLATFORM} += thunderbolt_platform.o
+thunderbolt_platform-y := platform.o
+
thunderbolt-${CONFIG_ACPI} += acpi.o
thunderbolt-$(CONFIG_DEBUG_FS) += debugfs.o
thunderbolt-$(CONFIG_USB4_CONFIGFS) += configfs.o
diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h
index 65f9daf36068..ef5d24d81251 100644
--- a/drivers/thunderbolt/nhi.h
+++ b/drivers/thunderbolt/nhi.h
@@ -27,7 +27,6 @@ enum nhi_mailbox_cmd {
NHI_MAILBOX_ALLOW_ALL_DEVS = 0x23,
};
-void nhi_enable_int_throttling(struct tb_nhi *nhi);
void nhi_disable_interrupts(struct tb_nhi *nhi);
irqreturn_t tb_nhi_isr(int irq, void *data);
irqreturn_t tb_ring_msix(int irq, void *data);
diff --git a/drivers/thunderbolt/pci.c b/drivers/thunderbolt/pci.c
index 9379c598a221..698f47ec63da 100644
--- a/drivers/thunderbolt/pci.c
+++ b/drivers/thunderbolt/pci.c
@@ -736,4 +736,5 @@ static struct pci_driver nhi_driver = {
.shutdown = nhi_pci_shutdown,
.driver.pm = &tb_nhi_pm_ops,
};
+
module_pci_driver(nhi_driver);
diff --git a/drivers/thunderbolt/platform.c b/drivers/thunderbolt/platform.c
new file mode 100644
index 000000000000..3562b6c7ea7d
--- /dev/null
+++ b/drivers/thunderbolt/platform.c
@@ -0,0 +1,172 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Thunderbolt driver - NHI driver (Platform bus specific implementation)
+ *
+ * Author: Chunfeng Yun <chunfeng.yun@mediatek.com>
+ */
+
+#include <linux/acpi.h>
+#include <linux/interrupt.h>
+#include <linux/errno.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/pm_wakeirq.h>
+#include <linux/slab.h>
+
+#include "nhi.h"
+#include "tb.h"
+
+static int nhi_platform_init(struct tb_nhi *nhi)
+{
+ /* avoid device link fail for pcie tunnel */
+ pm_runtime_set_active(nhi->dev);
+ pm_runtime_get_noresume(nhi->dev);
+ pm_runtime_enable(nhi->dev);
+
+ device_set_wakeup_capable(nhi->dev, true);
+ device_enable_async_suspend(nhi->dev);
+
+ return 0;
+}
+
+static void nhi_platform_clear_wake_irq(void *dev)
+{
+ dev_pm_clear_wake_irq(dev);
+}
+
+static int nhi_platform_init_irq(struct tb_nhi *nhi)
+{
+ struct device *dev = nhi->dev;
+ int ret;
+
+ ret = devm_request_irq(dev, nhi->irq, tb_nhi_isr, IRQF_NO_SUSPEND,
+ dev_name(dev), nhi);
+ if (ret)
+ return dev_err_probe(dev, ret, "request irq failed\n");
+
+ if (nhi->wake_irq) {
+ ret = dev_pm_set_dedicated_wake_irq(dev, nhi->wake_irq);
+ if (ret) {
+ return dev_err_probe(dev, ret, "set wake irq %d failed\n", nhi->wake_irq);
+ }
+
+ dev_info(dev, "wake irq %d\n", nhi->wake_irq);
+
+ ret = devm_add_action_or_reset(dev, nhi_platform_clear_wake_irq, dev);
+ }
+
+ return ret;
+}
+
+static void nhi_platform_exit(struct tb_nhi *nhi)
+{
+ devm_free_irq(nhi->dev, nhi->irq, nhi);
+ flush_work(&nhi->interrupt_work);
+
+ pm_runtime_put_noidle(nhi->dev);
+ pm_runtime_disable(nhi->dev);
+ pm_runtime_set_suspended(nhi->dev);
+}
+
+static const struct tb_nhi_ops nhi_platform_default_ops = {
+ .init = nhi_platform_init,
+ .shutdown = nhi_platform_exit,
+ .init_interrupts = nhi_platform_init_irq,
+};
+
+static int nhi_platform_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct tb_nhi *nhi;
+
+ nhi = devm_kzalloc(dev, sizeof(*nhi), GFP_KERNEL);
+ if (!nhi)
+ return -ENOMEM;
+
+ nhi->dev = dev;
+ nhi->ops = &nhi_platform_default_ops;
+
+ nhi->iobase = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(nhi->iobase))
+ return PTR_ERR(nhi->iobase);
+
+ nhi->irq = platform_get_irq(pdev, 0);
+ if (nhi->irq < 0)
+ return nhi->irq;
+
+ /* ignore error */
+ nhi->wake_irq = platform_get_irq_optional(pdev, 1);
+ if (nhi->wake_irq == -EPROBE_DEFER)
+ return nhi->wake_irq;
+ else if (nhi->wake_irq < 0)
+ nhi->wake_irq = 0;
+
+ return tb_nhi_probe(nhi);
+}
+
+static void nhi_platform_do_remove(struct platform_device *pdev, bool reset)
+{
+ struct device *dev = &pdev->dev;
+ struct tb *tb = platform_get_drvdata(pdev);
+ struct tb_nhi *nhi = tb->nhi;
+
+ /*
+ * On system shutdown/reboot force a host router reset so the
+ * CM asserts DPR on connected tbt3 devices before the router tree
+ * is removed (see tb_stop()).
+ */
+ if (reset)
+ nhi->host_reset = true;
+
+ pm_runtime_get_sync(dev);
+ pm_runtime_dont_use_autosuspend(dev);
+
+ tb_domain_remove(tb);
+ wait_for_completion(&nhi->domain_released);
+ tb_nhi_shutdown(nhi);
+}
+
+static void nhi_platform_remove(struct platform_device *pdev)
+{
+ nhi_platform_do_remove(pdev, false);
+}
+
+static void nhi_platform_shutdown(struct platform_device *pdev)
+{
+ nhi_platform_do_remove(pdev, true);
+}
+
+
+#ifdef CONFIG_OF
+static const struct of_device_id nhi_platform_of_match[] = {
+ { .compatible = "usb4-nhi-platform" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, nhi_platform_of_match);
+#endif
+
+#ifdef CONFIG_ACPI
+static const struct acpi_device_id nhi_platform_acpi_match[] = {
+ { "ACPI0015", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, nhi_platform_acpi_match);
+#endif
+
+static struct platform_driver nhi_platform_driver = {
+ .probe = nhi_platform_probe,
+ .remove = nhi_platform_remove,
+ .shutdown = nhi_platform_shutdown,
+ .driver = {
+ .name = "thunderbolt",
+ .of_match_table = of_match_ptr(nhi_platform_of_match),
+ .acpi_match_table = ACPI_PTR(nhi_platform_acpi_match),
+ .pm = &tb_nhi_pm_ops,
+ },
+};
+
+module_platform_driver(nhi_platform_driver);
+MODULE_ALIAS("platform:thunderbolt");
+MODULE_DESCRIPTION("Thunderbolt/USB4 platform host interface driver");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/thunderbolt.h b/include/linux/thunderbolt.h
index 57502da29080..72497acac254 100644
--- a/include/linux/thunderbolt.h
+++ b/include/linux/thunderbolt.h
@@ -511,6 +511,8 @@ void tb_service_properties_changed(struct tb_service *svc);
* @going_away: The host controller device is about to disappear so when
* this flag is set, avoid touching the hardware anymore.
* @iommu_dma_protection: An IOMMU will isolate external-facing ports.
+ * @irq: interrupt number of NHI.
+ * @wake_irq: optional dedicated wakeup interrupt.
* @interrupt_work: Work scheduled to handle ring interrupt when no
* MSI-X is used.
* @hop_count: Number of rings (end point hops) supported by NHI.
@@ -532,6 +534,8 @@ struct tb_nhi {
u32 *interrupt_mask;
bool going_away;
bool iommu_dma_protection;
+ int irq;
+ int wake_irq;
struct work_struct interrupt_work;
u32 hop_count;
unsigned long quirks;
--
2.45.2
^ permalink raw reply [flat|nested] 15+ messages in thread* Re: [PATCH v2 4/7] thunderbolt: add platform NHI driver
2026-11-10 11:48 ` [PATCH v2 4/7] thunderbolt: add platform NHI driver Chunfeng Yun
@ 2026-11-10 12:43 ` Konrad Dybcio
2026-11-10 14:37 ` Mika Westerberg
1 sibling, 0 replies; 15+ messages in thread
From: Konrad Dybcio @ 2026-11-10 12:43 UTC (permalink / raw)
To: Chunfeng Yun, Mika Westerberg
Cc: Andreas Noever, Sven Peter, Yehezkel Bernat, Matthias Brugger,
Greg Kroah-Hartman, AngeloGioacchino Del Regno, linux-kernel,
linux-usb, linux-arm-kernel, linux-mediatek
On 11/10/26 12:48 PM, Chunfeng Yun wrote:
> Historically, the Thunderbolt/USB4 Native Host Interface (NHI)
> controller has been exposed as a PCI device handled by
> drivers/thunderbolt/pci.c. However, some SoCs (such as MediaTek
> mt8901/mt8992) integrate the NHI as a memory-mapped platform device
> described via Device Tree or ACPI.
>
> To support platform-attached NHI controllers:
>
> Introduce platform.c to register a platform driver matching
> "usb4-nhi-platform" (OF) and "ACPI0015" (ACPI);
> Implement platform tb_nhi_ops hooks for pm, interrupts related;
>
> Support an optional dedicated wake interrupt: on integrated SoCs,
> the MAC/PHY reside in a power domain whose clocks and power can be
> gated during sleep, preventing the primary NHI interrupt from
> acting as a wakeup source; so add an optional dedicated wakeup
> interrupt which comes from the always-on power domain.
>
> Clean up the unused nhi_enable_int_throttling() prototype.
>
> Signed-off-by: Chunfeng Yun <chunfeng.yun@mediatek.com>
> ---
> v2 changes:
> * Added CONFIG_USB4_PLATFORM to build the platform driver as a separate
> module (thunderbolt_platform.ko), instead of statically embedding it
> into thunderbolt.o.
> * Renamed plat.c to platform.c, renamed internal functions from nhi_plat_*
> to nhi_platform_*, and updated the Device Tree compatible string from
> "thunderbolt-platform" to "usb4-nhi-platform".
There are still no dt-bindings for this meaning it can't be merged.
You also haven't answered Sven and mine's concerns about this being
actually used.
Konrad
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v2 4/7] thunderbolt: add platform NHI driver
2026-11-10 11:48 ` [PATCH v2 4/7] thunderbolt: add platform NHI driver Chunfeng Yun
2026-11-10 12:43 ` Konrad Dybcio
@ 2026-11-10 14:37 ` Mika Westerberg
1 sibling, 0 replies; 15+ messages in thread
From: Mika Westerberg @ 2026-11-10 14:37 UTC (permalink / raw)
To: Chunfeng Yun
Cc: Mika Westerberg, Andreas Noever, Konrad Dybcio, Sven Peter,
Yehezkel Bernat, Matthias Brugger, Greg Kroah-Hartman,
AngeloGioacchino Del Regno, linux-kernel, linux-usb,
linux-arm-kernel, linux-mediatek
On Tue, Nov 10, 2026 at 07:48:52PM +0800, Chunfeng Yun wrote:
> Historically, the Thunderbolt/USB4 Native Host Interface (NHI)
> controller has been exposed as a PCI device handled by
> drivers/thunderbolt/pci.c. However, some SoCs (such as MediaTek
> mt8901/mt8992) integrate the NHI as a memory-mapped platform device
> described via Device Tree or ACPI.
>
> To support platform-attached NHI controllers:
>
> Introduce platform.c to register a platform driver matching
> "usb4-nhi-platform" (OF) and "ACPI0015" (ACPI);
> Implement platform tb_nhi_ops hooks for pm, interrupts related;
>
> Support an optional dedicated wake interrupt: on integrated SoCs,
> the MAC/PHY reside in a power domain whose clocks and power can be
> gated during sleep, preventing the primary NHI interrupt from
> acting as a wakeup source; so add an optional dedicated wakeup
> interrupt which comes from the always-on power domain.
>
> Clean up the unused nhi_enable_int_throttling() prototype.
>
> Signed-off-by: Chunfeng Yun <chunfeng.yun@mediatek.com>
> ---
> v2 changes:
> * Added CONFIG_USB4_PLATFORM to build the platform driver as a separate
> module (thunderbolt_platform.ko), instead of statically embedding it
> into thunderbolt.o.
> * Renamed plat.c to platform.c, renamed internal functions from nhi_plat_*
> to nhi_platform_*, and updated the Device Tree compatible string from
> "thunderbolt-platform" to "usb4-nhi-platform".
> * Removed domain lifecycle changes (domain.c) and PCI driver cleanups
> (pci.c) from this patch, handled by previous patches.
> * Squashed the optional dedicated wake IRQ support (previously a separate
> patch in v1) directly into this patch.
> * Replaced the custom local ISR with the shared tb_nhi_isr(), adopted
> the renamed tb_nhi_probe() / tb_nhi_shutdown() / tb_nhi_pm_ops
> interfaces, and added -EPROBE_DEFER handling for the wake IRQ.
> ---
> drivers/thunderbolt/Kconfig | 9 ++
> drivers/thunderbolt/Makefile | 3 +
> drivers/thunderbolt/nhi.h | 1 -
> drivers/thunderbolt/pci.c | 1 +
> drivers/thunderbolt/platform.c | 172 +++++++++++++++++++++++++++++++++
> include/linux/thunderbolt.h | 4 +
> 6 files changed, 189 insertions(+), 1 deletion(-)
> create mode 100644 drivers/thunderbolt/platform.c
>
> diff --git a/drivers/thunderbolt/Kconfig b/drivers/thunderbolt/Kconfig
> index 605af440457a..846ab51df7ab 100644
> --- a/drivers/thunderbolt/Kconfig
> +++ b/drivers/thunderbolt/Kconfig
> @@ -27,6 +27,15 @@ config USB4_PCI
> To compile this driver a module, choose M here. The module will be
> called thunderbolt_pci.
>
> +config USB4_PLATFORM
> + tristate "Support for platform USB4 and Thunderbolt controllers"
> + help
> + Say Y here to add support the USB4 and Thunderbolt host routers
> + for a platform device on such as ARM64 SoCs.
> +
> + To compile this driver a module, choose M here. The module will be
> + called thunderbolt_platform.
> +
> config USB4_CONFIGFS
> def_tristate USB4
> depends on CONFIGFS_FS && !(USB4=y && CONFIGFS_FS=m)
> diff --git a/drivers/thunderbolt/Makefile b/drivers/thunderbolt/Makefile
> index a563bf2b0345..334fa4ebdfc8 100644
> --- a/drivers/thunderbolt/Makefile
> +++ b/drivers/thunderbolt/Makefile
> @@ -8,6 +8,9 @@ thunderbolt-objs += usb4_port.o nvm.o retimer.o quirks.o clx.o
> obj-${CONFIG_USB4_PCI} += thunderbolt_pci.o
> thunderbolt_pci-y := pci.o icm.o
>
> +obj-${CONFIG_USB4_PLATFORM} += thunderbolt_platform.o
> +thunderbolt_platform-y := platform.o
> +
> thunderbolt-${CONFIG_ACPI} += acpi.o
> thunderbolt-$(CONFIG_DEBUG_FS) += debugfs.o
> thunderbolt-$(CONFIG_USB4_CONFIGFS) += configfs.o
> diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h
> index 65f9daf36068..ef5d24d81251 100644
> --- a/drivers/thunderbolt/nhi.h
> +++ b/drivers/thunderbolt/nhi.h
> @@ -27,7 +27,6 @@ enum nhi_mailbox_cmd {
> NHI_MAILBOX_ALLOW_ALL_DEVS = 0x23,
> };
>
> -void nhi_enable_int_throttling(struct tb_nhi *nhi);
> void nhi_disable_interrupts(struct tb_nhi *nhi);
> irqreturn_t tb_nhi_isr(int irq, void *data);
> irqreturn_t tb_ring_msix(int irq, void *data);
> diff --git a/drivers/thunderbolt/pci.c b/drivers/thunderbolt/pci.c
> index 9379c598a221..698f47ec63da 100644
> --- a/drivers/thunderbolt/pci.c
> +++ b/drivers/thunderbolt/pci.c
> @@ -736,4 +736,5 @@ static struct pci_driver nhi_driver = {
> .shutdown = nhi_pci_shutdown,
> .driver.pm = &tb_nhi_pm_ops,
> };
> +
I guess this is unintented change?
> module_pci_driver(nhi_driver);
> diff --git a/drivers/thunderbolt/platform.c b/drivers/thunderbolt/platform.c
> new file mode 100644
> index 000000000000..3562b6c7ea7d
> --- /dev/null
> +++ b/drivers/thunderbolt/platform.c
> @@ -0,0 +1,172 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Thunderbolt driver - NHI driver (Platform bus specific implementation)
> + *
> + * Author: Chunfeng Yun <chunfeng.yun@mediatek.com>
> + */
> +
> +#include <linux/acpi.h>
> +#include <linux/interrupt.h>
> +#include <linux/errno.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/pm_wakeirq.h>
> +#include <linux/slab.h>
> +
> +#include "nhi.h"
> +#include "tb.h"
> +
> +static int nhi_platform_init(struct tb_nhi *nhi)
> +{
> + /* avoid device link fail for pcie tunnel */
Do you have the ACPI _DSD there in place? Then I don't think you need
these. And "pcie" -> "PCIe" everywhere (except in code).
> + pm_runtime_set_active(nhi->dev);
> + pm_runtime_get_noresume(nhi->dev);
> + pm_runtime_enable(nhi->dev);
> +
> + device_set_wakeup_capable(nhi->dev, true);
> + device_enable_async_suspend(nhi->dev);
I also suggest not to enable all the fancy power management until you have
the basic stuff like tunneling fully functional.
> +
> + return 0;
> +}
> +
> +static void nhi_platform_clear_wake_irq(void *dev)
> +{
> + dev_pm_clear_wake_irq(dev);
> +}
> +
> +static int nhi_platform_init_irq(struct tb_nhi *nhi)
> +{
> + struct device *dev = nhi->dev;
> + int ret;
> +
> + ret = devm_request_irq(dev, nhi->irq, tb_nhi_isr, IRQF_NO_SUSPEND,
> + dev_name(dev), nhi);
> + if (ret)
> + return dev_err_probe(dev, ret, "request irq failed\n");
failed to request interrupt
Please be consistent with the rest of the driver.
> +
> + if (nhi->wake_irq) {
> + ret = dev_pm_set_dedicated_wake_irq(dev, nhi->wake_irq);
> + if (ret) {
> + return dev_err_probe(dev, ret, "set wake irq %d failed\n", nhi->wake_irq);
Split this into 2 lines.
> + }
> +
> + dev_info(dev, "wake irq %d\n", nhi->wake_irq);
Remove this. The driver should be silent when it works. You can add debug
but I don't think this is useful.
> +
> + ret = devm_add_action_or_reset(dev, nhi_platform_clear_wake_irq, dev);
Can you pass dev_pm_clear_wake_irq directly here?
> + }
> +
> + return ret;
> +}
> +
> +static void nhi_platform_exit(struct tb_nhi *nhi)
> +{
> + devm_free_irq(nhi->dev, nhi->irq, nhi);
Isn't it released automatically?
> + flush_work(&nhi->interrupt_work);
> +
> + pm_runtime_put_noidle(nhi->dev);
> + pm_runtime_disable(nhi->dev);
> + pm_runtime_set_suspended(nhi->dev);
> +}
> +
> +static const struct tb_nhi_ops nhi_platform_default_ops = {
> + .init = nhi_platform_init,
> + .shutdown = nhi_platform_exit,
> + .init_interrupts = nhi_platform_init_irq,
> +};
> +
> +static int nhi_platform_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct tb_nhi *nhi;
> +
> + nhi = devm_kzalloc(dev, sizeof(*nhi), GFP_KERNEL);
devm_kzalloc_obj()
> + if (!nhi)
> + return -ENOMEM;
> +
> + nhi->dev = dev;
> + nhi->ops = &nhi_platform_default_ops;
> +
> + nhi->iobase = devm_platform_ioremap_resource(pdev, 0);
> + if (IS_ERR(nhi->iobase))
> + return PTR_ERR(nhi->iobase);
> +
> + nhi->irq = platform_get_irq(pdev, 0);
> + if (nhi->irq < 0)
> + return nhi->irq;
> +
> + /* ignore error */
This should explain why it's okay to ignore the error.
> + nhi->wake_irq = platform_get_irq_optional(pdev, 1);
> + if (nhi->wake_irq == -EPROBE_DEFER)
> + return nhi->wake_irq;
> + else if (nhi->wake_irq < 0)
> + nhi->wake_irq = 0;
> +
> + return tb_nhi_probe(nhi);
> +}
> +
> +static void nhi_platform_do_remove(struct platform_device *pdev, bool reset)
> +{
> + struct device *dev = &pdev->dev;
> + struct tb *tb = platform_get_drvdata(pdev);
> + struct tb_nhi *nhi = tb->nhi;
Reverse-christmas tree if possible:
struct tb *tb = platform_get_drvdata(pdev);
struct device *dev = &pdev->dev;
struct tb_nhi *nhi = tb->nhi;
> +
> + /*
> + * On system shutdown/reboot force a host router reset so the
> + * CM asserts DPR on connected tbt3 devices before the router tree
Thunderbolt 3
> + * is removed (see tb_stop()).
> + */
> + if (reset)
> + nhi->host_reset = true;
> +
> + pm_runtime_get_sync(dev);
> + pm_runtime_dont_use_autosuspend(dev);
> +
> + tb_domain_remove(tb);
> + wait_for_completion(&nhi->domain_released);
> + tb_nhi_shutdown(nhi);
> +}
> +
> +static void nhi_platform_remove(struct platform_device *pdev)
> +{
> + nhi_platform_do_remove(pdev, false);
> +}
> +
> +static void nhi_platform_shutdown(struct platform_device *pdev)
> +{
> + nhi_platform_do_remove(pdev, true);
> +}
> +
> +
> +#ifdef CONFIG_OF
> +static const struct of_device_id nhi_platform_of_match[] = {
> + { .compatible = "usb4-nhi-platform" },
> + { }
> +};
I think this was agreed to be dropped for now.
> +MODULE_DEVICE_TABLE(of, nhi_platform_of_match);
> +#endif
> +
> +#ifdef CONFIG_ACPI
> +static const struct acpi_device_id nhi_platform_acpi_match[] = {
> + { "ACPI0015", 0 },
> + { }
> +};
> +MODULE_DEVICE_TABLE(acpi, nhi_platform_acpi_match);
> +#endif
> +
> +static struct platform_driver nhi_platform_driver = {
> + .probe = nhi_platform_probe,
> + .remove = nhi_platform_remove,
> + .shutdown = nhi_platform_shutdown,
> + .driver = {
> + .name = "thunderbolt",
> + .of_match_table = of_match_ptr(nhi_platform_of_match),
> + .acpi_match_table = ACPI_PTR(nhi_platform_acpi_match),
> + .pm = &tb_nhi_pm_ops,
> + },
> +};
> +
> +module_platform_driver(nhi_platform_driver);
> +MODULE_ALIAS("platform:thunderbolt");
> +MODULE_DESCRIPTION("Thunderbolt/USB4 platform host interface driver");
> +MODULE_LICENSE("GPL v2");
> diff --git a/include/linux/thunderbolt.h b/include/linux/thunderbolt.h
> index 57502da29080..72497acac254 100644
> --- a/include/linux/thunderbolt.h
> +++ b/include/linux/thunderbolt.h
> @@ -511,6 +511,8 @@ void tb_service_properties_changed(struct tb_service *svc);
> * @going_away: The host controller device is about to disappear so when
> * this flag is set, avoid touching the hardware anymore.
> * @iommu_dma_protection: An IOMMU will isolate external-facing ports.
> + * @irq: interrupt number of NHI.
> + * @wake_irq: optional dedicated wakeup interrupt.
> * @interrupt_work: Work scheduled to handle ring interrupt when no
> * MSI-X is used.
> * @hop_count: Number of rings (end point hops) supported by NHI.
> @@ -532,6 +534,8 @@ struct tb_nhi {
> u32 *interrupt_mask;
> bool going_away;
> bool iommu_dma_protection;
> + int irq;
> + int wake_irq;
> struct work_struct interrupt_work;
> u32 hop_count;
> unsigned long quirks;
> --
> 2.45.2
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v2 5/7] thunderbolt: increase the lane adapter link-up timeout value
2026-11-10 11:48 [PATCH 0/7] thunderbolt: Add support platform NHI driver Chunfeng Yun
` (3 preceding siblings ...)
2026-11-10 11:48 ` [PATCH v2 4/7] thunderbolt: add platform NHI driver Chunfeng Yun
@ 2026-11-10 11:48 ` Chunfeng Yun
2026-11-10 14:39 ` Mika Westerberg
2026-11-10 11:48 ` [PATCH v2 6/7] thunderbolt: decouple the waiting time of TB_PORT_UNPLUGGED and TB_PORT_UP Chunfeng Yun
2026-11-10 11:48 ` [PATCH v2 7/7] thunderbolt: prevent pcie tunnel activation/teardown warnings Chunfeng Yun
6 siblings, 1 reply; 15+ messages in thread
From: Chunfeng Yun @ 2026-11-10 11:48 UTC (permalink / raw)
To: Mika Westerberg
Cc: Andreas Noever, Konrad Dybcio, Sven Peter, Yehezkel Bernat,
Matthias Brugger, Greg Kroah-Hartman, AngeloGioacchino Del Regno,
linux-kernel, linux-usb, linux-arm-kernel, linux-mediatek,
Chunfeng Yun
when plug into a USB3 device into USB4 hub's downstream facing port
to trigger usb4 driver wakeup, encounter the lane link up timeout
issue, then cause usb3 hub disconnect:
usb4_hi NVDA8100:00: 0:1: failed to reach state TB_PORT_UP. Ignoring port...
usb4_hi NVDA8100:00: 0:1: lost during suspend, disconnecting
usb4_hi NVDA8100:00: 0:4 <-> 1:20 (USB3): deactivating
usb4_hi NVDA8100:00: deactivating USB3 Down path from 0:4 to 1:20
usb4_hi NVDA8100:00: deactivating USB3 Up path from 1:20 to 0:4
...
thunderbolt 0-1: device disconnected
usb 2-1: USB disconnect, device number 10
Completes link training at ~1.48s (with two retimer) and transitions
to TB_PORT_UP after change the timeout to 2s;
In the USB4 spec, Phase 5 (Link Equalization) alone is allowed up to
1000 ms. When add tPhase2~4 (200 ms), and retimer transaction delays
(tCmdResponse = 50 ms), the physical link is allowed by spec to take
1200 ms to 1500 ms+ to complete initialization.
Signed-off-by: Chunfeng Yun <chunfeng.yun@mediatek.com>
---
v2: change timeout from 2.5s to 2s
---
drivers/thunderbolt/switch.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c
index 7ff3da79da76..7daee83c7ed8 100644
--- a/drivers/thunderbolt/switch.c
+++ b/drivers/thunderbolt/switch.c
@@ -486,7 +486,7 @@ int tb_port_state(struct tb_port *port)
* @port: Port to wait
* @wait_if_unplugged: Wait also when port is unplugged
*
- * Wait up to 1 second for a port to reach state TB_PORT_UP. If
+ * Wait up to 2 seconds for a port to reach state TB_PORT_UP. If
* wait_if_unplugged is set then we also wait if the port is in state
* TB_PORT_UNPLUGGED (it takes a while for the device to be registered after
* switch resume). Otherwise we only wait if a device is registered but the link
@@ -500,7 +500,7 @@ int tb_port_state(struct tb_port *port)
*/
int tb_wait_for_port(struct tb_port *port, bool wait_if_unplugged)
{
- int retries = 10;
+ int retries = 20;
int state;
if (!port->cap_phy) {
tb_port_WARN(port, "does not have PHY\n");
--
2.45.2
^ permalink raw reply [flat|nested] 15+ messages in thread* Re: [PATCH v2 5/7] thunderbolt: increase the lane adapter link-up timeout value
2026-11-10 11:48 ` [PATCH v2 5/7] thunderbolt: increase the lane adapter link-up timeout value Chunfeng Yun
@ 2026-11-10 14:39 ` Mika Westerberg
0 siblings, 0 replies; 15+ messages in thread
From: Mika Westerberg @ 2026-11-10 14:39 UTC (permalink / raw)
To: Chunfeng Yun
Cc: Mika Westerberg, Andreas Noever, Konrad Dybcio, Sven Peter,
Yehezkel Bernat, Matthias Brugger, Greg Kroah-Hartman,
AngeloGioacchino Del Regno, linux-kernel, linux-usb,
linux-arm-kernel, linux-mediatek
On Tue, Nov 10, 2026 at 07:48:53PM +0800, Chunfeng Yun wrote:
> when plug into a USB3 device into USB4 hub's downstream facing port
> to trigger usb4 driver wakeup, encounter the lane link up timeout
USB4
> issue, then cause usb3 hub disconnect:
USB3
Everywhere.
>
> usb4_hi NVDA8100:00: 0:1: failed to reach state TB_PORT_UP. Ignoring port...
What's usb4_hi?
> usb4_hi NVDA8100:00: 0:1: lost during suspend, disconnecting
> usb4_hi NVDA8100:00: 0:4 <-> 1:20 (USB3): deactivating
> usb4_hi NVDA8100:00: deactivating USB3 Down path from 0:4 to 1:20
> usb4_hi NVDA8100:00: deactivating USB3 Up path from 1:20 to 0:4
> ...
> thunderbolt 0-1: device disconnected
> usb 2-1: USB disconnect, device number 10
>
> Completes link training at ~1.48s (with two retimer) and transitions
> to TB_PORT_UP after change the timeout to 2s;
>
> In the USB4 spec, Phase 5 (Link Equalization) alone is allowed up to
> 1000 ms. When add tPhase2~4 (200 ms), and retimer transaction delays
> (tCmdResponse = 50 ms), the physical link is allowed by spec to take
> 1200 ms to 1500 ms+ to complete initialization.
Okay that makes sense.
> Signed-off-by: Chunfeng Yun <chunfeng.yun@mediatek.com>
> ---
> v2: change timeout from 2.5s to 2s
> ---
> drivers/thunderbolt/switch.c | 4 ++--
> 1 file changed, 2 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c
> index 7ff3da79da76..7daee83c7ed8 100644
> --- a/drivers/thunderbolt/switch.c
> +++ b/drivers/thunderbolt/switch.c
> @@ -486,7 +486,7 @@ int tb_port_state(struct tb_port *port)
> * @port: Port to wait
> * @wait_if_unplugged: Wait also when port is unplugged
> *
> - * Wait up to 1 second for a port to reach state TB_PORT_UP. If
> + * Wait up to 2 seconds for a port to reach state TB_PORT_UP. If
> * wait_if_unplugged is set then we also wait if the port is in state
> * TB_PORT_UNPLUGGED (it takes a while for the device to be registered after
> * switch resume). Otherwise we only wait if a device is registered but the link
> @@ -500,7 +500,7 @@ int tb_port_state(struct tb_port *port)
> */
> int tb_wait_for_port(struct tb_port *port, bool wait_if_unplugged)
> {
> - int retries = 10;
> + int retries = 20;
> int state;
> if (!port->cap_phy) {
> tb_port_WARN(port, "does not have PHY\n");
> --
> 2.45.2
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v2 6/7] thunderbolt: decouple the waiting time of TB_PORT_UNPLUGGED and TB_PORT_UP
2026-11-10 11:48 [PATCH 0/7] thunderbolt: Add support platform NHI driver Chunfeng Yun
` (4 preceding siblings ...)
2026-11-10 11:48 ` [PATCH v2 5/7] thunderbolt: increase the lane adapter link-up timeout value Chunfeng Yun
@ 2026-11-10 11:48 ` Chunfeng Yun
2026-11-10 14:40 ` Mika Westerberg
2026-11-10 11:48 ` [PATCH v2 7/7] thunderbolt: prevent pcie tunnel activation/teardown warnings Chunfeng Yun
6 siblings, 1 reply; 15+ messages in thread
From: Chunfeng Yun @ 2026-11-10 11:48 UTC (permalink / raw)
To: Mika Westerberg
Cc: Andreas Noever, Konrad Dybcio, Sven Peter, Yehezkel Bernat,
Matthias Brugger, Greg Kroah-Hartman, AngeloGioacchino Del Regno,
linux-kernel, linux-usb, linux-arm-kernel, linux-mediatek,
Chunfeng Yun
When usb4 is woke up by device disconnect, no need wait for 2s,
usually the phase2~4 takes at most about 200ms, and enters to
TB_PORT_CONNECTING, if it is still in TB_PORT_UNPLUGGED after 400ms,
treat it as disconnected.
Signed-off-by: Chunfeng Yun <chunfeng.yun@mediatek.com>
---
new changes
---
drivers/thunderbolt/switch.c | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c
index 7daee83c7ed8..e12546bfb869 100644
--- a/drivers/thunderbolt/switch.c
+++ b/drivers/thunderbolt/switch.c
@@ -489,8 +489,9 @@ int tb_port_state(struct tb_port *port)
* Wait up to 2 seconds for a port to reach state TB_PORT_UP. If
* wait_if_unplugged is set then we also wait if the port is in state
* TB_PORT_UNPLUGGED (it takes a while for the device to be registered after
- * switch resume). Otherwise we only wait if a device is registered but the link
- * has not yet been established.
+ * switch resume, Wait at most 400 ms for link to start connecting,
+ * if timeout, means the device is disconnected). Otherwise we only wait
+ * if a device is registered but the link has not yet been established.
*
* Return:
* * %0 - If the port is not connected or failed to reach
@@ -501,6 +502,7 @@ int tb_port_state(struct tb_port *port)
int tb_wait_for_port(struct tb_port *port, bool wait_if_unplugged)
{
int retries = 20;
+ int unplugged_retries = 4;
int state;
if (!port->cap_phy) {
tb_port_WARN(port, "does not have PHY\n");
@@ -519,7 +521,7 @@ int tb_wait_for_port(struct tb_port *port, bool wait_if_unplugged)
return 0;
case TB_PORT_UNPLUGGED:
- if (wait_if_unplugged) {
+ if (wait_if_unplugged && unplugged_retries--) {
/* used during resume */
tb_port_dbg(port,
"is unplugged (state: 7), retrying...\n");
--
2.45.2
^ permalink raw reply [flat|nested] 15+ messages in thread* Re: [PATCH v2 6/7] thunderbolt: decouple the waiting time of TB_PORT_UNPLUGGED and TB_PORT_UP
2026-11-10 11:48 ` [PATCH v2 6/7] thunderbolt: decouple the waiting time of TB_PORT_UNPLUGGED and TB_PORT_UP Chunfeng Yun
@ 2026-11-10 14:40 ` Mika Westerberg
0 siblings, 0 replies; 15+ messages in thread
From: Mika Westerberg @ 2026-11-10 14:40 UTC (permalink / raw)
To: Chunfeng Yun
Cc: Mika Westerberg, Andreas Noever, Konrad Dybcio, Sven Peter,
Yehezkel Bernat, Matthias Brugger, Greg Kroah-Hartman,
AngeloGioacchino Del Regno, linux-kernel, linux-usb,
linux-arm-kernel, linux-mediatek
On Tue, Nov 10, 2026 at 07:48:54PM +0800, Chunfeng Yun wrote:
> When usb4 is woke up by device disconnect, no need wait for 2s,
> usually the phase2~4 takes at most about 200ms, and enters to
> TB_PORT_CONNECTING, if it is still in TB_PORT_UNPLUGGED after 400ms,
> treat it as disconnected.
>
> Signed-off-by: Chunfeng Yun <chunfeng.yun@mediatek.com>
> ---
> new changes
> ---
> drivers/thunderbolt/switch.c | 8 +++++---
> 1 file changed, 5 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c
> index 7daee83c7ed8..e12546bfb869 100644
> --- a/drivers/thunderbolt/switch.c
> +++ b/drivers/thunderbolt/switch.c
> @@ -489,8 +489,9 @@ int tb_port_state(struct tb_port *port)
> * Wait up to 2 seconds for a port to reach state TB_PORT_UP. If
> * wait_if_unplugged is set then we also wait if the port is in state
> * TB_PORT_UNPLUGGED (it takes a while for the device to be registered after
> - * switch resume). Otherwise we only wait if a device is registered but the link
> - * has not yet been established.
> + * switch resume, Wait at most 400 ms for link to start connecting,
router
> + * if timeout, means the device is disconnected). Otherwise we only wait
> + * if a device is registered but the link has not yet been established.
> *
> * Return:
> * * %0 - If the port is not connected or failed to reach
> @@ -501,6 +502,7 @@ int tb_port_state(struct tb_port *port)
> int tb_wait_for_port(struct tb_port *port, bool wait_if_unplugged)
> {
> int retries = 20;
> + int unplugged_retries = 4;
Reverse-christmas tree
> int state;
> if (!port->cap_phy) {
> tb_port_WARN(port, "does not have PHY\n");
> @@ -519,7 +521,7 @@ int tb_wait_for_port(struct tb_port *port, bool wait_if_unplugged)
> return 0;
>
> case TB_PORT_UNPLUGGED:
> - if (wait_if_unplugged) {
> + if (wait_if_unplugged && unplugged_retries--) {
> /* used during resume */
> tb_port_dbg(port,
> "is unplugged (state: 7), retrying...\n");
> --
> 2.45.2
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v2 7/7] thunderbolt: prevent pcie tunnel activation/teardown warnings
2026-11-10 11:48 [PATCH 0/7] thunderbolt: Add support platform NHI driver Chunfeng Yun
` (5 preceding siblings ...)
2026-11-10 11:48 ` [PATCH v2 6/7] thunderbolt: decouple the waiting time of TB_PORT_UNPLUGGED and TB_PORT_UP Chunfeng Yun
@ 2026-11-10 11:48 ` Chunfeng Yun
2026-11-10 14:50 ` Mika Westerberg
6 siblings, 1 reply; 15+ messages in thread
From: Chunfeng Yun @ 2026-11-10 11:48 UTC (permalink / raw)
To: Mika Westerberg
Cc: Andreas Noever, Konrad Dybcio, Sven Peter, Yehezkel Bernat,
Matthias Brugger, Greg Kroah-Hartman, AngeloGioacchino Del Regno,
linux-kernel, linux-usb, linux-arm-kernel, linux-mediatek,
Chunfeng Yun
When usb4 enter sleep mode, echo 1 to authorized of sysfile,
will wakeup usb4 driver, but fail to create pcie tunnel, cause
the warning due to a race condition between router disconnect
during resume and user authorization
[ 278.831186] usb4_hi NVDA8100:00: 0:1: is unplugged (state: 7), retrying...
[ 278.943031] usb4_hi NVDA8100:00: 0:1: failed to reach state TB_PORT_UP. Ignoring port...
[ 278.951125] usb4_hi NVDA8100:00: 0:1: lost during suspend, disconnecting
...
[ 279.013763] usb4_hi NVDA8100:00: 0:7 <-> 1:9 (PCI): activating
[ 279.019587] usb4_hi NVDA8100:00: activating PCIe Down path from 0:7 to 1:9
[ 279.026468] usb4_hi NVDA8100:00: 1:1: Writing hop 1
[ 279.031335] usb4_hi NVDA8100:00: 1:1: In HopID: 8 => Out port: 9 Out HopID: 8
[ 279.038546] usb4_hi NVDA8100:00: 1:1: Weight: 1 Priority: 3 Credits: 32 Drop: 0 PM: 0
[ 279.046536] usb4_hi NVDA8100:00: 1:1: Counter enabled: 0 Counter index: 2047
[ 279.053834] usb4_hi NVDA8100:00: 1:1: Flow Control (In/Eg): 1/0 Shared Buffer (In/Eg): 0/0
[ 279.062259] usb4_hi NVDA8100:00: 1:1: Unknown1: 0x0 Unknown2: 0x0 Unknown3: 0x0
[ 279.069730] ------------[ cut here ]------------
[ 279.074335] usb4_hi NVDA8100:00: PCIe Down path activation failed
[ 279.080426] WARNING: CPU: 5 PID: 2433 at drivers/thunderbolt/path.c:587 tb_path_activate+0x1a4/0x5b0
...
[ 279.220285] Call trace:
[ 279.222722] tb_path_activate+0x1a4/0x5b0 (P)
[ 279.227068] tb_tunnel_activate+0x140/0x498
[ 279.231241] tb_tunnel_pci+0x180/0x490
[ 279.234980] tb_domain_approve_switch+0x74/0xb0
[ 279.239500] authorized_store+0x214/0x270
[ 279.243498] dev_attr_store+0x20/0x70
...
[ 279.288888] ---[ end trace 0000000000000000 ]---
[ 279.293497] usb4_hi NVDA8100:00: 0:7 <-> 1:9 (PCI): activation failed
When usb4 enter sleep mode, echo 0 to authorized of sysfile,
will wakeup usb4 driver, but fail to disconnect pcie due to
the PCIe tunnel had already been torn down and freed if the port
is fail to resume,
[ 545.887822] usb4_hi NVDA8100:00: 0:1: is unplugged (state: 7), retrying...
[ 545.998689] usb4_hi NVDA8100:00: 0:1: failed to reach state TB_PORT_UP. Ignoring port...
[ 546.006778] usb4_hi NVDA8100:00: 0:1: lost during suspend, disconnecting
[ 546.013474] usb4_hi NVDA8100:00: 0:4 <-> 1:16 (USB3): deactivating
[ 546.020449] usb4_hi NVDA8100:00: deactivating USB3 Down path from 0:4 to 1:16
[ 546.027959] usb4_hi NVDA8100:00: deactivating USB3 Up path from 1:16 to 0:4
[ 546.035649] usb4_hi NVDA8100:00: 0:7 <-> 1:9 (PCI): deactivating
[ 546.042463] usb4_hi NVDA8100:00: deactivating PCIe Down path from 0:7 to 1:9
[ 546.050320] usb4_hi NVDA8100:00: deactivating PCIe Up path from 1:9 to 0:7
[ 546.057790] usb4_hi NVDA8100:00: 0: TMU: mode change off -> bi-directional, HiFi requested
[ 546.067910] usb4_hi NVDA8100:00: 0: TMU: mode set to: bi-directional, HiFi
[ 546.076120] usb4_hi NVDA8100:00: 0:1: total credits changed 66 -> 66
[ 546.082844] usb4_hi NVDA8100:00: 1: link width set to symmetric, dual lanes
[ 546.089815] ------------[ cut here ]------------
[ 546.094420] WARNING: CPU: 7 PID: 2433 at drivers/thunderbolt/tb.c:2245 tb_disconnect_pci+0x108/0x120
...
[ 546.238810] Call trace:
[ 546.241246] tb_disconnect_pci+0x108/0x120 (P)
[ 546.245680] tb_domain_disapprove_switch+0x24/0x60
[ 546.250464] disapprove_switch+0xbc/0xe8
[ 546.254378] authorized_store+0x19c/0x270
[ 546.258378] dev_attr_store+0x20/0x70
...
[ 546.303767] ---[ end trace 0000000000000000 ]---
The check prevents a redundant lookup and eliminates the warning
Signed-off-by: Chunfeng Yun <chunfeng.yun@mediatek.com>
---
v2: no changes
---
drivers/thunderbolt/switch.c | 5 +++++
drivers/thunderbolt/tb.c | 7 +++++++
2 files changed, 12 insertions(+)
diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c
index e12546bfb869..6e9f439ebf3f 100644
--- a/drivers/thunderbolt/switch.c
+++ b/drivers/thunderbolt/switch.c
@@ -1850,6 +1850,11 @@ static int tb_switch_set_authorized(struct tb_switch *sw, unsigned int val)
if (!!sw->authorized == !!val)
goto unlock;
+ if (sw->is_unplugged) {
+ ret = -ENODEV;
+ goto unlock;
+ }
+
switch (val) {
/* Disapprove switch */
case 0:
diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c
index 57c11ba13459..69310622d786 100644
--- a/drivers/thunderbolt/tb.c
+++ b/drivers/thunderbolt/tb.c
@@ -2266,6 +2266,10 @@ static int tb_disconnect_pci(struct tb *tb, struct tb_switch *sw)
struct tb_tunnel *tunnel;
struct tb_port *up;
+ /* If the switch has already been unplugged, tunnels are already freed */
+ if (sw->is_unplugged)
+ return 0;
+
up = tb_switch_find_port(sw, TB_TYPE_PCIE_UP);
if (WARN_ON(!up))
return -ENODEV;
@@ -2288,6 +2292,9 @@ static int tb_tunnel_pci(struct tb *tb, struct tb_switch *sw)
struct tb_cm *tcm = tb_priv(tb);
struct tb_tunnel *tunnel;
+ if (sw->is_unplugged)
+ return -ENODEV;
+
up = tb_switch_find_port(sw, TB_TYPE_PCIE_UP);
if (!up)
return 0;
--
2.45.2
^ permalink raw reply [flat|nested] 15+ messages in thread* Re: [PATCH v2 7/7] thunderbolt: prevent pcie tunnel activation/teardown warnings
2026-11-10 11:48 ` [PATCH v2 7/7] thunderbolt: prevent pcie tunnel activation/teardown warnings Chunfeng Yun
@ 2026-11-10 14:50 ` Mika Westerberg
0 siblings, 0 replies; 15+ messages in thread
From: Mika Westerberg @ 2026-11-10 14:50 UTC (permalink / raw)
To: Chunfeng Yun
Cc: Mika Westerberg, Andreas Noever, Konrad Dybcio, Sven Peter,
Yehezkel Bernat, Matthias Brugger, Greg Kroah-Hartman,
AngeloGioacchino Del Regno, linux-kernel, linux-usb,
linux-arm-kernel, linux-mediatek
On Tue, Nov 10, 2026 at 07:48:55PM +0800, Chunfeng Yun wrote:
> When usb4 enter sleep mode, echo 1 to authorized of sysfile,
> will wakeup usb4 driver, but fail to create pcie tunnel, cause
> the warning due to a race condition between router disconnect
> during resume and user authorization
This is hard to understand.
Also PCIe, USB4 and so on.
>
> [ 278.831186] usb4_hi NVDA8100:00: 0:1: is unplugged (state: 7), retrying...
> [ 278.943031] usb4_hi NVDA8100:00: 0:1: failed to reach state TB_PORT_UP. Ignoring port...
> [ 278.951125] usb4_hi NVDA8100:00: 0:1: lost during suspend, disconnecting
> ...
> [ 279.013763] usb4_hi NVDA8100:00: 0:7 <-> 1:9 (PCI): activating
> [ 279.019587] usb4_hi NVDA8100:00: activating PCIe Down path from 0:7 to 1:9
> [ 279.026468] usb4_hi NVDA8100:00: 1:1: Writing hop 1
> [ 279.031335] usb4_hi NVDA8100:00: 1:1: In HopID: 8 => Out port: 9 Out HopID: 8
> [ 279.038546] usb4_hi NVDA8100:00: 1:1: Weight: 1 Priority: 3 Credits: 32 Drop: 0 PM: 0
> [ 279.046536] usb4_hi NVDA8100:00: 1:1: Counter enabled: 0 Counter index: 2047
> [ 279.053834] usb4_hi NVDA8100:00: 1:1: Flow Control (In/Eg): 1/0 Shared Buffer (In/Eg): 0/0
> [ 279.062259] usb4_hi NVDA8100:00: 1:1: Unknown1: 0x0 Unknown2: 0x0 Unknown3: 0x0
> [ 279.069730] ------------[ cut here ]------------
> [ 279.074335] usb4_hi NVDA8100:00: PCIe Down path activation failed
> [ 279.080426] WARNING: CPU: 5 PID: 2433 at drivers/thunderbolt/path.c:587 tb_path_activate+0x1a4/0x5b0
> ...
> [ 279.220285] Call trace:
> [ 279.222722] tb_path_activate+0x1a4/0x5b0 (P)
> [ 279.227068] tb_tunnel_activate+0x140/0x498
> [ 279.231241] tb_tunnel_pci+0x180/0x490
> [ 279.234980] tb_domain_approve_switch+0x74/0xb0
> [ 279.239500] authorized_store+0x214/0x270
> [ 279.243498] dev_attr_store+0x20/0x70
> ...
> [ 279.288888] ---[ end trace 0000000000000000 ]---
> [ 279.293497] usb4_hi NVDA8100:00: 0:7 <-> 1:9 (PCI): activation failed
>
> When usb4 enter sleep mode, echo 0 to authorized of sysfile,
> will wakeup usb4 driver, but fail to disconnect pcie due to
> the PCIe tunnel had already been torn down and freed if the port
> is fail to resume,
Please put the steps like:
1) Boot the system, nothing connected.
2) Once booted up, connected device.
3) Authorize PCIe tunnel via echo 1 > ..
...
So that we can see what are the steps you reproduce this. The above is
really hard to follow at least to me.
>
> [ 545.887822] usb4_hi NVDA8100:00: 0:1: is unplugged (state: 7), retrying...
> [ 545.998689] usb4_hi NVDA8100:00: 0:1: failed to reach state TB_PORT_UP. Ignoring port...
> [ 546.006778] usb4_hi NVDA8100:00: 0:1: lost during suspend, disconnecting
> [ 546.013474] usb4_hi NVDA8100:00: 0:4 <-> 1:16 (USB3): deactivating
> [ 546.020449] usb4_hi NVDA8100:00: deactivating USB3 Down path from 0:4 to 1:16
> [ 546.027959] usb4_hi NVDA8100:00: deactivating USB3 Up path from 1:16 to 0:4
> [ 546.035649] usb4_hi NVDA8100:00: 0:7 <-> 1:9 (PCI): deactivating
> [ 546.042463] usb4_hi NVDA8100:00: deactivating PCIe Down path from 0:7 to 1:9
> [ 546.050320] usb4_hi NVDA8100:00: deactivating PCIe Up path from 1:9 to 0:7
> [ 546.057790] usb4_hi NVDA8100:00: 0: TMU: mode change off -> bi-directional, HiFi requested
> [ 546.067910] usb4_hi NVDA8100:00: 0: TMU: mode set to: bi-directional, HiFi
> [ 546.076120] usb4_hi NVDA8100:00: 0:1: total credits changed 66 -> 66
> [ 546.082844] usb4_hi NVDA8100:00: 1: link width set to symmetric, dual lanes
> [ 546.089815] ------------[ cut here ]------------
> [ 546.094420] WARNING: CPU: 7 PID: 2433 at drivers/thunderbolt/tb.c:2245 tb_disconnect_pci+0x108/0x120
> ...
> [ 546.238810] Call trace:
> [ 546.241246] tb_disconnect_pci+0x108/0x120 (P)
> [ 546.245680] tb_domain_disapprove_switch+0x24/0x60
> [ 546.250464] disapprove_switch+0xbc/0xe8
> [ 546.254378] authorized_store+0x19c/0x270
> [ 546.258378] dev_attr_store+0x20/0x70
> ...
> [ 546.303767] ---[ end trace 0000000000000000 ]---
>
> The check prevents a redundant lookup and eliminates the warning
>
> Signed-off-by: Chunfeng Yun <chunfeng.yun@mediatek.com>
> ---
> v2: no changes
> ---
> drivers/thunderbolt/switch.c | 5 +++++
> drivers/thunderbolt/tb.c | 7 +++++++
> 2 files changed, 12 insertions(+)
>
> diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c
> index e12546bfb869..6e9f439ebf3f 100644
> --- a/drivers/thunderbolt/switch.c
> +++ b/drivers/thunderbolt/switch.c
> @@ -1850,6 +1850,11 @@ static int tb_switch_set_authorized(struct tb_switch *sw, unsigned int val)
> if (!!sw->authorized == !!val)
> goto unlock;
>
> + if (sw->is_unplugged) {
> + ret = -ENODEV;
> + goto unlock;
> + }
> +
> switch (val) {
> /* Disapprove switch */
> case 0:
> diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c
> index 57c11ba13459..69310622d786 100644
> --- a/drivers/thunderbolt/tb.c
> +++ b/drivers/thunderbolt/tb.c
> @@ -2266,6 +2266,10 @@ static int tb_disconnect_pci(struct tb *tb, struct tb_switch *sw)
> struct tb_tunnel *tunnel;
> struct tb_port *up;
>
> + /* If the switch has already been unplugged, tunnels are already freed */
router
everywhere
> + if (sw->is_unplugged)
> + return 0;
> +
Actually I'm thinking that we can just get rid of the WARN_ON()s instead.
If the router is gone we cannot find the upstream port nor the tunnel and
that's fine so just return error instead.
> up = tb_switch_find_port(sw, TB_TYPE_PCIE_UP);
> if (WARN_ON(!up))
> return -ENODEV;
> @@ -2288,6 +2292,9 @@ static int tb_tunnel_pci(struct tb *tb, struct tb_switch *sw)
> struct tb_cm *tcm = tb_priv(tb);
> struct tb_tunnel *tunnel;
>
> + if (sw->is_unplugged)
> + return -ENODEV;
> +
> up = tb_switch_find_port(sw, TB_TYPE_PCIE_UP);
> if (!up)
> return 0;
> --
> 2.45.2
^ permalink raw reply [flat|nested] 15+ messages in thread