diff options
| author | Mark Brown <broonie@kernel.org> | 2026-09-17 20:19:39 +0100 |
|---|---|---|
| committer | Mark Brown <broonie@kernel.org> | 2026-09-17 20:19:39 +0100 |
| commit | f6ab341c3eaa4e6000ba1989301ebf030d0e7640 (patch) | |
| tree | 14c72dff5d8168a8d253316a91a0aea19bf910d9 | |
| parent | 0caee94fe0061253f6071e45e0896cad59743ab4 (diff) | |
| parent | 5da91c3694b8060b651aab5d95b2e4729c6e2b56 (diff) | |
| download | linux-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.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 | 222 | ||||
| -rw-r--r-- | drivers/thunderbolt/nhi.h | 2 | ||||
| -rw-r--r-- | drivers/thunderbolt/path.c | 2 | ||||
| -rw-r--r-- | drivers/thunderbolt/pci.c | 113 | ||||
| -rw-r--r-- | drivers/thunderbolt/quirks.c | 18 | ||||
| -rw-r--r-- | drivers/thunderbolt/stream.c | 131 | ||||
| -rw-r--r-- | drivers/thunderbolt/switch.c | 4 | ||||
| -rw-r--r-- | drivers/thunderbolt/tb.c | 78 | ||||
| -rw-r--r-- | drivers/thunderbolt/tb.h | 3 | ||||
| -rw-r--r-- | include/linux/thunderbolt.h | 47 |
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); |
