187 lines
4.9 KiB
Diff
187 lines
4.9 KiB
Diff
diff --git a/drivers/thunderbolt/domain.c b/drivers/thunderbolt/domain.c
|
|
--- a/drivers/thunderbolt/domain.c
|
|
+++ b/drivers/thunderbolt/domain.c
|
|
@@ -909,6 +909,10 @@ int tb_domain_init(void)
|
|
{
|
|
int ret;
|
|
|
|
+ ret = tb_pci_rescan_init();
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
tb_configfs_init();
|
|
tb_debugfs_init();
|
|
tb_acpi_init();
|
|
@@ -927,6 +931,7 @@ err_xdomain:
|
|
err_acpi:
|
|
tb_acpi_exit();
|
|
tb_debugfs_exit();
|
|
+ tb_pci_rescan_exit();
|
|
|
|
return ret;
|
|
}
|
|
@@ -940,4 +945,5 @@ void tb_domain_exit(void)
|
|
tb_acpi_exit();
|
|
tb_debugfs_exit();
|
|
tb_configfs_exit();
|
|
+ tb_pci_rescan_exit();
|
|
}
|
|
diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c
|
|
--- a/drivers/thunderbolt/tb.c
|
|
+++ b/drivers/thunderbolt/tb.c
|
|
@@ -9,7 +9,9 @@
|
|
#include <linux/slab.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/delay.h>
|
|
+#include <linux/kref.h>
|
|
#include <linux/pm_runtime.h>
|
|
+#include <linux/workqueue.h>
|
|
#include <linux/platform_data/x86/apple.h>
|
|
|
|
#include "tb.h"
|
|
@@ -18,6 +20,7 @@
|
|
|
|
#define TB_TIMEOUT 100 /* ms */
|
|
#define TB_RELEASE_BW_TIMEOUT 10000 /* ms */
|
|
+#define TB_PCI_RESCAN_DELAY 300 /* ms */
|
|
|
|
/*
|
|
* How many time bandwidth allocation request from graphics driver is
|
|
@@ -49,6 +52,13 @@ MODULE_PARM_DESC(asym_threshold,
|
|
"threshold (Mb/s) when to Gen 4 switch link symmetry. 0 disables. (default: "
|
|
__MODULE_STRING(TB_ASYM_THRESHOLD) ")");
|
|
|
|
+struct tb_pci_rescan {
|
|
+ struct delayed_work work;
|
|
+ struct kref kref;
|
|
+ struct pci_bus *bus;
|
|
+ bool stopped;
|
|
+};
|
|
+
|
|
/**
|
|
* struct tb_cm - Simple Thunderbolt connection manager
|
|
* @tunnel_list: List of active tunnels
|
|
@@ -67,8 +77,11 @@ struct tb_cm {
|
|
bool hotplug_active;
|
|
struct delayed_work remove_work;
|
|
struct tb_bandwidth_group groups[MAX_GROUPS];
|
|
+ struct tb_pci_rescan *pci_rescan;
|
|
};
|
|
|
|
+static struct workqueue_struct *tb_pci_rescan_wq;
|
|
+
|
|
static inline struct tb *tcm_to_tb(struct tb_cm *tcm)
|
|
{
|
|
return ((void *)tcm - sizeof(struct tb));
|
|
@@ -2282,6 +2295,82 @@ static int tb_disconnect_pci(struct tb *tb, struct tb_switch *sw)
|
|
return 0;
|
|
}
|
|
|
|
+static void tb_pci_rescan_release(struct kref *kref)
|
|
+{
|
|
+ struct tb_pci_rescan *rescan = container_of(kref, typeof(*rescan), kref);
|
|
+
|
|
+ put_device(&rescan->bus->dev);
|
|
+ kfree(rescan);
|
|
+}
|
|
+
|
|
+static void tb_pci_rescan_work_fn(struct work_struct *work)
|
|
+{
|
|
+ struct tb_pci_rescan *rescan =
|
|
+ container_of(to_delayed_work(work), typeof(*rescan), work);
|
|
+
|
|
+ pci_lock_rescan_remove();
|
|
+ if (!READ_ONCE(rescan->stopped))
|
|
+ pci_rescan_bus(rescan->bus);
|
|
+ pci_unlock_rescan_remove();
|
|
+ kref_put(&rescan->kref, tb_pci_rescan_release);
|
|
+}
|
|
+
|
|
+static void tb_pci_rescan_schedule(struct tb *tb)
|
|
+{
|
|
+ struct tb_cm *tcm = tb_priv(tb);
|
|
+ struct tb_pci_rescan *rescan = tcm->pci_rescan;
|
|
+
|
|
+ if (!rescan) {
|
|
+ struct pci_bus *bus;
|
|
+
|
|
+ if (!dev_is_pci(tb->nhi->dev))
|
|
+ return;
|
|
+ bus = to_pci_dev(tb->nhi->dev)->bus;
|
|
+ if (bus->parent)
|
|
+ bus = bus->parent;
|
|
+
|
|
+ rescan = kzalloc_obj(*rescan);
|
|
+ if (!rescan)
|
|
+ return;
|
|
+ INIT_DELAYED_WORK(&rescan->work, tb_pci_rescan_work_fn);
|
|
+ kref_init(&rescan->kref);
|
|
+ get_device(&bus->dev);
|
|
+ rescan->bus = bus;
|
|
+ tcm->pci_rescan = rescan;
|
|
+ }
|
|
+
|
|
+ kref_get(&rescan->kref);
|
|
+ if (mod_delayed_work(tb_pci_rescan_wq, &rescan->work,
|
|
+ msecs_to_jiffies(TB_PCI_RESCAN_DELAY)))
|
|
+ kref_put(&rescan->kref, tb_pci_rescan_release);
|
|
+}
|
|
+
|
|
+static void tb_pci_rescan_stop(struct tb_cm *tcm)
|
|
+{
|
|
+ struct tb_pci_rescan *rescan = tcm->pci_rescan;
|
|
+
|
|
+ if (!rescan)
|
|
+ return;
|
|
+
|
|
+ WRITE_ONCE(rescan->stopped, true);
|
|
+ if (cancel_delayed_work(&rescan->work))
|
|
+ kref_put(&rescan->kref, tb_pci_rescan_release);
|
|
+ kref_put(&rescan->kref, tb_pci_rescan_release);
|
|
+ tcm->pci_rescan = NULL;
|
|
+}
|
|
+
|
|
+int tb_pci_rescan_init(void)
|
|
+{
|
|
+ tb_pci_rescan_wq = alloc_workqueue("thunderbolt_pci_rescan",
|
|
+ WQ_FREEZABLE | WQ_UNBOUND, 0);
|
|
+ return tb_pci_rescan_wq ? 0 : -ENOMEM;
|
|
+}
|
|
+
|
|
+void tb_pci_rescan_exit(void)
|
|
+{
|
|
+ destroy_workqueue(tb_pci_rescan_wq);
|
|
+}
|
|
+
|
|
static int tb_tunnel_pci(struct tb *tb, struct tb_switch *sw)
|
|
{
|
|
struct tb_port *up, *down, *port;
|
|
@@ -2323,6 +2412,7 @@ static int tb_tunnel_pci(struct tb *tb, struct tb_switch *sw)
|
|
tb_sw_warn(sw, "failed to connect xHCI\n");
|
|
|
|
list_add_tail(&tunnel->list, &tcm->tunnel_list);
|
|
+ tb_pci_rescan_schedule(tb);
|
|
return 0;
|
|
}
|
|
|
|
@@ -2957,6 +3047,7 @@ static void tb_stop(struct tb *tb)
|
|
struct tb_tunnel *n;
|
|
|
|
cancel_delayed_work(&tcm->remove_work);
|
|
+ tb_pci_rescan_stop(tcm);
|
|
/* tunnels are only present after everything has been initialized */
|
|
list_for_each_entry_safe(tunnel, n, &tcm->tunnel_list, list) {
|
|
/*
|
|
diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h
|
|
--- a/drivers/thunderbolt/tb.h
|
|
+++ b/drivers/thunderbolt/tb.h
|
|
@@ -759,6 +759,8 @@ static inline int tb_port_write(struct tb_port *port, const void *buffer,
|
|
|
|
struct tb *icm_probe(struct tb_nhi *nhi);
|
|
struct tb *tb_probe(struct tb_nhi *nhi);
|
|
+int tb_pci_rescan_init(void);
|
|
+void tb_pci_rescan_exit(void);
|
|
|
|
extern const struct device_type tb_domain_type;
|
|
extern const struct device_type tb_retimer_type;
|