diff options
| author | Mark Brown <broonie@kernel.org> | 2026-09-07 14:28:24 +0100 |
|---|---|---|
| committer | Mark Brown <broonie@kernel.org> | 2026-09-07 14:28:24 +0100 |
| commit | 0e98cf90e278cf6959faf07af3b41aff24fd7845 (patch) | |
| tree | 786b3d1e0877bab5427e422d28f91f6fc108cc9e | |
| parent | ba001f006abf3f70ec257414b168655ae5556098 (diff) | |
| parent | 2d8b6e38b86c4cbad9d7bdd8371c696ef34b900c (diff) | |
| download | linux-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.c | 6 | ||||
| -rw-r--r-- | drivers/thunderbolt/dma_test.c | 10 | ||||
| -rw-r--r-- | drivers/thunderbolt/eeprom.c | 23 | ||||
| -rw-r--r-- | drivers/thunderbolt/nhi.c | 58 | ||||
| -rw-r--r-- | drivers/thunderbolt/path.c | 2 | ||||
| -rw-r--r-- | drivers/thunderbolt/quirks.c | 18 | ||||
| -rw-r--r-- | drivers/thunderbolt/switch.c | 4 | ||||
| -rw-r--r-- | drivers/thunderbolt/tb.h | 3 | ||||
| -rw-r--r-- | include/linux/thunderbolt.h | 42 |
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 */ |
