summaryrefslogtreecommitdiffstats
path: root/drivers/thunderbolt
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/thunderbolt')
-rw-r--r--drivers/thunderbolt/ctl.c63
-rw-r--r--drivers/thunderbolt/domain.c26
-rw-r--r--drivers/thunderbolt/nhi.c31
-rw-r--r--drivers/thunderbolt/nhi.h21
-rw-r--r--drivers/thunderbolt/nhi_regs.h4
-rw-r--r--drivers/thunderbolt/pci.c23
-rw-r--r--drivers/thunderbolt/quirks.c3
-rw-r--r--drivers/thunderbolt/stream.c3
-rw-r--r--drivers/thunderbolt/switch.c9
-rw-r--r--drivers/thunderbolt/tb.c37
-rw-r--r--drivers/thunderbolt/test.c58
-rw-r--r--drivers/thunderbolt/tunnel.c73
-rw-r--r--drivers/thunderbolt/tunnel.h8
-rw-r--r--drivers/thunderbolt/xdomain.c5
14 files changed, 180 insertions, 184 deletions
diff --git a/drivers/thunderbolt/ctl.c b/drivers/thunderbolt/ctl.c
index cd47b627f..965988b18 100644
--- a/drivers/thunderbolt/ctl.c
+++ b/drivers/thunderbolt/ctl.c
@@ -73,7 +73,6 @@ struct tb_ctl {
#define tb_ctl_dbg_once(ctl, format, arg...) \
dev_dbg_once((ctl)->nhi->dev, format, ## arg)
-static DECLARE_WAIT_QUEUE_HEAD(tb_cfg_request_cancel_queue);
/* Serializes access to request kref_get/put */
static DEFINE_MUTEX(tb_cfg_request_lock);
@@ -133,41 +132,42 @@ void tb_cfg_request_put(struct tb_cfg_request *req)
static int tb_cfg_request_enqueue(struct tb_ctl *ctl,
struct tb_cfg_request *req)
{
+ tb_cfg_request_get(req);
+
WARN_ON(test_bit(TB_CFG_REQUEST_ACTIVE, &req->flags));
WARN_ON(req->ctl);
- mutex_lock(&ctl->request_queue_lock);
+ guard(mutex)(&ctl->request_queue_lock);
if (!ctl->running) {
- mutex_unlock(&ctl->request_queue_lock);
+ tb_cfg_request_put(req);
return -ENOTCONN;
}
req->ctl = ctl;
list_add_tail(&req->list, &ctl->request_queue);
set_bit(TB_CFG_REQUEST_ACTIVE, &req->flags);
- mutex_unlock(&ctl->request_queue_lock);
return 0;
}
-static void tb_cfg_request_dequeue(struct tb_cfg_request *req)
+static bool tb_cfg_request_is_active(struct tb_cfg_request *req)
{
- struct tb_ctl *ctl = req->ctl;
-
- mutex_lock(&ctl->request_queue_lock);
- if (!test_bit(TB_CFG_REQUEST_ACTIVE, &req->flags)) {
- mutex_unlock(&ctl->request_queue_lock);
- return;
- }
+ return test_bit(TB_CFG_REQUEST_ACTIVE, &req->flags);
+}
- list_del(&req->list);
- clear_bit(TB_CFG_REQUEST_ACTIVE, &req->flags);
- if (test_bit(TB_CFG_REQUEST_CANCELED, &req->flags))
- wake_up(&tb_cfg_request_cancel_queue);
- mutex_unlock(&ctl->request_queue_lock);
+static bool tb_cfg_request_is_canceled(struct tb_cfg_request *req)
+{
+ return test_bit(TB_CFG_REQUEST_CANCELED, &req->flags);
}
-static bool tb_cfg_request_is_active(struct tb_cfg_request *req)
+static void tb_cfg_request_dequeue(struct tb_cfg_request *req)
{
- return test_bit(TB_CFG_REQUEST_ACTIVE, &req->flags);
+ struct tb_ctl *ctl = req->ctl;
+
+ guard(mutex)(&ctl->request_queue_lock);
+ if (tb_cfg_request_is_active(req)) {
+ list_del(&req->list);
+ clear_bit(TB_CFG_REQUEST_ACTIVE, &req->flags);
+ tb_cfg_request_put(req);
+ }
}
static struct tb_cfg_request *
@@ -178,7 +178,7 @@ tb_cfg_request_find(struct tb_ctl *ctl, struct ctl_pkg *pkg)
mutex_lock(&pkg->ctl->request_queue_lock);
list_for_each_entry(iter, &pkg->ctl->request_queue, list) {
tb_cfg_request_get(iter);
- if (iter->match(iter, pkg)) {
+ if (!tb_cfg_request_is_canceled(iter) && iter->match(iter, pkg)) {
req = iter;
break;
}
@@ -512,8 +512,11 @@ static void tb_ctl_rx_callback(struct tb_ring *ring, struct ring_frame *frame,
trace_tb_rx(pkg->ctl->index, frame->eof, pkg->buffer, frame->size, !req);
if (req) {
- if (req->copy(req, pkg))
- schedule_work(&req->work);
+ scoped_guard(mutex, &pkg->ctl->request_queue_lock) {
+ if (!tb_cfg_request_is_canceled(req) &&
+ req->copy(req, pkg))
+ schedule_work(&req->work);
+ }
tb_cfg_request_put(req);
}
@@ -525,11 +528,10 @@ static void tb_cfg_request_work(struct work_struct *work)
{
struct tb_cfg_request *req = container_of(work, typeof(*req), work);
- if (!test_bit(TB_CFG_REQUEST_CANCELED, &req->flags))
+ if (!tb_cfg_request_is_canceled(req))
req->callback(req->callback_data);
tb_cfg_request_dequeue(req);
- tb_cfg_request_put(req);
}
/**
@@ -555,10 +557,9 @@ int tb_cfg_request(struct tb_ctl *ctl, struct tb_cfg_request *req,
INIT_WORK(&req->work, tb_cfg_request_work);
INIT_LIST_HEAD(&req->list);
- tb_cfg_request_get(req);
ret = tb_cfg_request_enqueue(ctl, req);
if (ret)
- goto err_put;
+ return ret;
ret = tb_ctl_tx(ctl, req->request, req->request_size,
req->request_type);
@@ -572,9 +573,6 @@ int tb_cfg_request(struct tb_ctl *ctl, struct tb_cfg_request *req,
err_dequeue:
tb_cfg_request_dequeue(req);
-err_put:
- tb_cfg_request_put(req);
-
return ret;
}
@@ -588,9 +586,10 @@ err_put:
*/
void tb_cfg_request_cancel(struct tb_cfg_request *req, int err)
{
- set_bit(TB_CFG_REQUEST_CANCELED, &req->flags);
- schedule_work(&req->work);
- wait_event(tb_cfg_request_cancel_queue, !tb_cfg_request_is_active(req));
+ scoped_guard(mutex, &req->ctl->request_queue_lock)
+ set_bit(TB_CFG_REQUEST_CANCELED, &req->flags);
+ cancel_work_sync(&req->work);
+ tb_cfg_request_dequeue(req);
req->result.err = err;
}
diff --git a/drivers/thunderbolt/domain.c b/drivers/thunderbolt/domain.c
index 12c88509a..24611f05b 100644
--- a/drivers/thunderbolt/domain.c
+++ b/drivers/thunderbolt/domain.c
@@ -788,21 +788,6 @@ 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
@@ -825,20 +810,11 @@ 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;
- ret = tb->cm_ops->disconnect_xdomain_paths(tb, xd, transmit_path,
+ return 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)
diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c
index 5809809f6..be18f7b65 100644
--- a/drivers/thunderbolt/nhi.c
+++ b/drivers/thunderbolt/nhi.c
@@ -15,6 +15,7 @@
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/iommu.h>
+#include <linux/lockdep.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/property.h>
@@ -560,6 +561,8 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size,
INIT_LIST_HEAD(&ring->in_flight);
INIT_WORK(&ring->work, ring_work);
init_waitqueue_head(&ring->wait);
+ lockdep_register_key(&ring->lock_key);
+ lockdep_init_map(&ring->work.lockdep_map, "ring.work", &ring->lock_key, 0);
ring->nhi = nhi;
ring->hop = hop;
@@ -599,6 +602,7 @@ err_free_descs:
ring->size * sizeof(*ring->descriptors),
ring->descriptors, ring->descriptors_dma);
err_free_ring:
+ lockdep_unregister_key(&ring->lock_key);
kfree(ring);
return NULL;
@@ -848,6 +852,7 @@ void tb_ring_free(struct tb_ring *ring)
* to finish before freeing the ring.
*/
flush_work(&ring->work);
+ lockdep_unregister_key(&ring->lock_key);
kfree(ring);
}
EXPORT_SYMBOL_GPL(tb_ring_free);
@@ -1175,32 +1180,6 @@ 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;
diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h
index f72d6b274..d488eadad 100644
--- a/drivers/thunderbolt/nhi.h
+++ b/drivers/thunderbolt/nhi.h
@@ -36,8 +36,6 @@ 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;
/**
@@ -54,7 +52,6 @@ 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);
@@ -69,7 +66,6 @@ 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);
};
/*
@@ -120,24 +116,11 @@ 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_RESET_DMA_ON_TEARDOWN BIT(2)
+#define QUIRK_AUTO_CLEAR_INT BIT(0)
+#define QUIRK_E2E BIT(1)
/*
* Minimal number of vectors when we use MSI-X. Two for control channel
diff --git a/drivers/thunderbolt/nhi_regs.h b/drivers/thunderbolt/nhi_regs.h
index 99df60b6d..d6a197fab 100644
--- a/drivers/thunderbolt/nhi_regs.h
+++ b/drivers/thunderbolt/nhi_regs.h
@@ -115,10 +115,6 @@ 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)
diff --git a/drivers/thunderbolt/pci.c b/drivers/thunderbolt/pci.c
index 99333729f..8462ccb59 100644
--- a/drivers/thunderbolt/pci.c
+++ b/drivers/thunderbolt/pci.c
@@ -62,27 +62,6 @@ 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;
- }
}
}
@@ -279,7 +258,6 @@ 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 */
@@ -463,7 +441,6 @@ 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)
diff --git a/drivers/thunderbolt/quirks.c b/drivers/thunderbolt/quirks.c
index 9f7914ac2..96b7fe038 100644
--- a/drivers/thunderbolt/quirks.c
+++ b/drivers/thunderbolt/quirks.c
@@ -99,6 +99,9 @@ static const struct tb_quirk tb_quirks[] = {
quirk_usb3_maximum_bandwidth },
{ 0x8087, PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HUB_40G_BRIDGE, 0x0000, 0x0000,
quirk_usb3_maximum_bandwidth },
+ /* CLx is unstable on the Anker Prime TB5 dock (DROM 01ea:83b5) */
+ { 0x8087, PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HUB_80G_BRIDGE, 0x01ea, 0x83b5,
+ quirk_clx_disable },
/*
* Block Runtime PM in DP redrive mode for Intel Barlow Ridge host
* controllers.
diff --git a/drivers/thunderbolt/stream.c b/drivers/thunderbolt/stream.c
index 25c259dd0..988765c98 100644
--- a/drivers/thunderbolt/stream.c
+++ b/drivers/thunderbolt/stream.c
@@ -978,6 +978,9 @@ static int tbstream_dev_fops_open(struct inode *inode, struct file *file)
}
mutex_unlock(&sdev->lock);
+
+ /* Stream handles IOCB_NOWAIT just fine */
+ file->f_mode |= FMODE_NOWAIT;
return 0;
err_unlock:
diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c
index 404c0693d..cf571a7c9 100644
--- a/drivers/thunderbolt/switch.c
+++ b/drivers/thunderbolt/switch.c
@@ -774,6 +774,7 @@ static int tb_port_alloc_hopid(struct tb_port *port, bool in, int min_hopid,
{
int port_max_hopid;
struct ida *ida;
+ int ret;
if (in) {
port_max_hopid = port->config.max_in_hop_id;
@@ -793,7 +794,11 @@ static int tb_port_alloc_hopid(struct tb_port *port, bool in, int min_hopid,
if (max_hopid < 0 || max_hopid > port_max_hopid)
max_hopid = port_max_hopid;
- return ida_alloc_range(ida, min_hopid, max_hopid, GFP_KERNEL);
+ ret = ida_alloc_range(ida, min_hopid, max_hopid, GFP_KERNEL);
+ if (ret >= 0)
+ tb_switch_get(port->sw);
+
+ return ret;
}
/**
@@ -832,6 +837,7 @@ int tb_port_alloc_out_hopid(struct tb_port *port, int min_hopid, int max_hopid)
void tb_port_release_in_hopid(struct tb_port *port, int hopid)
{
ida_free(&port->in_hopids, hopid);
+ tb_switch_put(port->sw);
}
/**
@@ -842,6 +848,7 @@ void tb_port_release_in_hopid(struct tb_port *port, int hopid)
void tb_port_release_out_hopid(struct tb_port *port, int hopid)
{
ida_free(&port->out_hopids, hopid);
+ tb_switch_put(port->sw);
}
static inline bool tb_switch_is_reachable(const struct tb_switch *parent,
diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c
index 47753a5c0..4e5d0578f 100644
--- a/drivers/thunderbolt/tb.c
+++ b/drivers/thunderbolt/tb.c
@@ -89,6 +89,7 @@ static void tb_dp_resource_unavailable(struct tb *tb, struct tb_port *port,
const char *reason);
static void tb_queue_dp_bandwidth_request(struct tb *tb, u64 route, u8 port,
int retry, unsigned long delay);
+static void tb_dp_tunnel_active(struct tb_tunnel *tunnel, void *data);
static void tb_queue_hotplug(struct tb *tb, u64 route, u8 port, bool unplug)
{
@@ -385,7 +386,8 @@ static void tb_switch_discover_tunnels(struct tb_switch *sw,
switch (port->config.type) {
case TB_TYPE_DP_HDMI_IN:
- tunnel = tb_tunnel_discover_dp(tb, port, alloc_hopids);
+ tunnel = tb_tunnel_discover_dp(tb, port, alloc_hopids,
+ tb_dp_tunnel_active, tb);
tb_increase_tmu_accuracy(tunnel);
break;
@@ -1910,6 +1912,18 @@ static void tb_dp_tunnel_active(struct tb_tunnel *tunnel, void *data)
struct tb *tb = data;
mutex_lock(&tb->lock);
+
+ /*
+ * If the DPRX read was canceled the tunnel is already being torn
+ * down by whoever canceled it. Do not touch the adapters here
+ * because the routers may be gone by now.
+ */
+ if (tunnel->dprx_canceled) {
+ tb_tunnel_dbg(tunnel, "DPRX read canceled, not activating\n");
+ mutex_unlock(&tb->lock);
+ return;
+ }
+
if (tb_tunnel_is_active(tunnel)) {
int consumed_up, consumed_down, ret;
@@ -1964,8 +1978,6 @@ static void tb_dp_tunnel_active(struct tb_tunnel *tunnel, void *data)
tb_dp_resource_unavailable(tb, in, "DPRX negotiation failed");
}
mutex_unlock(&tb->lock);
-
- tb_domain_put(tb);
}
static void tb_tunnel_one_dp(struct tb *tb, struct tb_port *in,
@@ -2026,8 +2038,7 @@ static void tb_tunnel_one_dp(struct tb *tb, struct tb_port *in,
available_up, available_down);
tunnel = tb_tunnel_alloc_dp(tb, in, out, link_nr, available_up,
- available_down, tb_dp_tunnel_active,
- tb_domain_get(tb));
+ available_down, tb_dp_tunnel_active, tb);
if (!tunnel) {
tb_port_dbg(out, "could not allocate DP tunnel\n");
goto err_reclaim_usb;
@@ -2048,7 +2059,6 @@ err_free:
tb_tunnel_put(tunnel);
err_reclaim_usb:
tb_reclaim_usb3_bandwidth(tb, in, out);
- tb_domain_put(tb);
err_detach_group:
tb_detach_bandwidth_group(in);
err_dealloc_dp:
@@ -2950,11 +2960,12 @@ static void tb_stop(struct tb *tb)
/* tunnels are only present after everything has been initialized */
list_for_each_entry_safe(tunnel, n, &tcm->tunnel_list, list) {
/*
- * DMA tunnels require the driver to be functional so we
- * tear them down. Other protocol tunnels can be left
- * intact.
+ * DMA tunnels and DP tunnels which are not yet active require
+ * the driver to be functional so we tear them down.
+ * Other protocol tunnels can be left intact.
*/
- if (tb_tunnel_is_dma(tunnel))
+ if (tb_tunnel_is_dma(tunnel) ||
+ (tb_tunnel_is_dp(tunnel) && !tb_tunnel_is_active(tunnel)))
tb_tunnel_deactivate(tunnel);
tb_tunnel_put(tunnel);
}
@@ -3274,11 +3285,11 @@ static void tb_remove_work(struct work_struct *work)
struct tb *tb = tcm_to_tb(tcm);
mutex_lock(&tb->lock);
- if (tb->root_switch)
+ if (tb->root_switch) {
tb_free_unplugged_children(tb->root_switch);
+ tb_free_unplugged_xdomains(tb->root_switch);
+ }
mutex_unlock(&tb->lock);
-
- tb_free_unplugged_xdomains(tb->root_switch);
}
static int tb_runtime_resume(struct tb *tb)
diff --git a/drivers/thunderbolt/test.c b/drivers/thunderbolt/test.c
index 05652ee82..c8c648f31 100644
--- a/drivers/thunderbolt/test.c
+++ b/drivers/thunderbolt/test.c
@@ -33,6 +33,18 @@ static void kunit_ida_init(struct kunit *test, struct ida *ida)
kunit_alloc_resource(test, __ida_init, __ida_destroy, GFP_KERNEL, ida);
}
+static void tb_test_switch_release(struct device *dev)
+{
+ /* The memory is owned by KUnit, nothing to do here */
+}
+
+static void tb_test_switch_put(void *data)
+{
+ struct tb_switch *sw = data;
+
+ put_device(&sw->dev);
+}
+
static struct tb_switch *alloc_switch(struct kunit *test, u64 route,
u8 upstream_port, u8 max_port_number)
{
@@ -44,6 +56,15 @@ static struct tb_switch *alloc_switch(struct kunit *test, u64 route,
if (!sw)
return NULL;
+ /*
+ * HopID allocations take a reference to their routers and those devices
+ * have to be initialized for that to work.
+ */
+ sw->dev.release = tb_test_switch_release;
+ device_initialize(&sw->dev);
+ if (kunit_add_action_or_reset(test, tb_test_switch_put, sw))
+ return NULL;
+
sw->config.upstream_port_number = upstream_port;
sw->config.depth = tb_route_length(route);
sw->config.route_hi = upper_32_bits(route);
@@ -1386,6 +1407,10 @@ static void tb_test_tunnel_pcie(struct kunit *test)
tb_tunnel_put(tunnel1);
}
+static void tb_test_dp_tunnel_active(struct tb_tunnel *tunnel, void *data)
+{
+}
+
static void tb_test_tunnel_dp(struct kunit *test)
{
struct tb_switch *host, *dev;
@@ -1406,7 +1431,8 @@ static void tb_test_tunnel_dp(struct kunit *test)
in = &host->ports[5];
out = &dev->ports[13];
- tunnel = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, NULL, NULL);
+ tunnel = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0,
+ tb_test_dp_tunnel_active, NULL);
KUNIT_ASSERT_NOT_NULL(test, tunnel);
KUNIT_EXPECT_EQ(test, tunnel->type, TB_TUNNEL_DP);
KUNIT_EXPECT_PTR_EQ(test, tunnel->src_port, in);
@@ -1452,7 +1478,8 @@ static void tb_test_tunnel_dp_chain(struct kunit *test)
in = &host->ports[5];
out = &dev4->ports[14];
- tunnel = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, NULL, NULL);
+ tunnel = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0,
+ tb_test_dp_tunnel_active, NULL);
KUNIT_ASSERT_NOT_NULL(test, tunnel);
KUNIT_EXPECT_EQ(test, tunnel->type, TB_TUNNEL_DP);
KUNIT_EXPECT_PTR_EQ(test, tunnel->src_port, in);
@@ -1502,7 +1529,8 @@ static void tb_test_tunnel_dp_tree(struct kunit *test)
in = &dev2->ports[13];
out = &dev5->ports[13];
- tunnel = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, NULL, NULL);
+ tunnel = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0,
+ tb_test_dp_tunnel_active, NULL);
KUNIT_ASSERT_NOT_NULL(test, tunnel);
KUNIT_EXPECT_EQ(test, tunnel->type, TB_TUNNEL_DP);
KUNIT_EXPECT_PTR_EQ(test, tunnel->src_port, in);
@@ -1567,7 +1595,8 @@ static void tb_test_tunnel_dp_max_length(struct kunit *test)
in = &dev6->ports[13];
out = &dev12->ports[13];
- tunnel = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, NULL, NULL);
+ tunnel = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0,
+ tb_test_dp_tunnel_active, NULL);
KUNIT_ASSERT_NOT_NULL(test, tunnel);
KUNIT_EXPECT_EQ(test, tunnel->type, TB_TUNNEL_DP);
KUNIT_EXPECT_PTR_EQ(test, tunnel->src_port, in);
@@ -1637,7 +1666,8 @@ static void tb_test_tunnel_3dp(struct kunit *test)
out2 = &dev5->ports[13];
out3 = &dev4->ports[14];
- tunnel1 = tb_tunnel_alloc_dp(NULL, in1, out1, 1, 0, 0, NULL, NULL);
+ tunnel1 = tb_tunnel_alloc_dp(NULL, in1, out1, 1, 0, 0,
+ tb_test_dp_tunnel_active, NULL);
KUNIT_ASSERT_TRUE(test, tunnel1 != NULL);
KUNIT_EXPECT_EQ(test, tunnel1->type, TB_TUNNEL_DP);
KUNIT_EXPECT_PTR_EQ(test, tunnel1->src_port, in1);
@@ -1645,7 +1675,8 @@ static void tb_test_tunnel_3dp(struct kunit *test)
KUNIT_ASSERT_EQ(test, tunnel1->npaths, 3);
KUNIT_ASSERT_EQ(test, tunnel1->paths[0]->path_length, 3);
- tunnel2 = tb_tunnel_alloc_dp(NULL, in2, out2, 1, 0, 0, NULL, NULL);
+ tunnel2 = tb_tunnel_alloc_dp(NULL, in2, out2, 1, 0, 0,
+ tb_test_dp_tunnel_active, NULL);
KUNIT_ASSERT_TRUE(test, tunnel2 != NULL);
KUNIT_EXPECT_EQ(test, tunnel2->type, TB_TUNNEL_DP);
KUNIT_EXPECT_PTR_EQ(test, tunnel2->src_port, in2);
@@ -1653,7 +1684,8 @@ static void tb_test_tunnel_3dp(struct kunit *test)
KUNIT_ASSERT_EQ(test, tunnel2->npaths, 3);
KUNIT_ASSERT_EQ(test, tunnel2->paths[0]->path_length, 4);
- tunnel3 = tb_tunnel_alloc_dp(NULL, in3, out3, 1, 0, 0, NULL, NULL);
+ tunnel3 = tb_tunnel_alloc_dp(NULL, in3, out3, 1, 0, 0,
+ tb_test_dp_tunnel_active, NULL);
KUNIT_ASSERT_TRUE(test, tunnel3 != NULL);
KUNIT_EXPECT_EQ(test, tunnel3->type, TB_TUNNEL_DP);
KUNIT_EXPECT_PTR_EQ(test, tunnel3->src_port, in3);
@@ -1751,7 +1783,8 @@ static void tb_test_tunnel_port_on_path(struct kunit *test)
in = &dev2->ports[13];
out = &dev5->ports[13];
- dp_tunnel = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, NULL, NULL);
+ dp_tunnel = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0,
+ tb_test_dp_tunnel_active, NULL);
KUNIT_ASSERT_NOT_NULL(test, dp_tunnel);
KUNIT_EXPECT_TRUE(test, tb_tunnel_port_on_path(dp_tunnel, in));
@@ -2183,7 +2216,8 @@ static void tb_test_credit_alloc_dp(struct kunit *test)
in = &host->ports[5];
out = &dev->ports[14];
- tunnel = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, NULL, NULL);
+ tunnel = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0,
+ tb_test_dp_tunnel_active, NULL);
KUNIT_ASSERT_NOT_NULL(test, tunnel);
KUNIT_ASSERT_EQ(test, tunnel->npaths, (size_t)3);
@@ -2419,7 +2453,8 @@ static struct tb_tunnel *TB_TEST_DP_TUNNEL1(struct kunit *test,
in = &host->ports[5];
out = &dev->ports[13];
- dp_tunnel1 = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, NULL, NULL);
+ dp_tunnel1 = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0,
+ tb_test_dp_tunnel_active, NULL);
KUNIT_ASSERT_NOT_NULL(test, dp_tunnel1);
KUNIT_ASSERT_EQ(test, dp_tunnel1->npaths, (size_t)3);
@@ -2456,7 +2491,8 @@ static struct tb_tunnel *TB_TEST_DP_TUNNEL2(struct kunit *test,
in = &host->ports[6];
out = &dev->ports[14];
- dp_tunnel2 = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, NULL, NULL);
+ dp_tunnel2 = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0,
+ tb_test_dp_tunnel_active, NULL);
KUNIT_ASSERT_NOT_NULL(test, dp_tunnel2);
KUNIT_ASSERT_EQ(test, dp_tunnel2->npaths, (size_t)3);
diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c
index 7e8284575..ffd2d04b3 100644
--- a/drivers/thunderbolt/tunnel.c
+++ b/drivers/thunderbolt/tunnel.c
@@ -510,6 +510,7 @@ struct tb_tunnel *tb_tunnel_discover_pci(struct tb *tb, struct tb_port *down,
goto err_deactivate;
}
+ tb_tunnel_set_active(tunnel, true);
tb_tunnel_dbg(tunnel, "discovered\n");
return tunnel;
@@ -1093,8 +1094,14 @@ static void tb_dp_dprx_work(struct work_struct *work)
struct tb_tunnel *tunnel = container_of(work, typeof(*tunnel), dprx_work.work);
struct tb *tb = tunnel->tb;
+ /*
+ * The DPRX read can be canceled while this work is waiting for
+ * tb->lock. Check the flag only once it is held: while the lock is
+ * held the tunnel cannot be torn down under us and the adapters are
+ * safe to access.
+ */
+ mutex_lock(&tb->lock);
if (!tunnel->dprx_canceled) {
- mutex_lock(&tb->lock);
if (tb_dp_is_usb4(tunnel->src_port->sw) &&
tb_dp_wait_dprx(tunnel, TB_DPRX_WAIT_TIMEOUT)) {
if (ktime_before(ktime_get(), tunnel->dprx_timeout)) {
@@ -1106,41 +1113,42 @@ static void tb_dp_dprx_work(struct work_struct *work)
} else {
tb_tunnel_set_active(tunnel, true);
}
- mutex_unlock(&tb->lock);
}
+ mutex_unlock(&tb->lock);
- if (tunnel->callback)
- tunnel->callback(tunnel, tunnel->callback_data);
+ tunnel->callback(tunnel, tunnel->callback_data);
tb_tunnel_put(tunnel);
+ tb_domain_put(tb);
}
static int tb_dp_dprx_start(struct tb_tunnel *tunnel)
{
/*
- * Bump up the reference to keep the tunnel around. It will be
- * dropped in tb_dp_dprx_stop() once the tunnel is deactivated.
+ * Bump up the references to keep the tunnel and the domain around
+ * until the work has run or has been canceled.
*/
tb_tunnel_get(tunnel);
+ tb_domain_get(tunnel->tb);
tunnel->dprx_started = true;
+ tunnel->dprx_canceled = false;
+ tunnel->dprx_timeout = dprx_timeout_to_ktime(dprx_timeout);
+ queue_delayed_work(tunnel->tb->wq, &tunnel->dprx_work, 0);
- if (tunnel->callback) {
- tunnel->dprx_timeout = dprx_timeout_to_ktime(dprx_timeout);
- queue_delayed_work(tunnel->tb->wq, &tunnel->dprx_work, 0);
- return -EINPROGRESS;
- }
-
- return tb_dp_is_usb4(tunnel->src_port->sw) ?
- tb_dp_wait_dprx(tunnel, dprx_timeout) : 0;
+ return -EINPROGRESS;
}
static void tb_dp_dprx_stop(struct tb_tunnel *tunnel)
{
+ struct tb *tb = tunnel->tb;
+
if (tunnel->dprx_started) {
tunnel->dprx_started = false;
tunnel->dprx_canceled = true;
- if (cancel_delayed_work(&tunnel->dprx_work))
+ if (cancel_delayed_work(&tunnel->dprx_work)) {
tb_tunnel_put(tunnel);
+ tb_domain_put(tb);
+ }
}
}
@@ -1582,20 +1590,28 @@ static void tb_dp_dump(struct tb_tunnel *tunnel)
* @tb: Pointer to the domain structure
* @in: DP in adapter
* @alloc_hopid: Allocate HopIDs from visited ports
+ * @callback: Callback that is called when the DP tunnel is fully
+ * activated (or there is an error)
+ * @callback_data: Data for @callback
*
* If @in adapter is active, follows the tunnel to the DP out adapter
* and back. Returns the discovered tunnel or %NULL if there was no
- * tunnel.
+ * tunnel. See tb_tunnel_alloc_dp() for @callback.
*
* Return: Pointer to &struct tb_tunnel or %NULL if no tunnel found.
*/
struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in,
- bool alloc_hopid)
+ bool alloc_hopid,
+ void (*callback)(struct tb_tunnel *, void *),
+ void *callback_data)
{
struct tb_tunnel *tunnel;
struct tb_port *port;
struct tb_path *path;
+ if (WARN_ON(!callback))
+ return NULL;
+
if (!tb_dp_port_is_enabled(in))
return NULL;
@@ -1611,6 +1627,9 @@ struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in,
tunnel->alloc_bandwidth = tb_dp_alloc_bandwidth;
tunnel->consumed_bandwidth = tb_dp_consumed_bandwidth;
tunnel->src_port = in;
+ tunnel->callback = callback;
+ tunnel->callback_data = callback_data;
+ INIT_DELAYED_WORK(&tunnel->dprx_work, tb_dp_dprx_work);
path = tb_path_discover(in, TB_DP_VIDEO_HOPID, NULL, -1,
&tunnel->dst_port, "Video", alloc_hopid);
@@ -1656,6 +1675,7 @@ struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in,
tb_dp_dump(tunnel);
+ tb_tunnel_set_active(tunnel, true);
tb_tunnel_dbg(tunnel, "discovered\n");
return tunnel;
@@ -1677,16 +1697,16 @@ err_free:
* %0 if no available bandwidth.
* @max_down: Maximum available downstream bandwidth for the DP tunnel.
* %0 if no available bandwidth.
- * @callback: Optional callback that is called when the DP tunnel is
- * fully activated (or there is an error)
- * @callback_data: Optional data for @callback
+ * @callback: Callback that is called when the DP tunnel is fully
+ * activated (or there is an error)
+ * @callback_data: Data for @callback
*
* Allocates a tunnel between @in and @out that is capable of tunneling
- * Display Port traffic. If @callback is not %NULL it will be called
- * after tb_tunnel_activate() once the tunnel has been fully activated.
- * It can call tb_tunnel_is_active() to check if activation was
- * successful (or if it returns %false there was some sort of issue).
- * The @callback is called without @tb->lock held.
+ * Display Port traffic. The @callback is called after tb_tunnel_activate()
+ * once the tunnel has been fully activated. It can call
+ * tb_tunnel_is_active() to check if activation was successful (or if it
+ * returns %false there was some sort of issue). The @callback is called
+ * without @tb->lock held.
*
* Return: Pointer to @struct tb_tunnel or %NULL in case of failure.
*/
@@ -1701,7 +1721,7 @@ struct tb_tunnel *tb_tunnel_alloc_dp(struct tb *tb, struct tb_port *in,
struct tb_path *path;
bool pm_support;
- if (WARN_ON(!in->cap_adap || !out->cap_adap))
+ if (WARN_ON(!in->cap_adap || !out->cap_adap || !callback))
return NULL;
tunnel = tb_tunnel_alloc(tb, 3, TB_TUNNEL_DP);
@@ -2288,6 +2308,7 @@ struct tb_tunnel *tb_tunnel_discover_usb3(struct tb *tb, struct tb_port *down,
tb_usb3_reclaim_available_bandwidth;
}
+ tb_tunnel_set_active(tunnel, true);
tb_tunnel_dbg(tunnel, "discovered\n");
return tunnel;
diff --git a/drivers/thunderbolt/tunnel.h b/drivers/thunderbolt/tunnel.h
index 4878763a8..7d1d255ab 100644
--- a/drivers/thunderbolt/tunnel.h
+++ b/drivers/thunderbolt/tunnel.h
@@ -66,8 +66,8 @@ enum tb_tunnel_state {
* @dprx_canceled: Was DPRX capabilities read poll canceled
* @dprx_timeout: If set DPRX capabilities read poll work will timeout after this passes
* @dprx_work: Worker that is scheduled to poll completion of DPRX capabilities read
- * @callback: Optional callback called when DP tunnel is fully activated
- * @callback_data: Optional data for @callback
+ * @callback: Callback called when DP tunnel is fully activated
+ * @callback_data: Data for @callback
* @paths: All paths required by the tunnel
*/
struct tb_tunnel {
@@ -117,7 +117,9 @@ struct tb_tunnel *tb_tunnel_alloc_pci(struct tb *tb, struct tb_port *up,
bool tb_tunnel_reserved_pci(struct tb_port *port, int *reserved_up,
int *reserved_down);
struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in,
- bool alloc_hopid);
+ bool alloc_hopid,
+ void (*callback)(struct tb_tunnel *, void *),
+ void *callback_data);
struct tb_tunnel *tb_tunnel_alloc_dp(struct tb *tb, struct tb_port *in,
struct tb_port *out, int link_nr,
int max_up, int max_down,
diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c
index c179bd751..b187a6066 100644
--- a/drivers/thunderbolt/xdomain.c
+++ b/drivers/thunderbolt/xdomain.c
@@ -377,6 +377,10 @@ static int tb_xdp_properties_request(struct tb_ctl *ctl, u64 route,
len += sizeof(res->hdr.xd_hdr) / 4;
len -= sizeof(*res) / 4;
+ if (len > TB_XDP_PROPERTIES_MAX_DATA_LENGTH) {
+ ret = -EINVAL;
+ goto err;
+ }
if (res->offset != req.offset) {
ret = -EINVAL;
@@ -1814,7 +1818,6 @@ static void tb_xdomain_state_work(struct work_struct *work)
tb_xdomain_failed(xd);
} else {
xd->state = XDOMAIN_STATE_ENUMERATED;
- tb_xdomain_queue_properties_changed(xd);
}
break;