thunderbolt: Add quirk to reset host interface on DMA path teardown for AMD USB4 routers

Some AMD USB4 host routers have a bug in the Host Interface where
DMA path setup and teardown cycles may cause the Tx ring to hang.

Fix this by issuing a Host Interface Reset on every DMA path teardown
for affected routers. The Host Interface Reset brings the registers in
the memory BAR to their default state and clears the End-to-End Flow
Control state, preventing the hang condition.

Co-developed-by: Sanath S <Sanath.S@amd.com>
Signed-off-by: Sanath S <Sanath.S@amd.com>
Signed-off-by: Basavaraj Natikar <Basavaraj.Natikar@amd.com>
Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
This commit is contained in:
Basavaraj Natikar
2026-08-07 08:14:26 +02:00
committed by Mika Westerberg
parent e120d14d03
commit f1de1fc5f6
5 changed files with 97 additions and 3 deletions
+25 -1
View File
@@ -788,6 +788,21 @@ int tb_domain_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd,
transmit_ring, receive_path, receive_ring);
}
static void tb_domain_reset_interface(struct tb *tb)
{
struct tb_nhi *nhi = tb->nhi;
if (!nhi->ops->reset_interface)
return;
guard(mutex)(&tb->lock);
/* The reset clears the ring state so stop the control channel */
tb_ctl_stop(tb->ctl);
nhi->ops->reset_interface(nhi);
tb_ctl_start(tb->ctl);
}
/**
* tb_domain_disconnect_xdomain_paths() - Disable DMA paths for XDomain
* @tb: Domain disabling the DMA paths
@@ -810,11 +825,20 @@ int tb_domain_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd,
int transmit_path, int transmit_ring,
int receive_path, int receive_ring)
{
int ret;
if (!tb->cm_ops->disconnect_xdomain_paths)
return -ENOTSUPP;
return tb->cm_ops->disconnect_xdomain_paths(tb, xd, transmit_path,
ret = tb->cm_ops->disconnect_xdomain_paths(tb, xd, transmit_path,
transmit_ring, receive_path, receive_ring);
if (ret)
return ret;
if (tb->nhi->quirks & QUIRK_RESET_DMA_ON_TEARDOWN)
tb_domain_reset_interface(tb);
return 0;
}
static int disconnect_xdomain(struct device *dev, void *data)
+26
View File
@@ -1175,6 +1175,32 @@ static void nhi_reset(struct tb_nhi *nhi)
dev_warn(nhi->dev, "timeout resetting host router\n");
}
/**
* nhi_reset_interface() - Reset the host interface
* @nhi: Host interface to reset
*
* Brings the registers in the memory BAR back to their default state and
* clears the End-to-End Flow Control state. The caller is responsible for
* stopping the control channel over the reset because it clears the ring
* state as well.
*/
void nhi_reset_interface(struct tb_nhi *nhi)
{
u32 val;
val = ioread32(nhi->iobase + REG_CAPS);
/* Only v1 host interfaces implement the reset */
if (FIELD_GET(REG_CAPS_VERSION_MASK, val) >= REG_CAPS_VERSION_2)
return;
dev_dbg(nhi->dev, "issuing host interface reset\n");
iowrite32(REG_HOST_INTERFACE_RESET_RST,
nhi->iobase + REG_HOST_INTERFACE_RESET);
/* Wait for tHIReset (10 ms) to complete */
usleep_range(10000, 20000);
}
static struct tb *nhi_select_cm(struct tb_nhi *nhi)
{
struct tb *tb;
+19 -2
View File
@@ -36,6 +36,8 @@ 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);
void nhi_reset_interface(struct tb_nhi *nhi);
extern const struct dev_pm_ops nhi_pm_ops;
/**
@@ -52,6 +54,7 @@ extern const struct dev_pm_ops 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
* @reset_interface: Resets the host interface
*/
struct tb_nhi_ops {
int (*init)(struct tb_nhi *nhi);
@@ -66,6 +69,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);
void (*reset_interface)(struct tb_nhi *nhi);
};
/*
@@ -116,11 +120,24 @@ struct tb_nhi_ops {
#define PCI_DEVICE_ID_INTEL_PTL_P_NHI0 0xe433
#define PCI_DEVICE_ID_INTEL_PTL_P_NHI1 0xe434
#define PCI_DEVICE_ID_AMD_1AH_M60H_NHI0 0x1120
#define PCI_DEVICE_ID_AMD_1AH_M60H_NHI1 0x1121
#define PCI_DEVICE_ID_AMD_1AH_M68H_NHI0 0x113b
#define PCI_DEVICE_ID_AMD_1AH_M68H_NHI1 0x113c
#define PCI_DEVICE_ID_AMD_1AH_M80H_NHI0 0x1155
#define PCI_DEVICE_ID_AMD_1AH_M80H_NHI1 0x1158
#define PCI_DEVICE_ID_AMD_1AH_M80H_NHI2 0x1159
#define PCI_DEVICE_ID_AMD_1AH_M24H_NHI0 0x151c
#define PCI_DEVICE_ID_AMD_1AH_M24H_NHI1 0x151d
#define PCI_DEVICE_ID_AMD_1AH_M70H_NHI0 0x158d
#define PCI_DEVICE_ID_AMD_1AH_M70H_NHI1 0x158e
#define PCI_CLASS_SERIAL_USB_USB4 0x0c0340
/* Host interface quirks */
#define QUIRK_AUTO_CLEAR_INT BIT(0)
#define QUIRK_E2E BIT(1)
#define QUIRK_AUTO_CLEAR_INT BIT(0)
#define QUIRK_E2E BIT(1)
#define QUIRK_RESET_DMA_ON_TEARDOWN BIT(2)
/*
* Minimal number of vectors when we use MSI-X. Two for control channel
+4
View File
@@ -115,6 +115,10 @@ struct ring_desc {
#define REG_CAPS_VERSION_MASK GENMASK(23, 16)
#define REG_CAPS_VERSION_2 0x40
/* Host Interface Reset - resets TX/RX rings and E2E flow control counters */
#define REG_HOST_INTERFACE_RESET 0x39858
#define REG_HOST_INTERFACE_RESET_RST BIT(0)
#define REG_DMA_MISC 0x39864
#define REG_DMA_MISC_INT_AUTO_CLEAR BIT(2)
#define REG_DMA_MISC_DISABLE_AUTO_CLEAR BIT(17)
+23
View File
@@ -62,6 +62,27 @@ static void nhi_pci_check_quirks(struct tb_nhi_pci *nhi_pci)
nhi->quirks |= QUIRK_E2E;
break;
}
} else if (pdev->vendor == PCI_VENDOR_ID_AMD) {
switch (pdev->device) {
case PCI_DEVICE_ID_AMD_1AH_M60H_NHI0:
case PCI_DEVICE_ID_AMD_1AH_M60H_NHI1:
case PCI_DEVICE_ID_AMD_1AH_M68H_NHI0:
case PCI_DEVICE_ID_AMD_1AH_M68H_NHI1:
case PCI_DEVICE_ID_AMD_1AH_M80H_NHI0:
case PCI_DEVICE_ID_AMD_1AH_M80H_NHI1:
case PCI_DEVICE_ID_AMD_1AH_M80H_NHI2:
case PCI_DEVICE_ID_AMD_1AH_M24H_NHI0:
case PCI_DEVICE_ID_AMD_1AH_M24H_NHI1:
case PCI_DEVICE_ID_AMD_1AH_M70H_NHI0:
case PCI_DEVICE_ID_AMD_1AH_M70H_NHI1:
/*
* These AMD hosts may hang the Tx ring when the
* DMA paths are torn down so they need the host
* interface reset after each teardown.
*/
nhi->quirks |= QUIRK_RESET_DMA_ON_TEARDOWN;
break;
}
}
}
@@ -258,6 +279,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,
.reset_interface = nhi_reset_interface,
};
/* Ice Lake specific NHI operations */
@@ -441,6 +463,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,
.reset_interface = nhi_reset_interface,
};
static int nhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)