summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Brown <broonie@kernel.org>2026-09-17 20:19:39 +0100
committerMark Brown <broonie@kernel.org>2026-09-17 20:19:39 +0100
commitf6ab341c3eaa4e6000ba1989301ebf030d0e7640 (patch)
tree14c72dff5d8168a8d253316a91a0aea19bf910d9
parent0caee94fe0061253f6071e45e0896cad59743ab4 (diff)
parent5da91c3694b8060b651aab5d95b2e4729c6e2b56 (diff)
downloadlinux-next-f6ab341c3eaa4e6000ba1989301ebf030d0e7640.tar.gz
linux-next-f6ab341c3eaa4e6000ba1989301ebf030d0e7640.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.c222
-rw-r--r--drivers/thunderbolt/nhi.h2
-rw-r--r--drivers/thunderbolt/path.c2
-rw-r--r--drivers/thunderbolt/pci.c113
-rw-r--r--drivers/thunderbolt/quirks.c18
-rw-r--r--drivers/thunderbolt/stream.c131
-rw-r--r--drivers/thunderbolt/switch.c4
-rw-r--r--drivers/thunderbolt/tb.c78
-rw-r--r--drivers/thunderbolt/tb.h3
-rw-r--r--include/linux/thunderbolt.h47
13 files changed, 469 insertions, 190 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 be18f7b6595a..e99a3fcc4a29 100644
--- a/drivers/thunderbolt/nhi.c
+++ b/drivers/thunderbolt/nhi.c
@@ -41,6 +41,7 @@ static bool host_reset = true;
module_param(host_reset, bool, 0444);
MODULE_PARM_DESC(host_reset, "reset USB4 host router (default: true)");
+/* Returns absolute bit number of the ring in the interrupt registers */
static int ring_interrupt_index(const struct tb_ring *ring)
{
int bit = ring->hop;
@@ -49,24 +50,41 @@ static int ring_interrupt_index(const struct tb_ring *ring)
return bit;
}
-static void nhi_mask_interrupt(struct tb_nhi *nhi, int mask, int ring)
+static void nhi_mask_interrupt(struct tb_nhi *nhi, u32 mask, int reg_index)
{
- if (nhi->quirks & QUIRK_AUTO_CLEAR_INT) {
- u32 val;
+ int offset = reg_index * 4;
+ u32 val;
- val = ioread32(nhi->iobase + REG_RING_INTERRUPT_BASE + ring);
- iowrite32(val & ~mask, nhi->iobase + REG_RING_INTERRUPT_BASE + ring);
- } else {
- iowrite32(mask, nhi->iobase + REG_RING_INTERRUPT_MASK_CLEAR_BASE + ring);
- }
+ /* Use shadow copy instead of reading the register */
+ val = nhi->interrupt_mask[reg_index] & ~mask;
+ nhi->interrupt_mask[reg_index] = val;
+
+ if (nhi->quirks & QUIRK_AUTO_CLEAR_INT)
+ iowrite32(val, nhi->iobase + REG_RING_INTERRUPT_BASE + offset);
+ else
+ iowrite32(mask, nhi->iobase + REG_RING_INTERRUPT_MASK_CLEAR_BASE + offset);
}
-static void nhi_clear_interrupt(struct tb_nhi *nhi, int ring)
+static void nhi_unmask_interrupt(struct tb_nhi *nhi, u32 mask, int reg_index)
{
+ int offset = reg_index * 4;
+ u32 val;
+
+ /* Use shadow copy instead of reading the register */
+ val = nhi->interrupt_mask[reg_index] | mask;
+ nhi->interrupt_mask[reg_index] = val;
+
+ iowrite32(val, nhi->iobase + REG_RING_INTERRUPT_BASE + offset);
+}
+
+static void nhi_clear_interrupt(struct tb_nhi *nhi, int reg_index)
+{
+ int offset = reg_index * 4;
+
if (nhi->quirks & QUIRK_AUTO_CLEAR_INT)
- ioread32(nhi->iobase + REG_RING_NOTIFY_BASE + ring);
+ ioread32(nhi->iobase + REG_RING_NOTIFY_BASE + offset);
else
- iowrite32(~0, nhi->iobase + REG_RING_INT_CLEAR + ring);
+ iowrite32(~0, nhi->iobase + REG_RING_INT_CLEAR + offset);
}
/*
@@ -76,22 +94,17 @@ static void nhi_clear_interrupt(struct tb_nhi *nhi, int ring)
*/
static void ring_interrupt_active(struct tb_ring *ring, bool active)
{
- int index = ring_interrupt_index(ring) / 32 * 4;
- int reg = REG_RING_INTERRUPT_BASE + index;
- int interrupt_bit = ring_interrupt_index(ring) & 31;
- int mask = 1 << interrupt_bit;
+ int interrupt_index = ring_interrupt_index(ring);
+ int reg_index = interrupt_index / 32;
+ int reg = REG_RING_INTERRUPT_BASE + reg_index * 4;
+ int interrupt_bit = interrupt_index % 32;
+ u32 mask = BIT(interrupt_bit);
u32 old, new;
if (ring->irq > 0) {
u32 step, shift, ivr, misc, itr;
void __iomem *ivr_base;
int auto_clear_bit;
- int index;
-
- if (ring->is_tx)
- index = ring->hop;
- else
- index = ring->hop + ring->nhi->hop_count;
/*
* Intel routers support a bit that isn't part of
@@ -114,8 +127,8 @@ static void ring_interrupt_active(struct tb_ring *ring, bool active)
ring->nhi->iobase + REG_DMA_MISC);
ivr_base = ring->nhi->iobase + REG_INT_VEC_ALLOC_BASE;
- step = index / REG_INT_VEC_ALLOC_REGS * REG_INT_VEC_ALLOC_BITS;
- shift = index % REG_INT_VEC_ALLOC_REGS * REG_INT_VEC_ALLOC_BITS;
+ step = interrupt_index / REG_INT_VEC_ALLOC_REGS * REG_INT_VEC_ALLOC_BITS;
+ shift = interrupt_index % REG_INT_VEC_ALLOC_REGS * REG_INT_VEC_ALLOC_BITS;
ivr = ioread32(ivr_base + step);
ivr &= ~(REG_INT_VEC_ALLOC_MASK << shift);
if (active)
@@ -129,7 +142,7 @@ static void ring_interrupt_active(struct tb_ring *ring, bool active)
ring->vector * 4);
}
- old = ioread32(ring->nhi->iobase + reg);
+ old = ring->nhi->interrupt_mask[reg_index];
if (active)
new = old | mask;
else
@@ -139,15 +152,24 @@ static void ring_interrupt_active(struct tb_ring *ring, bool active)
"%s interrupt at register %#x bit %d (%#x -> %#x)\n",
active ? "enabling" : "disabling", reg, interrupt_bit, old, new);
- if (new == old)
- dev_WARN(ring->nhi->dev, "interrupt for %s %d is already %s\n",
- RING_TYPE(ring), ring->hop,
- str_enabled_disabled(active));
+ if (new == old) {
+ /*
+ * Rings that are polled mask the interrupt while the
+ * completions are being advanced (see __ring_interrupt())
+ * so for those it can already be disabled by the time
+ * the ring is stopped.
+ */
+ if (active || !ring->start_poll)
+ dev_WARN(ring->nhi->dev,
+ "interrupt for %s %d is already %s\n",
+ RING_TYPE(ring), ring->hop,
+ str_enabled_disabled(active));
+ }
if (active)
- iowrite32(new, ring->nhi->iobase + reg);
+ nhi_unmask_interrupt(ring->nhi, mask, reg_index);
else
- nhi_mask_interrupt(ring->nhi, mask, index);
+ nhi_mask_interrupt(ring->nhi, mask, reg_index);
}
/*
@@ -160,11 +182,11 @@ void nhi_disable_interrupts(struct tb_nhi *nhi)
int i = 0;
/* disable interrupts */
for (i = 0; i < RING_INTERRUPT_REG_COUNT(nhi); i++)
- nhi_mask_interrupt(nhi, ~0, 4 * i);
+ nhi_mask_interrupt(nhi, ~0, i);
/* clear interrupt status bits */
for (i = 0; i < RING_NOTIFY_REG_COUNT(nhi); i++)
- nhi_clear_interrupt(nhi, 4 * i);
+ nhi_clear_interrupt(nhi, i);
}
/* ring helper methods */
@@ -227,12 +249,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 +303,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 +352,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 +371,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 +379,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 +389,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
*
@@ -377,6 +440,13 @@ struct ring_frame *tb_ring_poll(struct tb_ring *ring)
}
ring->tail = (ring->tail + 1) % ring->size;
+
+ /*
+ * There is one more slot available so we can push next
+ * descriptor to the ring. This is needed when the ring
+ * is created with %RING_FLAG_NO_INTERRUPT.
+ */
+ ring_write_descriptors(ring, true);
}
unlock:
@@ -385,19 +455,35 @@ unlock:
}
EXPORT_SYMBOL_GPL(tb_ring_poll);
+/**
+ * tb_ring_poll_pending() - Does the ring have completed frame
+ * @ring: Ring to check
+ *
+ * Can be used to check whether there is a completed frame in the ring
+ * that next call to tb_ring_poll() returns.
+ *
+ * Return: %true if a completed frame is waiting, %false otherwise.
+ */
+bool tb_ring_poll_pending(struct tb_ring *ring)
+{
+ guard(spinlock_irqsave)(&ring->lock);
+
+ if (!ring->running || ring_empty(ring))
+ return false;
+ return !!(ring->descriptors[ring->tail].flags & RING_DESC_COMPLETED);
+}
+EXPORT_SYMBOL_GPL(tb_ring_poll_pending);
+
static void __ring_interrupt_mask(struct tb_ring *ring, bool mask)
{
- int idx = ring_interrupt_index(ring);
- int reg = REG_RING_INTERRUPT_BASE + idx / 32 * 4;
- int bit = idx % 32;
- u32 val;
+ int interrupt_index = ring_interrupt_index(ring);
+ int reg_index = interrupt_index / 32;
+ int interrupt_bit = interrupt_index % 32;
- val = ioread32(ring->nhi->iobase + reg);
if (mask)
- val &= ~BIT(bit);
+ nhi_mask_interrupt(ring->nhi, BIT(interrupt_bit), reg_index);
else
- val |= BIT(bit);
- iowrite32(val, ring->nhi->iobase + reg);
+ nhi_unmask_interrupt(ring->nhi, BIT(interrupt_bit), reg_index);
}
/* Both @nhi->lock and @ring->lock should be held */
@@ -788,6 +874,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:
@@ -1182,24 +1269,40 @@ static void nhi_reset(struct tb_nhi *nhi)
static struct tb *nhi_select_cm(struct tb_nhi *nhi)
{
+ bool linked = false;
struct tb *tb;
/*
* USB4 case is simple. If we got control of any of the
* capabilities, we use software CM.
*/
- if (tb_acpi_is_native())
- return tb_probe(nhi);
+ if (!tb_acpi_is_native()) {
+ /*
+ * Either firmware based CM is running (we did not get
+ * control from the firmware) or this is pre-USB4 PC so
+ * try first firmware CM and then fallback to software CM.
+ */
+ tb = icm_probe(nhi);
+ if (tb)
+ return tb;
+ }
- /*
- * Either firmware based CM is running (we did not get control
- * from the firmware) or this is pre-USB4 PC so try first
- * firmware CM and then fallback to software CM.
- */
- tb = icm_probe(nhi);
+ tb = tb_probe(nhi);
if (!tb)
- tb = tb_probe(nhi);
+ return NULL;
+ if (nhi->ops->add_links)
+ linked = nhi->ops->add_links(nhi);
+ if (!linked)
+ linked = tb_acpi_add_links(nhi);
+ /*
+ * Device links are needed to make sure we establish tunnels
+ * before protocol stacks are resumed so complain here if we
+ * found them missing.
+ */
+ if (!linked)
+ dev_warn(nhi->dev,
+ "device links to tunneled native ports are missing!\n");
return tb;
}
@@ -1222,7 +1325,10 @@ int nhi_probe(struct tb_nhi *nhi)
sizeof(*nhi->tx_rings), GFP_KERNEL);
nhi->rx_rings = devm_kcalloc(dev, nhi->hop_count,
sizeof(*nhi->rx_rings), GFP_KERNEL);
- if (!nhi->tx_rings || !nhi->rx_rings)
+ nhi->interrupt_mask = devm_kcalloc(dev, RING_INTERRUPT_REG_COUNT(nhi),
+ sizeof(*nhi->interrupt_mask),
+ GFP_KERNEL);
+ if (!nhi->tx_rings || !nhi->rx_rings || !nhi->interrupt_mask)
return -ENOMEM;
nhi_reset(nhi);
diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h
index d488eadadfce..88af9ab9fde7 100644
--- a/drivers/thunderbolt/nhi.h
+++ b/drivers/thunderbolt/nhi.h
@@ -41,6 +41,7 @@ extern const struct dev_pm_ops nhi_pm_ops;
/**
* struct tb_nhi_ops - NHI specific optional operations
* @init: NHI specific initialization
+ * @add_links: Setup device links for tunneling native protocols
* @suspend_noirq: NHI specific suspend_noirq hook
* @resume_noirq: NHI specific resume_noirq hook
* @runtime_suspend: NHI specific runtime_suspend hook
@@ -55,6 +56,7 @@ extern const struct dev_pm_ops nhi_pm_ops;
*/
struct tb_nhi_ops {
int (*init)(struct tb_nhi *nhi);
+ bool (*add_links)(struct tb_nhi *nhi);
int (*suspend_noirq)(struct tb_nhi *nhi, bool wakeup);
int (*resume_noirq)(struct tb_nhi *nhi);
int (*runtime_suspend)(struct tb_nhi *nhi);
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/pci.c b/drivers/thunderbolt/pci.c
index 8462ccb59b7e..4408229d8ac7 100644
--- a/drivers/thunderbolt/pci.c
+++ b/drivers/thunderbolt/pci.c
@@ -18,6 +18,7 @@
#include <linux/property.h>
#include <linux/string_helpers.h>
#include <linux/suspend.h>
+#include <linux/platform_data/x86/apple.h>
#include "nhi.h"
#include "nhi_regs.h"
@@ -108,6 +109,82 @@ static void nhi_pci_check_iommu(struct tb_nhi_pci *nhi_pci)
str_enabled_disabled(port_ok));
}
+static bool add_link(struct tb_nhi *nhi, struct pci_dev *pdev)
+{
+ const struct device_link *link;
+
+ link = device_link_add(&pdev->dev, nhi->dev,
+ DL_FLAG_AUTOREMOVE_SUPPLIER |
+ DL_FLAG_PM_RUNTIME);
+ if (!link) {
+ dev_warn(nhi->dev, "device link creation from %s failed\n",
+ dev_name(&pdev->dev));
+ return false;
+ }
+
+ dev_dbg(nhi->dev, "created link from %s\n", dev_name(&pdev->dev));
+ return true;
+}
+
+/*
+ * During suspend the Thunderbolt controller is reset and all PCIe
+ * tunnels are lost. The NHI driver will try to reestablish all tunnels
+ * during resume. This adds device links between the tunneled PCIe
+ * downstream ports and the NHI so that the device core will make sure
+ * NHI is resumed first before the rest.
+ */
+static bool nhi_pci_add_links(struct tb_nhi *nhi)
+{
+ struct pci_dev *nhi_pdev = to_pci_dev(nhi->dev);
+ struct pci_dev *upstream, *pdev;
+ bool ret;
+
+ if (!x86_apple_machine)
+ return false;
+
+ switch (nhi_pdev->device) {
+ case PCI_DEVICE_ID_INTEL_LIGHT_RIDGE:
+ case PCI_DEVICE_ID_INTEL_CACTUS_RIDGE_4C:
+ case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_NHI:
+ case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_NHI:
+ case PCI_DEVICE_ID_INTEL_TITAN_RIDGE_2C_NHI:
+ case PCI_DEVICE_ID_INTEL_TITAN_RIDGE_4C_NHI:
+ break;
+ default:
+ return false;
+ }
+
+ upstream = pci_upstream_bridge(nhi_pdev);
+ while (upstream) {
+ if (!pci_is_pcie(upstream))
+ return false;
+ if (pci_pcie_type(upstream) == PCI_EXP_TYPE_UPSTREAM)
+ break;
+ upstream = pci_upstream_bridge(upstream);
+ }
+
+ if (!upstream)
+ return false;
+
+ /*
+ * For each hotplug downstream port, create add device link back
+ * to NHI so that PCIe tunnels can be re-established after
+ * sleep.
+ */
+ ret = false;
+ for_each_pci_bridge(pdev, upstream->subordinate) {
+ if (!pci_is_pcie(pdev))
+ continue;
+ if (pci_pcie_type(pdev) != PCI_EXP_TYPE_DOWNSTREAM ||
+ !pdev->is_pciehp)
+ continue;
+
+ ret |= add_link(nhi, pdev);
+ }
+
+ return ret;
+}
+
static int nhi_pci_init_msi(struct tb_nhi *nhi)
{
struct tb_nhi_pci *nhi_pci = nhi_to_pci(nhi);
@@ -251,6 +328,7 @@ static bool nhi_pci_is_present(struct tb_nhi *nhi)
}
static const struct tb_nhi_ops pci_nhi_default_ops = {
+ .add_links = nhi_pci_add_links,
.pre_nvm_auth = nhi_pci_start_dma_port,
.post_nvm_auth = nhi_pci_complete_dma_port,
.request_ring_irq = nhi_pci_ring_request_msix,
@@ -421,6 +499,40 @@ static int icl_nhi_resume(struct tb_nhi *nhi)
return 0;
}
+static bool icl_nhi_add_links(struct tb_nhi *nhi)
+{
+ struct pci_dev *nhi_pdev = to_pci_dev(nhi->dev);
+ struct pci_bus *bus = nhi_pdev->bus;
+ struct pci_dev *pdev;
+ bool ret;
+
+ if (!x86_apple_machine)
+ return false;
+
+ /*
+ * On integrated controllers the tunneled PCIe root ports are
+ * directly under the host bridge.
+ */
+ if (!pci_is_root_bus(bus))
+ return false;
+
+ ret = false;
+ for_each_pci_bridge(pdev, bus) {
+ switch (nhi_pdev->device) {
+ case PCI_DEVICE_ID_INTEL_ICL_NHI0:
+ if (pdev->device == 0x8a1d || pdev->device == 0x8a1f)
+ ret |= add_link(nhi, pdev);
+ break;
+ case PCI_DEVICE_ID_INTEL_ICL_NHI1:
+ if (pdev->device == 0x8a21 || pdev->device == 0x8a23)
+ ret |= add_link(nhi, pdev);
+ break;
+ }
+ }
+
+ return ret;
+}
+
static void icl_nhi_shutdown(struct tb_nhi *nhi)
{
nhi_pci_release_irq(nhi);
@@ -430,6 +542,7 @@ static void icl_nhi_shutdown(struct tb_nhi *nhi)
static const struct tb_nhi_ops icl_nhi_ops = {
.init = icl_nhi_resume,
+ .add_links = icl_nhi_add_links,
.suspend_noirq = icl_nhi_suspend_noirq,
.resume_noirq = icl_nhi_resume,
.runtime_suspend = icl_nhi_suspend,
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/stream.c b/drivers/thunderbolt/stream.c
index 25c259dd0760..e2b4896dfafb 100644
--- a/drivers/thunderbolt/stream.c
+++ b/drivers/thunderbolt/stream.c
@@ -131,6 +131,7 @@ struct tbstream_ring {
* @ring_size: Size of the rings
* @throttling: Interrupt throttling rate in ns
* @busy_poll: Instead of interrupts, busy poll the rings
+ * @rx_pending: Receive ring has completions that need to be advanced
* @users: Number of times @cdev has been opened
* @closed: CLOSE packet was received
* @removed: Userspace removed the ConfigFS group underneath.
@@ -151,6 +152,7 @@ struct tbstream_dev {
unsigned int ring_size;
unsigned int throttling;
bool busy_poll;
+ bool rx_pending;
int users;
bool closed;
bool removed;
@@ -278,6 +280,14 @@ static inline bool tbstream_ring_available(const struct tbstream_ring *ring)
return ring->prod > ring->cons;
}
+static void tbstream_ring_poll(struct tbstream_ring *ring)
+{
+ struct ring_frame *frame;
+
+ while ((frame = tb_ring_poll(ring->ring)))
+ frame->callback(ring->ring, frame, false);
+}
+
static inline struct tb_xdomain *tbstream_dev_xdomain(struct tbstream_dev *sdev)
{
if (sdev->stream)
@@ -538,18 +548,6 @@ tbstream_dev_send_data(struct tbstream_dev *sdev, struct iov_iter *from,
return tb_ring_tx(sdev->tx_ring.ring, &sf->frame);
}
-static void
-tbstream_dev_poll_ring(struct tbstream_dev *sdev, struct tbstream_ring *ring)
-{
- struct ring_frame *frame;
-
- if (!sdev->busy_poll)
- return;
-
- while ((frame = tb_ring_poll(ring->ring)))
- frame->callback(ring->ring, frame, false);
-}
-
static int tbstream_dev_send_close(struct tbstream_dev *sdev)
{
struct tbstream_frame *sf;
@@ -566,7 +564,7 @@ static int tbstream_dev_send_close(struct tbstream_dev *sdev)
do {
if (tbstream_ring_available(&sdev->tx_ring))
break;
- tbstream_dev_poll_ring(sdev, &sdev->tx_ring);
+ tbstream_ring_poll(&sdev->tx_ring);
fsleep(15);
} while (ktime_before(ktime_get(), timeout));
}
@@ -577,16 +575,45 @@ static int tbstream_dev_send_close(struct tbstream_dev *sdev)
return tb_ring_tx(sdev->tx_ring.ring, &sf->frame);
}
+static void tbstream_dev_start_poll(void *data)
+{
+ struct tbstream_dev *sdev = data;
+
+ WRITE_ONCE(sdev->rx_pending, true);
+ wake_up_interruptible_poll(&sdev->wait, EPOLLIN | EPOLLRDNORM);
+}
+
+/* sdev->lock must be held */
+static void tbstream_dev_advance_rx(struct tbstream_dev *sdev)
+{
+ /*
+ * Clear before running the completions so that an interrupt
+ * that arrives while we are doing that is not missed.
+ */
+ WRITE_ONCE(sdev->rx_pending, false);
+ tbstream_ring_poll(&sdev->rx_ring);
+}
+
+/* sdev->lock must be held */
+static void tbstream_dev_complete_rx(struct tbstream_dev *sdev)
+{
+ if (!sdev->busy_poll)
+ tb_ring_poll_complete(sdev->rx_ring.ring);
+}
+
static int tbstream_dev_start(struct tbstream_dev *sdev)
{
struct tb_xdomain *xd = tbstream_dev_xdomain(sdev);
unsigned int flags = RING_FLAG_FRAME | RING_FLAG_E2E;
+ void (*start_poll)(void *) = NULL;
u16 sof_mask, eof_mask;
struct tb_ring *ring;
int ret, e2e_tx_hop;
if (sdev->busy_poll)
flags |= RING_FLAG_NO_INTERRUPT;
+ else
+ start_poll = tbstream_dev_start_poll;
ring = tb_ring_alloc_tx(xd->tb->nhi, -1, sdev->ring_size, flags);
if (!ring)
@@ -602,7 +629,8 @@ static int tbstream_dev_start(struct tbstream_dev *sdev)
eof_mask = BIT(TBSTREAM_DATA) | BIT(TBSTREAM_CLOSE);
ring = tb_ring_alloc_rx(xd->tb->nhi, -1, sdev->ring_size, flags,
- e2e_tx_hop, sof_mask, eof_mask, NULL, NULL);
+ e2e_tx_hop, sof_mask, eof_mask, start_poll,
+ sdev);
if (!ring) {
ret = -ENOMEM;
goto err_free_tx_buffers;
@@ -619,6 +647,8 @@ static int tbstream_dev_start(struct tbstream_dev *sdev)
tb_ring_throttling(sdev->tx_ring.ring, sdev->throttling);
tb_ring_throttling(sdev->rx_ring.ring, sdev->throttling);
+ sdev->rx_pending = false;
+
tb_ring_start(sdev->tx_ring.ring);
tb_ring_start(sdev->rx_ring.ring);
@@ -665,19 +695,16 @@ static void tbstream_dev_stop(struct tbstream_dev *sdev)
do {
if (tbstream_dev_tx_drained(sdev))
break;
- tbstream_dev_poll_ring(sdev, &sdev->tx_ring);
+ tbstream_ring_poll(&sdev->tx_ring);
fsleep(15);
} while (ktime_before(ktime_get(), timeout));
-
- tb_ring_stop(sdev->tx_ring.ring);
- tb_ring_stop(sdev->rx_ring.ring);
} else {
tb_ring_flush(sdev->tx_ring.ring, 500);
- tb_ring_stop(sdev->tx_ring.ring);
- tb_ring_flush(sdev->rx_ring.ring, 500);
- tb_ring_stop(sdev->rx_ring.ring);
}
+ tb_ring_stop(sdev->tx_ring.ring);
+ tb_ring_stop(sdev->rx_ring.ring);
+
xd = tbstream_dev_xdomain(sdev);
if (xd) {
tb_xdomain_disable_paths(xd, sdev->out_hopid,
@@ -707,6 +734,22 @@ static int tbstream_dev_lock(struct tbstream_dev *sdev, bool nowait)
return 0;
}
+/* Must not be called with @sdev->lock held */
+static int tbstream_dev_busy_poll_wait(struct tbstream_dev *sdev,
+ struct tbstream_ring *ring)
+{
+ for (;;) {
+ if (signal_pending(current))
+ return -ERESTARTSYS;
+ if (tb_ring_poll_pending(ring->ring))
+ return 0;
+ if (tbstream_dev_valid(sdev) != 0 ||
+ tbstream_dev_closed(sdev) || tbstream_dev_removed(sdev))
+ return 0;
+ cond_resched();
+ }
+}
+
static ssize_t
tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to)
{
@@ -725,8 +768,8 @@ tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to)
return ret;
for (;;) {
- /* When busy polling, advance any completions manually */
- tbstream_dev_poll_ring(sdev, &sdev->rx_ring);
+ /* Advance RX completions */
+ tbstream_dev_advance_rx(sdev);
ret = tbstream_dev_valid(sdev);
if (ret) {
@@ -742,17 +785,21 @@ tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to)
if (tbstream_ring_available(&sdev->rx_ring))
break;
+ /* Polled all we could. Re-enable the interrupt now. */
+ tbstream_dev_complete_rx(sdev);
mutex_unlock(&sdev->lock);
if (nowait)
return -EAGAIN;
if (sdev->busy_poll) {
- if (signal_pending(current))
- return -ERESTARTSYS;
- cond_resched();
+ ret = tbstream_dev_busy_poll_wait(sdev, &sdev->rx_ring);
+ if (ret)
+ return ret;
} else {
ret = wait_event_interruptible(sdev->wait,
+ READ_ONCE(sdev->rx_pending) ||
+ tb_ring_poll_pending(sdev->rx_ring.ring) ||
tbstream_ring_available(&sdev->rx_ring) ||
tbstream_dev_valid(sdev) != 0 ||
tbstream_dev_closed(sdev) ||
@@ -837,7 +884,9 @@ tbstream_dev_fops_write_iter(struct kiocb *kiocb, struct iov_iter *from)
return ret;
for (;;) {
- tbstream_dev_poll_ring(sdev, &sdev->tx_ring);
+ /* When busy polling, advance any completions manually */
+ if (sdev->busy_poll)
+ tbstream_ring_poll(&sdev->tx_ring);
ret = tbstream_dev_valid(sdev);
if (ret) {
@@ -859,9 +908,9 @@ tbstream_dev_fops_write_iter(struct kiocb *kiocb, struct iov_iter *from)
return -EAGAIN;
if (sdev->busy_poll) {
- if (signal_pending(current))
- return -ERESTARTSYS;
- cond_resched();
+ ret = tbstream_dev_busy_poll_wait(sdev, &sdev->tx_ring);
+ if (ret)
+ return ret;
} else {
ret = wait_event_interruptible(sdev->wait,
tbstream_ring_available(&sdev->tx_ring) ||
@@ -917,14 +966,26 @@ tbstream_dev_fops_poll(struct file *file, struct poll_table_struct *wait)
poll_wait(file, &sdev->wait, wait);
guard(mutex)(&sdev->lock);
- if (tbstream_dev_valid(sdev) != 0) {
- mask |= EPOLLHUP | EPOLLERR;
+ if (tbstream_dev_valid(sdev) != 0)
+ return EPOLLHUP | EPOLLERR;
+
+ /*
+ * The RX completions are only advanced from here and from
+ * read(2) so do that now, otherwise we would never report
+ * anything to be available.
+ */
+ tbstream_dev_advance_rx(sdev);
+
+ if (tbstream_ring_available(&sdev->tx_ring))
+ mask |= EPOLLOUT | EPOLLWRNORM;
+ if (tbstream_ring_available(&sdev->rx_ring)) {
+ mask |= EPOLLIN | EPOLLRDNORM;
} else {
- if (tbstream_ring_available(&sdev->tx_ring))
- mask |= EPOLLOUT | EPOLLWRNORM;
- if (tbstream_ring_available(&sdev->rx_ring))
+ tbstream_dev_complete_rx(sdev);
+ if (tb_ring_poll_pending(sdev->rx_ring.ring))
mask |= EPOLLIN | EPOLLRDNORM;
}
+
return mask;
}
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.c b/drivers/thunderbolt/tb.c
index 4e5d0578fd4b..4da608ccbccb 100644
--- a/drivers/thunderbolt/tb.c
+++ b/drivers/thunderbolt/tb.c
@@ -10,7 +10,6 @@
#include <linux/errno.h>
#include <linux/delay.h>
#include <linux/pm_runtime.h>
-#include <linux/platform_data/x86/apple.h>
#include "tb.h"
#include "tb_regs.h"
@@ -3334,75 +3333,6 @@ static const struct tb_cm_ops tb_cm_ops = {
.disconnect_xdomain_paths = tb_disconnect_xdomain_paths,
};
-/*
- * During suspend the Thunderbolt controller is reset and all PCIe
- * tunnels are lost. The NHI driver will try to reestablish all tunnels
- * during resume. This adds device links between the tunneled PCIe
- * downstream ports and the NHI so that the device core will make sure
- * NHI is resumed first before the rest.
- */
-static bool tb_apple_add_links(struct tb_nhi *nhi)
-{
- struct pci_dev *nhi_pdev = to_pci_dev(nhi->dev);
- struct pci_dev *upstream, *pdev;
- bool ret;
-
- if (!x86_apple_machine)
- return false;
-
- switch (nhi_pdev->device) {
- case PCI_DEVICE_ID_INTEL_LIGHT_RIDGE:
- case PCI_DEVICE_ID_INTEL_CACTUS_RIDGE_4C:
- case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_NHI:
- case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_NHI:
- break;
- default:
- return false;
- }
-
- upstream = pci_upstream_bridge(nhi_pdev);
- while (upstream) {
- if (!pci_is_pcie(upstream))
- return false;
- if (pci_pcie_type(upstream) == PCI_EXP_TYPE_UPSTREAM)
- break;
- upstream = pci_upstream_bridge(upstream);
- }
-
- if (!upstream)
- return false;
-
- /*
- * For each hotplug downstream port, create add device link
- * back to NHI so that PCIe tunnels can be re-established after
- * sleep.
- */
- ret = false;
- for_each_pci_bridge(pdev, upstream->subordinate) {
- const struct device_link *link;
-
- if (!pci_is_pcie(pdev))
- continue;
- if (pci_pcie_type(pdev) != PCI_EXP_TYPE_DOWNSTREAM ||
- !pdev->is_pciehp)
- continue;
-
- link = device_link_add(&pdev->dev, nhi->dev,
- DL_FLAG_AUTOREMOVE_SUPPLIER |
- DL_FLAG_PM_RUNTIME);
- if (link) {
- dev_dbg(nhi->dev, "created link from %s\n",
- dev_name(&pdev->dev));
- ret = true;
- } else {
- dev_warn(nhi->dev, "device link creation from %s failed\n",
- dev_name(&pdev->dev));
- }
- }
-
- return ret;
-}
-
struct tb *tb_probe(struct tb_nhi *nhi)
{
struct tb_cm *tcm;
@@ -3427,13 +3357,5 @@ struct tb *tb_probe(struct tb_nhi *nhi)
tb_dbg(tb, "using software connection manager\n");
- /*
- * Device links are needed to make sure we establish tunnels
- * before the PCIe/USB stack is resumed so complain here if we
- * found them missing.
- */
- if (!tb_apple_add_links(nhi) && !tb_acpi_add_links(nhi))
- tb_warn(tb, "device links to tunneled native ports are missing!\n");
-
return tb;
}
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..57502da29080 100644
--- a/include/linux/thunderbolt.h
+++ b/include/linux/thunderbolt.h
@@ -370,7 +370,7 @@ int tb_xdomain_request(struct tb_xdomain *xd, const void *request,
* @uuid: XDomain messages with this UUID are dispatched to this handler
* @callback: Callback called with the XDomain message. Returning %1
* here tells the XDomain core that the message was handled
- * by this handler and should not be forwared to other
+ * by this handler and should not be forwarded to other
* handlers.
* @data: Data passed with the callback
* @list: Handlers are linked using this
@@ -507,6 +507,7 @@ void tb_service_properties_changed(struct tb_service *svc);
* @iobase: MMIO space of the NHI
* @tx_rings: All Tx rings available on this host controller
* @rx_rings: All Rx rings available on this host controller
+ * @interrupt_mask: Shadow copy of the ring interrupt mask register
* @going_away: The host controller device is about to disappear so when
* this flag is set, avoid touching the hardware anymore.
* @iommu_dma_protection: An IOMMU will isolate external-facing ports.
@@ -528,6 +529,7 @@ struct tb_nhi {
void __iomem *iobase;
struct tb_ring **tx_rings;
struct tb_ring **rx_rings;
+ u32 *interrupt_mask;
bool going_away;
bool iommu_dma_protection;
struct work_struct interrupt_work;
@@ -553,6 +555,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 +586,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 +677,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 +699,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,11 +737,27 @@ 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 */
struct ring_frame *tb_ring_poll(struct tb_ring *ring);
+bool tb_ring_poll_pending(struct tb_ring *ring);
void tb_ring_poll_complete(struct tb_ring *ring);
int tb_ring_throttling(struct tb_ring *ring, unsigned int interval_nsec);