thunderbolt: Reset topology created by the boot firmware
authorSanath S <Sanath.S@amd.com>
Sat, 13 Jan 2024 09:52:48 +0000 (11:52 +0200)
committerMika Westerberg <mika.westerberg@linux.intel.com>
Mon, 22 Jan 2024 11:21:06 +0000 (13:21 +0200)
Boot firmware (typically BIOS) might have created tunnels of its own.
The tunnel configuration that it does might be sub-optimal. For instance
it may only support HBR2 monitors so the DisplayPort tunnels it created
may limit Linux graphics drivers. In addition there is an issue on some
AMD based systems where the BIOS does not allocate enough PCIe resources
for future topology extension. By resetting the USB4 topology the PCIe
links will be reset as well allowing Linux to re-allocate.

This aligns the behavior with Windows Connection Manager.

We already issued host router reset for USB4 v2 routers, now extend it
to USB4 v1 routers as well. For pre-USB4 (that's Apple systems) we leave
it as is and continue to discover the existing tunnels.

Suggested-by: Mario Limonciello <mario.limonciello@amd.com>
Signed-off-by: Sanath S <Sanath.S@amd.com>
Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
drivers/thunderbolt/domain.c
drivers/thunderbolt/icm.c
drivers/thunderbolt/nhi.c
drivers/thunderbolt/tb.c
drivers/thunderbolt/tb.h

index 9fb1a64f3300b8fcf7ebc1f7dafa6a68778da122..df0d845e069aca41643e850b84673b7a73330396 100644 (file)
@@ -423,6 +423,7 @@ err_free:
 /**
  * tb_domain_add() - Add domain to the system
  * @tb: Domain to add
+ * @reset: Issue reset to the host router
  *
  * Starts the domain and adds it to the system. Hotplugging devices will
  * work after this has been returned successfully. In order to remove
@@ -431,7 +432,7 @@ err_free:
  *
  * Return: %0 in case of success and negative errno in case of error
  */
-int tb_domain_add(struct tb *tb)
+int tb_domain_add(struct tb *tb, bool reset)
 {
        int ret;
 
@@ -460,7 +461,7 @@ int tb_domain_add(struct tb *tb)
 
        /* Start the domain */
        if (tb->cm_ops->start) {
-               ret = tb->cm_ops->start(tb);
+               ret = tb->cm_ops->start(tb, reset);
                if (ret)
                        goto err_domain_del;
        }
index 56790d50f9e3296bd3607162f6b2030fe28dc77a..baf10d099c7780ee9c83846f391ab1cf796f6f84 100644 (file)
@@ -2144,7 +2144,7 @@ static int icm_runtime_resume(struct tb *tb)
        return 0;
 }
 
-static int icm_start(struct tb *tb)
+static int icm_start(struct tb *tb, bool not_used)
 {
        struct icm *icm = tb_priv(tb);
        int ret;
index fb4f46e51753ab8f3952c7093489571d6a40a1b7..b22023fae60de43d8e456f24a9bf94770461a97d 100644 (file)
@@ -1221,7 +1221,7 @@ static void nhi_check_iommu(struct tb_nhi *nhi)
                str_enabled_disabled(port_ok));
 }
 
-static void nhi_reset(struct tb_nhi *nhi)
+static bool nhi_reset(struct tb_nhi *nhi)
 {
        ktime_t timeout;
        u32 val;
@@ -1229,11 +1229,11 @@ static void nhi_reset(struct tb_nhi *nhi)
        val = ioread32(nhi->iobase + REG_CAPS);
        /* Reset only v2 and later routers */
        if (FIELD_GET(REG_CAPS_VERSION_MASK, val) < REG_CAPS_VERSION_2)
-               return;
+               return false;
 
        if (!host_reset) {
                dev_dbg(&nhi->pdev->dev, "skipping host router reset\n");
-               return;
+               return false;
        }
 
        iowrite32(REG_RESET_HRR, nhi->iobase + REG_RESET);
@@ -1244,12 +1244,14 @@ static void nhi_reset(struct tb_nhi *nhi)
                val = ioread32(nhi->iobase + REG_RESET);
                if (!(val & REG_RESET_HRR)) {
                        dev_warn(&nhi->pdev->dev, "host router reset successful\n");
-                       return;
+                       return true;
                }
                usleep_range(10, 20);
        } while (ktime_before(ktime_get(), timeout));
 
        dev_warn(&nhi->pdev->dev, "timeout resetting host router\n");
+
+       return false;
 }
 
 static int nhi_init_msi(struct tb_nhi *nhi)
@@ -1331,6 +1333,7 @@ static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id)
        struct device *dev = &pdev->dev;
        struct tb_nhi *nhi;
        struct tb *tb;
+       bool reset;
        int res;
 
        if (!nhi_imr_valid(pdev))
@@ -1365,7 +1368,11 @@ static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id)
        nhi_check_quirks(nhi);
        nhi_check_iommu(nhi);
 
-       nhi_reset(nhi);
+       /*
+        * Only USB4 v2 hosts support host reset so if we already did
+        * that then don't do it again when the domain is initialized.
+        */
+       reset = nhi_reset(nhi) ? false : host_reset;
 
        res = nhi_init_msi(nhi);
        if (res)
@@ -1392,7 +1399,7 @@ static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id)
 
        dev_dbg(dev, "NHI initialized, starting thunderbolt\n");
 
-       res = tb_domain_add(tb);
+       res = tb_domain_add(tb, reset);
        if (res) {
                /*
                 * At this point the RX/TX rings might already have been
index 846d2813bb1a51db8744948298d57d5e5fcdb36e..9a261560d0f4c23087d96fae51b15b6902c7655d 100644 (file)
@@ -2581,7 +2581,7 @@ static int tb_scan_finalize_switch(struct device *dev, void *data)
        return 0;
 }
 
-static int tb_start(struct tb *tb)
+static int tb_start(struct tb *tb, bool reset)
 {
        struct tb_cm *tcm = tb_priv(tb);
        int ret;
@@ -2622,12 +2622,24 @@ static int tb_start(struct tb *tb)
        tb_switch_tmu_configure(tb->root_switch, TB_SWITCH_TMU_MODE_LOWRES);
        /* Enable TMU if it is off */
        tb_switch_tmu_enable(tb->root_switch);
-       /* Full scan to discover devices added before the driver was loaded. */
-       tb_scan_switch(tb->root_switch);
-       /* Find out tunnels created by the boot firmware */
-       tb_discover_tunnels(tb);
-       /* Add DP resources from the DP tunnels created by the boot firmware */
-       tb_discover_dp_resources(tb);
+
+       /*
+        * Boot firmware might have created tunnels of its own. Since we
+        * cannot be sure they are usable for us, tear them down and
+        * reset the ports to handle it as new hotplug for USB4 v1
+        * routers (for USB4 v2 and beyond we already do host reset).
+        */
+       if (reset && usb4_switch_version(tb->root_switch) == 1) {
+               tb_switch_reset(tb->root_switch);
+       } else {
+               /* Full scan to discover devices added before the driver was loaded. */
+               tb_scan_switch(tb->root_switch);
+               /* Find out tunnels created by the boot firmware */
+               tb_discover_tunnels(tb);
+               /* Add DP resources from the DP tunnels created by the boot firmware */
+               tb_discover_dp_resources(tb);
+       }
+
        /*
         * If the boot firmware did not create USB 3.x tunnels create them
         * now for the whole topology.
index 2c689e3b02b9417ee9028de5a65ec7e7fc16a13c..d0dfbf040356d831c728589d6af089ac74e00faf 100644 (file)
@@ -483,7 +483,7 @@ struct tb_path {
  */
 struct tb_cm_ops {
        int (*driver_ready)(struct tb *tb);
-       int (*start)(struct tb *tb);
+       int (*start)(struct tb *tb, bool reset);
        void (*stop)(struct tb *tb);
        int (*suspend_noirq)(struct tb *tb);
        int (*resume_noirq)(struct tb *tb);
@@ -746,7 +746,7 @@ int tb_xdomain_init(void);
 void tb_xdomain_exit(void);
 
 struct tb *tb_domain_alloc(struct tb_nhi *nhi, int timeout_msec, size_t privsize);
-int tb_domain_add(struct tb *tb);
+int tb_domain_add(struct tb *tb, bool reset);
 void tb_domain_remove(struct tb *tb);
 int tb_domain_suspend_noirq(struct tb *tb);
 int tb_domain_resume_noirq(struct tb *tb);