summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Brown <broonie@kernel.org>2026-09-07 14:28:24 +0100
committerMark Brown <broonie@kernel.org>2026-09-07 14:28:24 +0100
commit0e98cf90e278cf6959faf07af3b41aff24fd7845 (patch)
tree786b3d1e0877bab5427e422d28f91f6fc108cc9e
parentba001f006abf3f70ec257414b168655ae5556098 (diff)
parent2d8b6e38b86c4cbad9d7bdd8371c696ef34b900c (diff)
downloadlinux-next-0e98cf90e278cf6959faf07af3b41aff24fd7845.tar.gz
linux-next-0e98cf90e278cf6959faf07af3b41aff24fd7845.zip
Merge branch 'next' of https://git.kernel.org/pub/scm/linux/kernel/git/westeri/thunderbolt.git
-rw-r--r--drivers/net/thunderbolt/main.c6
-rw-r--r--drivers/thunderbolt/dma_test.c10
-rw-r--r--drivers/thunderbolt/eeprom.c23
-rw-r--r--drivers/thunderbolt/nhi.c58
-rw-r--r--drivers/thunderbolt/path.c2
-rw-r--r--drivers/thunderbolt/quirks.c18
-rw-r--r--drivers/thunderbolt/switch.c4
-rw-r--r--drivers/thunderbolt/tb.h3
-rw-r--r--include/linux/thunderbolt.h42
9 files changed, 140 insertions, 26 deletions
diff --git a/drivers/net/thunderbolt/main.c b/drivers/net/thunderbolt/main.c
index d9fb587a62c5..cf51b9c39f4e 100644
--- a/drivers/net/thunderbolt/main.c
+++ b/drivers/net/thunderbolt/main.c
@@ -540,11 +540,12 @@ static int tbnet_alloc_rx_buffers(struct tbnet *net, unsigned int nbuffers)
trace_tbnet_alloc_rx_frame(index, tf->page, dma_addr,
DMA_FROM_DEVICE);
- tb_ring_rx(ring->ring, &tf->frame);
+ tb_ring_rx_more(ring->ring, &tf->frame);
ring->prod++;
}
+ tb_ring_notify(ring->ring);
return 0;
err_free:
@@ -1243,7 +1244,8 @@ static netdev_tx_t tbnet_start_xmit(struct sk_buff *skb,
goto err_drop;
for (i = 0; i < frame_index + 1; i++)
- tb_ring_tx(net->tx_ring.ring, &frames[i]->frame);
+ tb_ring_tx_more(net->tx_ring.ring, &frames[i]->frame);
+ tb_ring_notify(net->tx_ring.ring);
if (net->svc->prtcstns & TBNET_MATCH_FRAGS_ID)
atomic_inc(&net->frame_id);
diff --git a/drivers/thunderbolt/dma_test.c b/drivers/thunderbolt/dma_test.c
index 519c67678b08..bcecb0edcb81 100644
--- a/drivers/thunderbolt/dma_test.c
+++ b/drivers/thunderbolt/dma_test.c
@@ -215,11 +215,6 @@ static void dma_test_stop_rings(struct dma_test *dt)
{
int ret;
- if (dt->rx_ring)
- tb_ring_stop(dt->rx_ring);
- if (dt->tx_ring)
- tb_ring_stop(dt->tx_ring);
-
ret = tb_xdomain_disable_paths(dt->xd, dt->tx_hopid,
dt->tx_ring ? dt->tx_ring->hop : -1,
dt->rx_hopid,
@@ -227,6 +222,11 @@ static void dma_test_stop_rings(struct dma_test *dt)
if (ret)
dev_warn(&dt->svc->dev, "failed to disable DMA paths\n");
+ if (dt->rx_ring)
+ tb_ring_stop(dt->rx_ring);
+ if (dt->tx_ring)
+ tb_ring_stop(dt->tx_ring);
+
dma_test_free_rings(dt);
}
diff --git a/drivers/thunderbolt/eeprom.c b/drivers/thunderbolt/eeprom.c
index 2a13fa6888ba..2acfe83928b6 100644
--- a/drivers/thunderbolt/eeprom.c
+++ b/drivers/thunderbolt/eeprom.c
@@ -324,7 +324,7 @@ int tb_drom_read_uid_only(struct tb_switch *sw, u64 *uid)
}
static int tb_drom_parse_entry_generic(struct tb_switch *sw,
- struct tb_drom_entry_header *header)
+ const struct tb_drom_entry_header *header)
{
const struct tb_drom_entry_generic *entry =
(const struct tb_drom_entry_generic *)header;
@@ -360,7 +360,7 @@ static int tb_drom_parse_entry_generic(struct tb_switch *sw,
}
static int tb_drom_parse_entry_port(struct tb_switch *sw,
- struct tb_drom_entry_header *header)
+ const struct tb_drom_entry_header *header)
{
struct tb_port *port;
int res;
@@ -386,11 +386,13 @@ static int tb_drom_parse_entry_port(struct tb_switch *sw,
type &= 0xffffff;
if (type == TB_TYPE_PORT) {
- struct tb_drom_entry_port *entry = (void *) header;
+ const struct tb_drom_entry_port *entry =
+ (const struct tb_drom_entry_port *)header;
+
if (header->len != sizeof(*entry)) {
tb_sw_warn(sw,
"port entry has size %#x (expected %#zx)\n",
- header->len, sizeof(struct tb_drom_entry_port));
+ header->len, sizeof(*entry));
return -EIO;
}
port->link_nr = entry->link_nr;
@@ -421,9 +423,16 @@ static int tb_drom_parse_entries(struct tb_switch *sw, size_t header_size)
int res;
while (pos < drom_size) {
- struct tb_drom_entry_header *entry = (void *) (sw->drom + pos);
- if (pos + 1 == drom_size || pos + entry->len > drom_size
- || !entry->len) {
+ const struct tb_drom_entry_header *entry;
+
+ if (drom_size - pos < sizeof(*entry)) {
+ tb_sw_warn(sw, "DROM buffer overrun\n");
+ return -EIO;
+ }
+
+ entry = (const struct tb_drom_entry_header *)(sw->drom + pos);
+ if (entry->len < sizeof(*entry) ||
+ entry->len > drom_size - pos) {
tb_sw_warn(sw, "DROM buffer overrun\n");
return -EIO;
}
diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c
index d4d1efa2afa0..c74479c4868d 100644
--- a/drivers/thunderbolt/nhi.c
+++ b/drivers/thunderbolt/nhi.c
@@ -227,12 +227,37 @@ static bool ring_empty(struct tb_ring *ring)
return ring->head == ring->tail;
}
+static void __ring_notify(struct tb_ring *ring)
+{
+ lockdep_assert_held(&ring->lock);
+
+ if (ring->notify_pending) {
+ /*
+ * The doorbell carries the absolute index of the head
+ * so a single write covers all the descriptors posted
+ * since the previous one.
+ */
+ if (ring->is_tx)
+ ring_iowrite_prod(ring, ring->head);
+ else
+ ring_iowrite_cons(ring, ring->head);
+ }
+ ring->notify_pending = false;
+}
+
/*
* ring_write_descriptors() - post frames from ring->queue to the controller
+ * @ring: Ring to post the frames to
+ * @notify: Notify the controller about the posted descriptors
+ *
+ * Unless @notify is %true the controller is not notified about the posted
+ * descriptors and the caller is expected to call tb_ring_notify() once it
+ * is done queuing frames. This allows batching of frames before
+ * updating the producer/consumer indices.
*
* ring->lock is held.
*/
-static void ring_write_descriptors(struct tb_ring *ring)
+static void ring_write_descriptors(struct tb_ring *ring, bool notify)
{
struct ring_frame *frame, *n;
struct ring_desc *descriptor;
@@ -256,11 +281,11 @@ static void ring_write_descriptors(struct tb_ring *ring)
descriptor->sof = frame->sof;
}
ring->head = (ring->head + 1) % ring->size;
- if (ring->is_tx)
- ring_iowrite_prod(ring, ring->head);
- else
- ring_iowrite_cons(ring, ring->head);
+ ring->notify_pending = true;
}
+
+ if (notify)
+ __ring_notify(ring);
}
/*
@@ -305,7 +330,7 @@ static void ring_work(struct work_struct *work)
}
ring->tail = (ring->tail + 1) % ring->size;
}
- ring_write_descriptors(ring);
+ ring_write_descriptors(ring, true);
invoke_callback:
/* allow callbacks to schedule new work */
@@ -324,7 +349,7 @@ invoke_callback:
wake_up(&ring->wait);
}
-int __tb_ring_enqueue(struct tb_ring *ring, struct ring_frame *frame)
+int __tb_ring_enqueue(struct tb_ring *ring, struct ring_frame *frame, bool more)
{
unsigned long flags;
int ret = 0;
@@ -332,7 +357,7 @@ int __tb_ring_enqueue(struct tb_ring *ring, struct ring_frame *frame)
spin_lock_irqsave(&ring->lock, flags);
if (ring->running) {
list_add_tail(&frame->list, &ring->queue);
- ring_write_descriptors(ring);
+ ring_write_descriptors(ring, !more);
} else {
ret = -ESHUTDOWN;
}
@@ -342,6 +367,22 @@ int __tb_ring_enqueue(struct tb_ring *ring, struct ring_frame *frame)
EXPORT_SYMBOL_GPL(__tb_ring_enqueue);
/**
+ * tb_ring_notify() - Notify the controller about the queued frames
+ * @ring: Ring to notify
+ *
+ * Notifies the controller about frames that were enqueued using
+ * tb_ring_tx_more() or tb_ring_rx_more(). Does nothing if there are no
+ * such frames pending.
+ */
+void tb_ring_notify(struct tb_ring *ring)
+{
+ guard(spinlock_irqsave)(&ring->lock);
+ if (ring->running)
+ __ring_notify(ring);
+}
+EXPORT_SYMBOL_GPL(tb_ring_notify);
+
+/**
* tb_ring_poll() - Poll one completed frame from the ring
* @ring: Ring to poll
*
@@ -788,6 +829,7 @@ void tb_ring_stop(struct tb_ring *ring)
ring_iowrite32desc(ring, 0, 12);
ring->head = 0;
ring->tail = 0;
+ ring->notify_pending = false;
ring->running = false;
err:
diff --git a/drivers/thunderbolt/path.c b/drivers/thunderbolt/path.c
index b2c322e76b8a..05249eed3f64 100644
--- a/drivers/thunderbolt/path.c
+++ b/drivers/thunderbolt/path.c
@@ -398,7 +398,7 @@ static int __tb_path_deactivate_hop(struct tb_port *port, int hop_index,
return ret;
/* Wait until it is drained */
- timeout = ktime_add_ms(ktime_get(), 500);
+ timeout = ktime_add_ms(ktime_get(), port->pp_timeout_msec);
do {
ret = tb_port_read(port, &hop, TB_CFG_HOPS, 2 * hop_index, 2);
if (ret)
diff --git a/drivers/thunderbolt/quirks.c b/drivers/thunderbolt/quirks.c
index 9f7914ac2f48..f41c5b7ef114 100644
--- a/drivers/thunderbolt/quirks.c
+++ b/drivers/thunderbolt/quirks.c
@@ -52,6 +52,19 @@ static void quirk_block_rpm_in_redrive(struct tb_switch *sw)
tb_sw_dbg(sw, "preventing runtime PM in DP redrive mode\n");
}
+static void quirk_stuck_pending(struct tb_switch *sw)
+{
+ struct tb_port *port;
+
+ tb_switch_for_each_port(sw, port) {
+ if (!tb_port_is_nhi(port))
+ continue;
+
+ port->pp_timeout_msec = 0;
+ tb_port_dbg(port, "pending bit does not clear, reading it once\n");
+ }
+}
+
struct tb_quirk {
u16 hw_vendor_id;
u16 hw_device_id;
@@ -114,6 +127,11 @@ static const struct tb_quirk tb_quirks[] = {
{ 0x0438, 0x0209, 0x0000, 0x0000, quirk_clx_disable },
{ 0x0438, 0x020a, 0x0000, 0x0000, quirk_clx_disable },
{ 0x0438, 0x020b, 0x0000, 0x0000, quirk_clx_disable },
+ /*
+ * ASMedia ASM4242 never clears the Pending Packets bit of its host
+ * interface adapter.
+ */
+ { 0x174c, 0x2428, 0x0000, 0x0000, quirk_stuck_pending },
};
/**
diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c
index cf571a7c98d0..d22d8db8f890 100644
--- a/drivers/thunderbolt/switch.c
+++ b/drivers/thunderbolt/switch.c
@@ -19,6 +19,9 @@
#include "tb.h"
+/* How long a hop is given to drain when a path is deactivated */
+#define TB_PORT_PENDING_TIMEOUT 500 /* ms */
+
/* Switch NVM support */
struct nvm_auth_status {
@@ -712,6 +715,7 @@ static int tb_init_port(struct tb_port *port)
int cap;
INIT_LIST_HEAD(&port->list);
+ port->pp_timeout_msec = TB_PORT_PENDING_TIMEOUT;
/* Control adapter does not have configuration space */
if (!port->port)
diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h
index 4373336d9425..c112954ce3fd 100644
--- a/drivers/thunderbolt/tb.h
+++ b/drivers/thunderbolt/tb.h
@@ -273,6 +273,8 @@ struct tb_bandwidth_group {
* @max_bw: Maximum possible bandwidth through this adapter if set to
* non-zero.
* @redrive: For DP IN, if true the adapter is in redrive mode.
+ * @pp_timeout_msec: How long a hop of this adapter is given to drain when a
+ * path is deactivated. %0 means a single read.
*
* In USB4 terminology this structure represents an adapter (protocol or
* lane adapter).
@@ -302,6 +304,7 @@ struct tb_port {
struct list_head group_list;
unsigned int max_bw;
bool redrive;
+ unsigned int pp_timeout_msec;
};
/**
diff --git a/include/linux/thunderbolt.h b/include/linux/thunderbolt.h
index b62dfa52b149..7fb9e7e1aae4 100644
--- a/include/linux/thunderbolt.h
+++ b/include/linux/thunderbolt.h
@@ -553,6 +553,8 @@ struct tb_nhi {
* @work: Interrupt work structure
* @is_tx: Is the ring Tx or Rx
* @running: Is the ring running
+ * @notify_pending: Controller has not been notified about the posted
+ * descriptors yet
* @irq: MSI-X irq number if the ring uses MSI-X. %0 otherwise.
* @vector: MSI-X vector number the ring uses (only set if @irq is > 0)
* @flags: Ring specific flags
@@ -582,6 +584,7 @@ struct tb_ring {
struct work_struct work;
bool is_tx:1;
bool running:1;
+ bool notify_pending:1;
int irq;
u8 vector;
unsigned int flags;
@@ -672,7 +675,8 @@ bool tb_ring_flush(struct tb_ring *ring, unsigned int timeout_msec);
void tb_ring_stop(struct tb_ring *ring);
void tb_ring_free(struct tb_ring *ring);
-int __tb_ring_enqueue(struct tb_ring *ring, struct ring_frame *frame);
+int __tb_ring_enqueue(struct tb_ring *ring, struct ring_frame *frame, bool more);
+void tb_ring_notify(struct tb_ring *ring);
/**
* tb_ring_rx() - enqueue a frame on an RX ring
@@ -693,7 +697,24 @@ int __tb_ring_enqueue(struct tb_ring *ring, struct ring_frame *frame);
static inline int tb_ring_rx(struct tb_ring *ring, struct ring_frame *frame)
{
WARN_ON(ring->is_tx);
- return __tb_ring_enqueue(ring, frame);
+ return __tb_ring_enqueue(ring, frame, false);
+}
+
+/**
+ * tb_ring_rx_more() - enqueue a frame on an RX ring without notifying
+ * @ring: Ring to enqueue the frame
+ * @frame: Frame to enqueue
+ *
+ * Same as tb_ring_rx() but does not notify the controller about the
+ * enqueued frame. The caller must call tb_ring_notify() once it is done
+ * enqueuing frames.
+ *
+ * Return: %-ESHUTDOWN if tb_ring_stop() has been called, %0 otherwise.
+ */
+static inline int tb_ring_rx_more(struct tb_ring *ring, struct ring_frame *frame)
+{
+ WARN_ON(ring->is_tx);
+ return __tb_ring_enqueue(ring, frame, true);
}
/**
@@ -714,7 +735,22 @@ static inline int tb_ring_rx(struct tb_ring *ring, struct ring_frame *frame)
static inline int tb_ring_tx(struct tb_ring *ring, struct ring_frame *frame)
{
WARN_ON(!ring->is_tx);
- return __tb_ring_enqueue(ring, frame);
+ return __tb_ring_enqueue(ring, frame, false);
+}
+
+/**
+ * tb_ring_tx_more() - enqueue a frame on a TX ring without notifying
+ * @ring: Ring to enqueue the frame
+ * @frame: Frame to enqueue
+ *
+ * Same as tb_ring_rx_more() but for TX ring.
+ *
+ * Return: %-ESHUTDOWN if tb_ring_stop() has been called, %0 otherwise.
+ */
+static inline int tb_ring_tx_more(struct tb_ring *ring, struct ring_frame *frame)
+{
+ WARN_ON(!ring->is_tx);
+ return __tb_ring_enqueue(ring, frame, true);
}
/* Used only when the ring is in polling mode */