[x86] Backport Thunderbolt support on Apple computers from 3.17

svn path=/dists/sid/linux/; revision=22016
This commit is contained in:
Ben Hutchings 2014-11-01 14:13:53 +00:00
parent 26e2a5caca
commit 53475cbadc
33 changed files with 5926 additions and 0 deletions

1
debian/changelog vendored
View File

@ -152,6 +152,7 @@ linux (3.16.7-1) UNRELEASED; urgency=medium
* linux-image: Recommend irqbalance if CONFIG_SMP is enabled
(Closes: #577788)
* [armhf] leds: Enable LEDS_PWM as module (for Cubox-i)
* [x86] Backport Thunderbolt support on Apple computers from 3.17
[ Mauricio Faria de Oliveira ]
* [ppc64el] Disable CONFIG_CMDLINE{,_BOOL} usage for setting consoles

View File

@ -0,0 +1,942 @@
From: Andreas Noever <andreas.noever@gmail.com>
Date: Tue, 3 Jun 2014 22:03:58 +0200
Subject: [01/31] thunderbolt: Add initial cactus ridge NHI support
Origin: https://git.kernel.org/linus/16603153666d22df544ae9f9b3764fd18da28eeb
Thunderbolt hotplug is supposed to be handled by the firmware. But Apple
decided to implement thunderbolt at the operating system level. The
firmare only initializes thunderbolt devices that are present at boot
time. This driver enables hotplug of thunderbolt of non-chained
thunderbolt devices on Apple systems with a cactus ridge controller.
This first patch adds the Kconfig file as well the parts of the driver
which talk directly to the hardware (that is pci device setup, interrupt
handling and RX/TX ring management).
Signed-off-by: Andreas Noever <andreas.noever@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
drivers/Kconfig | 2 +
drivers/Makefile | 1 +
drivers/thunderbolt/Kconfig | 12 +
drivers/thunderbolt/Makefile | 3 +
drivers/thunderbolt/nhi.c | 630 +++++++++++++++++++++++++++++++++++++++++
drivers/thunderbolt/nhi.h | 114 ++++++++
drivers/thunderbolt/nhi_regs.h | 101 +++++++
7 files changed, 863 insertions(+)
create mode 100644 drivers/thunderbolt/Kconfig
create mode 100644 drivers/thunderbolt/Makefile
create mode 100644 drivers/thunderbolt/nhi.c
create mode 100644 drivers/thunderbolt/nhi.h
create mode 100644 drivers/thunderbolt/nhi_regs.h
diff --git a/drivers/Kconfig b/drivers/Kconfig
index 0e87a34..9b2dcc2 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -176,4 +176,6 @@ source "drivers/powercap/Kconfig"
source "drivers/mcb/Kconfig"
+source "drivers/thunderbolt/Kconfig"
+
endmenu
diff --git a/drivers/Makefile b/drivers/Makefile
index f98b50d..37b9ed4 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -158,3 +158,4 @@ obj-$(CONFIG_NTB) += ntb/
obj-$(CONFIG_FMC) += fmc/
obj-$(CONFIG_POWERCAP) += powercap/
obj-$(CONFIG_MCB) += mcb/
+obj-$(CONFIG_THUNDERBOLT) += thunderbolt/
diff --git a/drivers/thunderbolt/Kconfig b/drivers/thunderbolt/Kconfig
new file mode 100644
index 0000000..3a25529
--- /dev/null
+++ b/drivers/thunderbolt/Kconfig
@@ -0,0 +1,12 @@
+menuconfig THUNDERBOLT
+ tristate "Thunderbolt support for Apple devices"
+ default no
+ help
+ Cactus Ridge Thunderbolt Controller driver
+ This driver is required if you want to hotplug Thunderbolt devices on
+ Apple hardware.
+
+ Device chaining is currently not supported.
+
+ To compile this driver a module, choose M here. The module will be
+ called thunderbolt.
diff --git a/drivers/thunderbolt/Makefile b/drivers/thunderbolt/Makefile
new file mode 100644
index 0000000..d473ab9
--- /dev/null
+++ b/drivers/thunderbolt/Makefile
@@ -0,0 +1,3 @@
+obj-${CONFIG_THUNDERBOLT} := thunderbolt.o
+thunderbolt-objs := nhi.o
+
diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c
new file mode 100644
index 0000000..11070ff
--- /dev/null
+++ b/drivers/thunderbolt/nhi.c
@@ -0,0 +1,630 @@
+/*
+ * Thunderbolt Cactus Ridge driver - NHI driver
+ *
+ * The NHI (native host interface) is the pci device that allows us to send and
+ * receive frames from the thunderbolt bus.
+ *
+ * Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com>
+ */
+
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/pci.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/dmi.h>
+
+#include "nhi.h"
+#include "nhi_regs.h"
+
+#define RING_TYPE(ring) ((ring)->is_tx ? "TX ring" : "RX ring")
+
+
+static int ring_interrupt_index(struct tb_ring *ring)
+{
+ int bit = ring->hop;
+ if (!ring->is_tx)
+ bit += ring->nhi->hop_count;
+ return bit;
+}
+
+/**
+ * ring_interrupt_active() - activate/deactivate interrupts for a single ring
+ *
+ * ring->nhi->lock must be held.
+ */
+static void ring_interrupt_active(struct tb_ring *ring, bool active)
+{
+ int reg = REG_RING_INTERRUPT_BASE + ring_interrupt_index(ring) / 32;
+ int bit = ring_interrupt_index(ring) & 31;
+ int mask = 1 << bit;
+ u32 old, new;
+ old = ioread32(ring->nhi->iobase + reg);
+ if (active)
+ new = old | mask;
+ else
+ new = old & ~mask;
+
+ dev_info(&ring->nhi->pdev->dev,
+ "%s interrupt at register %#x bit %d (%#x -> %#x)\n",
+ active ? "enabling" : "disabling", reg, bit, old, new);
+
+ if (new == old)
+ dev_WARN(&ring->nhi->pdev->dev,
+ "interrupt for %s %d is already %s\n",
+ RING_TYPE(ring), ring->hop,
+ active ? "enabled" : "disabled");
+ iowrite32(new, ring->nhi->iobase + reg);
+}
+
+/**
+ * nhi_disable_interrupts() - disable interrupts for all rings
+ *
+ * Use only during init and shutdown.
+ */
+static void nhi_disable_interrupts(struct tb_nhi *nhi)
+{
+ int i = 0;
+ /* disable interrupts */
+ for (i = 0; i < RING_INTERRUPT_REG_COUNT(nhi); i++)
+ iowrite32(0, nhi->iobase + REG_RING_INTERRUPT_BASE + 4 * i);
+
+ /* clear interrupt status bits */
+ for (i = 0; i < RING_NOTIFY_REG_COUNT(nhi); i++)
+ ioread32(nhi->iobase + REG_RING_NOTIFY_BASE + 4 * i);
+}
+
+/* ring helper methods */
+
+static void __iomem *ring_desc_base(struct tb_ring *ring)
+{
+ void __iomem *io = ring->nhi->iobase;
+ io += ring->is_tx ? REG_TX_RING_BASE : REG_RX_RING_BASE;
+ io += ring->hop * 16;
+ return io;
+}
+
+static void __iomem *ring_options_base(struct tb_ring *ring)
+{
+ void __iomem *io = ring->nhi->iobase;
+ io += ring->is_tx ? REG_TX_OPTIONS_BASE : REG_RX_OPTIONS_BASE;
+ io += ring->hop * 32;
+ return io;
+}
+
+static void ring_iowrite16desc(struct tb_ring *ring, u32 value, u32 offset)
+{
+ iowrite16(value, ring_desc_base(ring) + offset);
+}
+
+static void ring_iowrite32desc(struct tb_ring *ring, u32 value, u32 offset)
+{
+ iowrite32(value, ring_desc_base(ring) + offset);
+}
+
+static void ring_iowrite64desc(struct tb_ring *ring, u64 value, u32 offset)
+{
+ iowrite32(value, ring_desc_base(ring) + offset);
+ iowrite32(value >> 32, ring_desc_base(ring) + offset + 4);
+}
+
+static void ring_iowrite32options(struct tb_ring *ring, u32 value, u32 offset)
+{
+ iowrite32(value, ring_options_base(ring) + offset);
+}
+
+static bool ring_full(struct tb_ring *ring)
+{
+ return ((ring->head + 1) % ring->size) == ring->tail;
+}
+
+static bool ring_empty(struct tb_ring *ring)
+{
+ return ring->head == ring->tail;
+}
+
+/**
+ * ring_write_descriptors() - post frames from ring->queue to the controller
+ *
+ * ring->lock is held.
+ */
+static void ring_write_descriptors(struct tb_ring *ring)
+{
+ struct ring_frame *frame, *n;
+ struct ring_desc *descriptor;
+ list_for_each_entry_safe(frame, n, &ring->queue, list) {
+ if (ring_full(ring))
+ break;
+ list_move_tail(&frame->list, &ring->in_flight);
+ descriptor = &ring->descriptors[ring->head];
+ descriptor->phys = frame->buffer_phy;
+ descriptor->time = 0;
+ descriptor->flags = RING_DESC_POSTED | RING_DESC_INTERRUPT;
+ if (ring->is_tx) {
+ descriptor->length = frame->size;
+ descriptor->eof = frame->eof;
+ descriptor->sof = frame->sof;
+ }
+ ring->head = (ring->head + 1) % ring->size;
+ ring_iowrite16desc(ring, ring->head, ring->is_tx ? 10 : 8);
+ }
+}
+
+/**
+ * ring_work() - progress completed frames
+ *
+ * If the ring is shutting down then all frames are marked as canceled and
+ * their callbacks are invoked.
+ *
+ * Otherwise we collect all completed frame from the ring buffer, write new
+ * frame to the ring buffer and invoke the callbacks for the completed frames.
+ */
+static void ring_work(struct work_struct *work)
+{
+ struct tb_ring *ring = container_of(work, typeof(*ring), work);
+ struct ring_frame *frame;
+ bool canceled = false;
+ LIST_HEAD(done);
+ mutex_lock(&ring->lock);
+
+ if (!ring->running) {
+ /* Move all frames to done and mark them as canceled. */
+ list_splice_tail_init(&ring->in_flight, &done);
+ list_splice_tail_init(&ring->queue, &done);
+ canceled = true;
+ goto invoke_callback;
+ }
+
+ while (!ring_empty(ring)) {
+ if (!(ring->descriptors[ring->tail].flags
+ & RING_DESC_COMPLETED))
+ break;
+ frame = list_first_entry(&ring->in_flight, typeof(*frame),
+ list);
+ list_move_tail(&frame->list, &done);
+ if (!ring->is_tx) {
+ frame->size = ring->descriptors[ring->tail].length;
+ frame->eof = ring->descriptors[ring->tail].eof;
+ frame->sof = ring->descriptors[ring->tail].sof;
+ frame->flags = ring->descriptors[ring->tail].flags;
+ if (frame->sof != 0)
+ dev_WARN(&ring->nhi->pdev->dev,
+ "%s %d got unexpected SOF: %#x\n",
+ RING_TYPE(ring), ring->hop,
+ frame->sof);
+ /*
+ * known flags:
+ * raw not enabled, interupt not set: 0x2=0010
+ * raw enabled: 0xa=1010
+ * raw not enabled: 0xb=1011
+ * partial frame (>MAX_FRAME_SIZE): 0xe=1110
+ */
+ if (frame->flags != 0xa)
+ dev_WARN(&ring->nhi->pdev->dev,
+ "%s %d got unexpected flags: %#x\n",
+ RING_TYPE(ring), ring->hop,
+ frame->flags);
+ }
+ ring->tail = (ring->tail + 1) % ring->size;
+ }
+ ring_write_descriptors(ring);
+
+invoke_callback:
+ mutex_unlock(&ring->lock); /* allow callbacks to schedule new work */
+ while (!list_empty(&done)) {
+ frame = list_first_entry(&done, typeof(*frame), list);
+ /*
+ * The callback may reenqueue or delete frame.
+ * Do not hold on to it.
+ */
+ list_del_init(&frame->list);
+ frame->callback(ring, frame, canceled);
+ }
+}
+
+int __ring_enqueue(struct tb_ring *ring, struct ring_frame *frame)
+{
+ int ret = 0;
+ mutex_lock(&ring->lock);
+ if (ring->running) {
+ list_add_tail(&frame->list, &ring->queue);
+ ring_write_descriptors(ring);
+ } else {
+ ret = -ESHUTDOWN;
+ }
+ mutex_unlock(&ring->lock);
+ return ret;
+}
+
+static struct tb_ring *ring_alloc(struct tb_nhi *nhi, u32 hop, int size,
+ bool transmit)
+{
+ struct tb_ring *ring = NULL;
+ dev_info(&nhi->pdev->dev, "allocating %s ring %d of size %d\n",
+ transmit ? "TX" : "RX", hop, size);
+
+ mutex_lock(&nhi->lock);
+ if (hop >= nhi->hop_count) {
+ dev_WARN(&nhi->pdev->dev, "invalid hop: %d\n", hop);
+ goto err;
+ }
+ if (transmit && nhi->tx_rings[hop]) {
+ dev_WARN(&nhi->pdev->dev, "TX hop %d already allocated\n", hop);
+ goto err;
+ } else if (!transmit && nhi->rx_rings[hop]) {
+ dev_WARN(&nhi->pdev->dev, "RX hop %d already allocated\n", hop);
+ goto err;
+ }
+ ring = kzalloc(sizeof(*ring), GFP_KERNEL);
+ if (!ring)
+ goto err;
+
+ mutex_init(&ring->lock);
+ INIT_LIST_HEAD(&ring->queue);
+ INIT_LIST_HEAD(&ring->in_flight);
+ INIT_WORK(&ring->work, ring_work);
+
+ ring->nhi = nhi;
+ ring->hop = hop;
+ ring->is_tx = transmit;
+ ring->size = size;
+ ring->head = 0;
+ ring->tail = 0;
+ ring->running = false;
+ ring->descriptors = dma_alloc_coherent(&ring->nhi->pdev->dev,
+ size * sizeof(*ring->descriptors),
+ &ring->descriptors_dma, GFP_KERNEL | __GFP_ZERO);
+ if (!ring->descriptors)
+ goto err;
+
+ if (transmit)
+ nhi->tx_rings[hop] = ring;
+ else
+ nhi->rx_rings[hop] = ring;
+ mutex_unlock(&nhi->lock);
+ return ring;
+
+err:
+ if (ring)
+ mutex_destroy(&ring->lock);
+ kfree(ring);
+ mutex_unlock(&nhi->lock);
+ return NULL;
+}
+
+struct tb_ring *ring_alloc_tx(struct tb_nhi *nhi, int hop, int size)
+{
+ return ring_alloc(nhi, hop, size, true);
+}
+
+struct tb_ring *ring_alloc_rx(struct tb_nhi *nhi, int hop, int size)
+{
+ return ring_alloc(nhi, hop, size, false);
+}
+
+/**
+ * ring_start() - enable a ring
+ *
+ * Must not be invoked in parallel with ring_stop().
+ */
+void ring_start(struct tb_ring *ring)
+{
+ mutex_lock(&ring->nhi->lock);
+ mutex_lock(&ring->lock);
+ if (ring->running) {
+ dev_WARN(&ring->nhi->pdev->dev, "ring already started\n");
+ goto err;
+ }
+ dev_info(&ring->nhi->pdev->dev, "starting %s %d\n",
+ RING_TYPE(ring), ring->hop);
+
+ ring_iowrite64desc(ring, ring->descriptors_dma, 0);
+ if (ring->is_tx) {
+ ring_iowrite32desc(ring, ring->size, 12);
+ ring_iowrite32options(ring, 0, 4); /* time releated ? */
+ ring_iowrite32options(ring,
+ RING_FLAG_ENABLE | RING_FLAG_RAW, 0);
+ } else {
+ ring_iowrite32desc(ring,
+ (TB_FRAME_SIZE << 16) | ring->size, 12);
+ ring_iowrite32options(ring, 0xffffffff, 4); /* SOF EOF mask */
+ ring_iowrite32options(ring,
+ RING_FLAG_ENABLE | RING_FLAG_RAW, 0);
+ }
+ ring_interrupt_active(ring, true);
+ ring->running = true;
+err:
+ mutex_unlock(&ring->lock);
+ mutex_unlock(&ring->nhi->lock);
+}
+
+
+/**
+ * ring_stop() - shutdown a ring
+ *
+ * Must not be invoked from a callback.
+ *
+ * This method will disable the ring. Further calls to ring_tx/ring_rx will
+ * return -ESHUTDOWN until ring_stop has been called.
+ *
+ * All enqueued frames will be canceled and their callbacks will be executed
+ * with frame->canceled set to true (on the callback thread). This method
+ * returns only after all callback invocations have finished.
+ */
+void ring_stop(struct tb_ring *ring)
+{
+ mutex_lock(&ring->nhi->lock);
+ mutex_lock(&ring->lock);
+ dev_info(&ring->nhi->pdev->dev, "stopping %s %d\n",
+ RING_TYPE(ring), ring->hop);
+ if (!ring->running) {
+ dev_WARN(&ring->nhi->pdev->dev, "%s %d already stopped\n",
+ RING_TYPE(ring), ring->hop);
+ goto err;
+ }
+ ring_interrupt_active(ring, false);
+
+ ring_iowrite32options(ring, 0, 0);
+ ring_iowrite64desc(ring, 0, 0);
+ ring_iowrite16desc(ring, 0, ring->is_tx ? 10 : 8);
+ ring_iowrite32desc(ring, 0, 12);
+ ring->head = 0;
+ ring->tail = 0;
+ ring->running = false;
+
+err:
+ mutex_unlock(&ring->lock);
+ mutex_unlock(&ring->nhi->lock);
+
+ /*
+ * schedule ring->work to invoke callbacks on all remaining frames.
+ */
+ schedule_work(&ring->work);
+ flush_work(&ring->work);
+}
+
+/*
+ * ring_free() - free ring
+ *
+ * When this method returns all invocations of ring->callback will have
+ * finished.
+ *
+ * Ring must be stopped.
+ *
+ * Must NOT be called from ring_frame->callback!
+ */
+void ring_free(struct tb_ring *ring)
+{
+ mutex_lock(&ring->nhi->lock);
+ /*
+ * Dissociate the ring from the NHI. This also ensures that
+ * nhi_interrupt_work cannot reschedule ring->work.
+ */
+ if (ring->is_tx)
+ ring->nhi->tx_rings[ring->hop] = NULL;
+ else
+ ring->nhi->rx_rings[ring->hop] = NULL;
+
+ if (ring->running) {
+ dev_WARN(&ring->nhi->pdev->dev, "%s %d still running\n",
+ RING_TYPE(ring), ring->hop);
+ }
+
+ dma_free_coherent(&ring->nhi->pdev->dev,
+ ring->size * sizeof(*ring->descriptors),
+ ring->descriptors, ring->descriptors_dma);
+
+ ring->descriptors = 0;
+ ring->descriptors_dma = 0;
+
+
+ dev_info(&ring->nhi->pdev->dev,
+ "freeing %s %d\n",
+ RING_TYPE(ring),
+ ring->hop);
+
+ mutex_unlock(&ring->nhi->lock);
+ /**
+ * ring->work can no longer be scheduled (it is scheduled only by
+ * nhi_interrupt_work and ring_stop). Wait for it to finish before
+ * freeing the ring.
+ */
+ flush_work(&ring->work);
+ mutex_destroy(&ring->lock);
+ kfree(ring);
+}
+
+static void nhi_interrupt_work(struct work_struct *work)
+{
+ struct tb_nhi *nhi = container_of(work, typeof(*nhi), interrupt_work);
+ int value = 0; /* Suppress uninitialized usage warning. */
+ int bit;
+ int hop = -1;
+ int type = 0; /* current interrupt type 0: TX, 1: RX, 2: RX overflow */
+ struct tb_ring *ring;
+
+ mutex_lock(&nhi->lock);
+
+ /*
+ * Starting at REG_RING_NOTIFY_BASE there are three status bitfields
+ * (TX, RX, RX overflow). We iterate over the bits and read a new
+ * dwords as required. The registers are cleared on read.
+ */
+ for (bit = 0; bit < 3 * nhi->hop_count; bit++) {
+ if (bit % 32 == 0)
+ value = ioread32(nhi->iobase
+ + REG_RING_NOTIFY_BASE
+ + 4 * (bit / 32));
+ if (++hop == nhi->hop_count) {
+ hop = 0;
+ type++;
+ }
+ if ((value & (1 << (bit % 32))) == 0)
+ continue;
+ if (type == 2) {
+ dev_warn(&nhi->pdev->dev,
+ "RX overflow for ring %d\n",
+ hop);
+ continue;
+ }
+ if (type == 0)
+ ring = nhi->tx_rings[hop];
+ else
+ ring = nhi->rx_rings[hop];
+ if (ring == NULL) {
+ dev_warn(&nhi->pdev->dev,
+ "got interrupt for inactive %s ring %d\n",
+ type ? "RX" : "TX",
+ hop);
+ continue;
+ }
+ /* we do not check ring->running, this is done in ring->work */
+ schedule_work(&ring->work);
+ }
+ mutex_unlock(&nhi->lock);
+}
+
+static irqreturn_t nhi_msi(int irq, void *data)
+{
+ struct tb_nhi *nhi = data;
+ schedule_work(&nhi->interrupt_work);
+ return IRQ_HANDLED;
+}
+
+static void nhi_shutdown(struct tb_nhi *nhi)
+{
+ int i;
+ dev_info(&nhi->pdev->dev, "shutdown\n");
+
+ for (i = 0; i < nhi->hop_count; i++) {
+ if (nhi->tx_rings[i])
+ dev_WARN(&nhi->pdev->dev,
+ "TX ring %d is still active\n", i);
+ if (nhi->rx_rings[i])
+ dev_WARN(&nhi->pdev->dev,
+ "RX ring %d is still active\n", i);
+ }
+ nhi_disable_interrupts(nhi);
+ /*
+ * We have to release the irq before calling flush_work. Otherwise an
+ * already executing IRQ handler could call schedule_work again.
+ */
+ devm_free_irq(&nhi->pdev->dev, nhi->pdev->irq, nhi);
+ flush_work(&nhi->interrupt_work);
+ mutex_destroy(&nhi->lock);
+}
+
+static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id)
+{
+ struct tb_nhi *nhi;
+ int res;
+
+ res = pcim_enable_device(pdev);
+ if (res) {
+ dev_err(&pdev->dev, "cannot enable PCI device, aborting\n");
+ return res;
+ }
+
+ res = pci_enable_msi(pdev);
+ if (res) {
+ dev_err(&pdev->dev, "cannot enable MSI, aborting\n");
+ return res;
+ }
+
+ res = pcim_iomap_regions(pdev, 1 << 0, "thunderbolt");
+ if (res) {
+ dev_err(&pdev->dev, "cannot obtain PCI resources, aborting\n");
+ return res;
+ }
+
+ nhi = devm_kzalloc(&pdev->dev, sizeof(*nhi), GFP_KERNEL);
+ if (!nhi)
+ return -ENOMEM;
+
+ nhi->pdev = pdev;
+ /* cannot fail - table is allocated bin pcim_iomap_regions */
+ nhi->iobase = pcim_iomap_table(pdev)[0];
+ nhi->hop_count = ioread32(nhi->iobase + REG_HOP_COUNT) & 0x3ff;
+ if (nhi->hop_count != 12)
+ dev_warn(&pdev->dev, "unexpected hop count: %d\n",
+ nhi->hop_count);
+ INIT_WORK(&nhi->interrupt_work, nhi_interrupt_work);
+
+ nhi->tx_rings = devm_kzalloc(&pdev->dev,
+ nhi->hop_count * sizeof(struct tb_ring),
+ GFP_KERNEL);
+ nhi->rx_rings = devm_kzalloc(&pdev->dev,
+ nhi->hop_count * sizeof(struct tb_ring),
+ GFP_KERNEL);
+ if (!nhi->tx_rings || !nhi->rx_rings)
+ return -ENOMEM;
+
+ nhi_disable_interrupts(nhi); /* In case someone left them on. */
+ res = devm_request_irq(&pdev->dev, pdev->irq, nhi_msi,
+ IRQF_NO_SUSPEND, /* must work during _noirq */
+ "thunderbolt", nhi);
+ if (res) {
+ dev_err(&pdev->dev, "request_irq failed, aborting\n");
+ return res;
+ }
+
+ mutex_init(&nhi->lock);
+
+ pci_set_master(pdev);
+
+ /* magic value - clock related? */
+ iowrite32(3906250 / 10000, nhi->iobase + 0x38c00);
+
+ pci_set_drvdata(pdev, nhi);
+
+ return 0;
+}
+
+static void nhi_remove(struct pci_dev *pdev)
+{
+ struct tb_nhi *nhi = pci_get_drvdata(pdev);
+ nhi_shutdown(nhi);
+}
+
+struct pci_device_id nhi_ids[] = {
+ /*
+ * We have to specify class, the TB bridges use the same device and
+ * vendor (sub)id.
+ */
+ {
+ .class = PCI_CLASS_SYSTEM_OTHER << 8, .class_mask = ~0,
+ .vendor = PCI_VENDOR_ID_INTEL, .device = 0x1547,
+ .subvendor = 0x2222, .subdevice = 0x1111,
+ },
+ {
+ .class = PCI_CLASS_SYSTEM_OTHER << 8, .class_mask = ~0,
+ .vendor = PCI_VENDOR_ID_INTEL, .device = 0x156c,
+ .subvendor = 0x2222, .subdevice = 0x1111,
+ },
+ { 0,}
+};
+
+MODULE_DEVICE_TABLE(pci, nhi_ids);
+MODULE_LICENSE("GPL");
+
+static struct pci_driver nhi_driver = {
+ .name = "thunderbolt",
+ .id_table = nhi_ids,
+ .probe = nhi_probe,
+ .remove = nhi_remove,
+};
+
+static int __init nhi_init(void)
+{
+ if (!dmi_match(DMI_BOARD_VENDOR, "Apple Inc."))
+ return -ENOSYS;
+ return pci_register_driver(&nhi_driver);
+}
+
+static void __exit nhi_unload(void)
+{
+ pci_unregister_driver(&nhi_driver);
+}
+
+module_init(nhi_init);
+module_exit(nhi_unload);
diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h
new file mode 100644
index 0000000..3172429
--- /dev/null
+++ b/drivers/thunderbolt/nhi.h
@@ -0,0 +1,114 @@
+/*
+ * Thunderbolt Cactus Ridge driver - NHI driver
+ *
+ * Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com>
+ */
+
+#ifndef DSL3510_H_
+#define DSL3510_H_
+
+#include <linux/mutex.h>
+#include <linux/workqueue.h>
+
+/**
+ * struct tb_nhi - thunderbolt native host interface
+ */
+struct tb_nhi {
+ struct mutex lock; /*
+ * Must be held during ring creation/destruction.
+ * Is acquired by interrupt_work when dispatching
+ * interrupts to individual rings.
+ **/
+ struct pci_dev *pdev;
+ void __iomem *iobase;
+ struct tb_ring **tx_rings;
+ struct tb_ring **rx_rings;
+ struct work_struct interrupt_work;
+ u32 hop_count; /* Number of rings (end point hops) supported by NHI. */
+};
+
+/**
+ * struct tb_ring - thunderbolt TX or RX ring associated with a NHI
+ */
+struct tb_ring {
+ struct mutex lock; /* must be acquired after nhi->lock */
+ struct tb_nhi *nhi;
+ int size;
+ int hop;
+ int head; /* write next descriptor here */
+ int tail; /* complete next descriptor here */
+ struct ring_desc *descriptors;
+ dma_addr_t descriptors_dma;
+ struct list_head queue;
+ struct list_head in_flight;
+ struct work_struct work;
+ bool is_tx:1; /* rx otherwise */
+ bool running:1;
+};
+
+struct ring_frame;
+typedef void (*ring_cb)(struct tb_ring*, struct ring_frame*, bool canceled);
+
+/**
+ * struct ring_frame - for use with ring_rx/ring_tx
+ */
+struct ring_frame {
+ dma_addr_t buffer_phy;
+ ring_cb callback;
+ struct list_head list;
+ u32 size:12; /* TX: in, RX: out*/
+ u32 flags:12; /* RX: out */
+ u32 eof:4; /* TX:in, RX: out */
+ u32 sof:4; /* TX:in, RX: out */
+};
+
+#define TB_FRAME_SIZE 0x100 /* minimum size for ring_rx */
+
+struct tb_ring *ring_alloc_tx(struct tb_nhi *nhi, int hop, int size);
+struct tb_ring *ring_alloc_rx(struct tb_nhi *nhi, int hop, int size);
+void ring_start(struct tb_ring *ring);
+void ring_stop(struct tb_ring *ring);
+void ring_free(struct tb_ring *ring);
+
+int __ring_enqueue(struct tb_ring *ring, struct ring_frame *frame);
+
+/**
+ * ring_rx() - enqueue a frame on an RX ring
+ *
+ * frame->buffer, frame->buffer_phy and frame->callback have to be set. The
+ * buffer must contain at least TB_FRAME_SIZE bytes.
+ *
+ * frame->callback will be invoked with frame->size, frame->flags, frame->eof,
+ * frame->sof set once the frame has been received.
+ *
+ * If ring_stop is called after the packet has been enqueued frame->callback
+ * will be called with canceled set to true.
+ *
+ * Return: Returns ESHUTDOWN if ring_stop has been called. Zero otherwise.
+ */
+static inline int ring_rx(struct tb_ring *ring, struct ring_frame *frame)
+{
+ WARN_ON(ring->is_tx);
+ return __ring_enqueue(ring, frame);
+}
+
+/**
+ * ring_tx() - enqueue a frame on an TX ring
+ *
+ * frame->buffer, frame->buffer_phy, frame->callback, frame->size, frame->eof
+ * and frame->sof have to be set.
+ *
+ * frame->callback will be invoked with once the frame has been transmitted.
+ *
+ * If ring_stop is called after the packet has been enqueued frame->callback
+ * will be called with canceled set to true.
+ *
+ * Return: Returns ESHUTDOWN if ring_stop has been called. Zero otherwise.
+ */
+static inline int ring_tx(struct tb_ring *ring, struct ring_frame *frame)
+{
+ WARN_ON(!ring->is_tx);
+ return __ring_enqueue(ring, frame);
+}
+
+#endif
diff --git a/drivers/thunderbolt/nhi_regs.h b/drivers/thunderbolt/nhi_regs.h
new file mode 100644
index 0000000..86b996c
--- /dev/null
+++ b/drivers/thunderbolt/nhi_regs.h
@@ -0,0 +1,101 @@
+/*
+ * Thunderbolt Cactus Ridge driver - NHI registers
+ *
+ * Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com>
+ */
+
+#ifndef DSL3510_REGS_H_
+#define DSL3510_REGS_H_
+
+#include <linux/types.h>
+
+enum ring_flags {
+ RING_FLAG_ISOCH_ENABLE = 1 << 27, /* TX only? */
+ RING_FLAG_E2E_FLOW_CONTROL = 1 << 28,
+ RING_FLAG_PCI_NO_SNOOP = 1 << 29,
+ RING_FLAG_RAW = 1 << 30, /* ignore EOF/SOF mask, include checksum */
+ RING_FLAG_ENABLE = 1 << 31,
+};
+
+enum ring_desc_flags {
+ RING_DESC_ISOCH = 0x1, /* TX only? */
+ RING_DESC_COMPLETED = 0x2, /* set by NHI */
+ RING_DESC_POSTED = 0x4, /* always set this */
+ RING_DESC_INTERRUPT = 0x8, /* request an interrupt on completion */
+};
+
+/**
+ * struct ring_desc - TX/RX ring entry
+ *
+ * For TX set length/eof/sof.
+ * For RX length/eof/sof are set by the NHI.
+ */
+struct ring_desc {
+ u64 phys;
+ u32 length:12;
+ u32 eof:4;
+ u32 sof:4;
+ enum ring_desc_flags flags:12;
+ u32 time; /* write zero */
+} __packed;
+
+/* NHI registers in bar 0 */
+
+/*
+ * 16 bytes per entry, one entry for every hop (REG_HOP_COUNT)
+ * 00: physical pointer to an array of struct ring_desc
+ * 08: ring tail (set by NHI)
+ * 10: ring head (index of first non posted descriptor)
+ * 12: descriptor count
+ */
+#define REG_TX_RING_BASE 0x00000
+
+/*
+ * 16 bytes per entry, one entry for every hop (REG_HOP_COUNT)
+ * 00: physical pointer to an array of struct ring_desc
+ * 08: ring head (index of first not posted descriptor)
+ * 10: ring tail (set by NHI)
+ * 12: descriptor count
+ * 14: max frame sizes (anything larger than 0x100 has no effect)
+ */
+#define REG_RX_RING_BASE 0x08000
+
+/*
+ * 32 bytes per entry, one entry for every hop (REG_HOP_COUNT)
+ * 00: enum_ring_flags
+ * 04: isoch time stamp ?? (write 0)
+ * ..: unknown
+ */
+#define REG_TX_OPTIONS_BASE 0x19800
+
+/*
+ * 32 bytes per entry, one entry for every hop (REG_HOP_COUNT)
+ * 00: enum ring_flags
+ * If RING_FLAG_E2E_FLOW_CONTROL is set then bits 13-23 must be set to
+ * the corresponding TX hop id.
+ * 04: EOF/SOF mask (ignored for RING_FLAG_RAW rings)
+ * ..: unknown
+ */
+#define REG_RX_OPTIONS_BASE 0x29800
+
+/*
+ * three bitfields: tx, rx, rx overflow
+ * Every bitfield contains one bit for every hop (REG_HOP_COUNT). Registers are
+ * cleared on read. New interrupts are fired only after ALL registers have been
+ * read (even those containing only disabled rings).
+ */
+#define REG_RING_NOTIFY_BASE 0x37800
+#define RING_NOTIFY_REG_COUNT(nhi) ((31 + 3 * nhi->hop_count) / 32)
+
+/*
+ * two bitfields: rx, tx
+ * Both bitfields contains one bit for every hop (REG_HOP_COUNT). To
+ * enable/disable interrupts set/clear the corresponding bits.
+ */
+#define REG_RING_INTERRUPT_BASE 0x38200
+#define RING_INTERRUPT_REG_COUNT(nhi) ((31 + 2 * nhi->hop_count) / 32)
+
+/* The last 11 bits contain the number of hops supported by the NHI port. */
+#define REG_HOP_COUNT 0x39640
+
+#endif

View File

@ -0,0 +1,849 @@
From: Andreas Noever <andreas.noever@gmail.com>
Date: Tue, 3 Jun 2014 22:03:59 +0200
Subject: [02/31] thunderbolt: Add control channel interface
Origin: https://git.kernel.org/linus/f25bf6fcb1a83a149bc8b5285d33b48cbd47c7d7
Thunderbolt devices are configured by reading/writing into their
configuration space (similar to pci). This is done by sending packets
through the NHI (native host interface) onto the control channel.
This patch handles the low level packet based protocol and exposes
higher level operations like tb_cfg_read/tb_cfg_write.
Signed-off-by: Andreas Noever <andreas.noever@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
drivers/thunderbolt/Makefile | 2 +-
drivers/thunderbolt/ctl.c | 731 +++++++++++++++++++++++++++++++++++++++++++
drivers/thunderbolt/ctl.h | 75 +++++
3 files changed, 807 insertions(+), 1 deletion(-)
create mode 100644 drivers/thunderbolt/ctl.c
create mode 100644 drivers/thunderbolt/ctl.h
diff --git a/drivers/thunderbolt/Makefile b/drivers/thunderbolt/Makefile
index d473ab9..4b21c99 100644
--- a/drivers/thunderbolt/Makefile
+++ b/drivers/thunderbolt/Makefile
@@ -1,3 +1,3 @@
obj-${CONFIG_THUNDERBOLT} := thunderbolt.o
-thunderbolt-objs := nhi.o
+thunderbolt-objs := nhi.o ctl.o
diff --git a/drivers/thunderbolt/ctl.c b/drivers/thunderbolt/ctl.c
new file mode 100644
index 0000000..9b0120b
--- /dev/null
+++ b/drivers/thunderbolt/ctl.c
@@ -0,0 +1,731 @@
+/*
+ * Thunderbolt Cactus Ridge driver - control channel and configuration commands
+ *
+ * Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com>
+ */
+
+#include <linux/crc32.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <linux/dmapool.h>
+#include <linux/workqueue.h>
+#include <linux/kfifo.h>
+
+#include "ctl.h"
+
+
+struct ctl_pkg {
+ struct tb_ctl *ctl;
+ void *buffer;
+ struct ring_frame frame;
+};
+
+#define TB_CTL_RX_PKG_COUNT 10
+
+/**
+ * struct tb_cfg - thunderbolt control channel
+ */
+struct tb_ctl {
+ struct tb_nhi *nhi;
+ struct tb_ring *tx;
+ struct tb_ring *rx;
+
+ struct dma_pool *frame_pool;
+ struct ctl_pkg *rx_packets[TB_CTL_RX_PKG_COUNT];
+ DECLARE_KFIFO(response_fifo, struct ctl_pkg*, 16);
+ struct completion response_ready;
+
+ hotplug_cb callback;
+ void *callback_data;
+};
+
+
+#define tb_ctl_WARN(ctl, format, arg...) \
+ dev_WARN(&(ctl)->nhi->pdev->dev, format, ## arg)
+
+#define tb_ctl_err(ctl, format, arg...) \
+ dev_err(&(ctl)->nhi->pdev->dev, format, ## arg)
+
+#define tb_ctl_warn(ctl, format, arg...) \
+ dev_warn(&(ctl)->nhi->pdev->dev, format, ## arg)
+
+#define tb_ctl_info(ctl, format, arg...) \
+ dev_info(&(ctl)->nhi->pdev->dev, format, ## arg)
+
+
+/* configuration packets definitions */
+
+enum tb_cfg_pkg_type {
+ TB_CFG_PKG_READ = 1,
+ TB_CFG_PKG_WRITE = 2,
+ TB_CFG_PKG_ERROR = 3,
+ TB_CFG_PKG_NOTIFY_ACK = 4,
+ TB_CFG_PKG_EVENT = 5,
+ TB_CFG_PKG_XDOMAIN_REQ = 6,
+ TB_CFG_PKG_XDOMAIN_RESP = 7,
+ TB_CFG_PKG_OVERRIDE = 8,
+ TB_CFG_PKG_RESET = 9,
+ TB_CFG_PKG_PREPARE_TO_SLEEP = 0xd,
+};
+
+/* common header */
+struct tb_cfg_header {
+ u32 route_hi:22;
+ u32 unknown:10; /* highest order bit is set on replies */
+ u32 route_lo;
+} __packed;
+
+/* additional header for read/write packets */
+struct tb_cfg_address {
+ u32 offset:13; /* in dwords */
+ u32 length:6; /* in dwords */
+ u32 port:6;
+ enum tb_cfg_space space:2;
+ u32 seq:2; /* sequence number */
+ u32 zero:3;
+} __packed;
+
+/* TB_CFG_PKG_READ, response for TB_CFG_PKG_WRITE */
+struct cfg_read_pkg {
+ struct tb_cfg_header header;
+ struct tb_cfg_address addr;
+} __packed;
+
+/* TB_CFG_PKG_WRITE, response for TB_CFG_PKG_READ */
+struct cfg_write_pkg {
+ struct tb_cfg_header header;
+ struct tb_cfg_address addr;
+ u32 data[64]; /* maximum size, tb_cfg_address.length has 6 bits */
+} __packed;
+
+/* TB_CFG_PKG_ERROR */
+struct cfg_error_pkg {
+ struct tb_cfg_header header;
+ enum tb_cfg_error error:4;
+ u32 zero1:4;
+ u32 port:6;
+ u32 zero2:2; /* Both should be zero, still they are different fields. */
+ u32 zero3:16;
+} __packed;
+
+/* TB_CFG_PKG_EVENT */
+struct cfg_event_pkg {
+ struct tb_cfg_header header;
+ u32 port:6;
+ u32 zero:25;
+ bool unplug:1;
+} __packed;
+
+/* TB_CFG_PKG_RESET */
+struct cfg_reset_pkg {
+ struct tb_cfg_header header;
+} __packed;
+
+/* TB_CFG_PKG_PREPARE_TO_SLEEP */
+struct cfg_pts_pkg {
+ struct tb_cfg_header header;
+ u32 data;
+} __packed;
+
+
+/* utility functions */
+
+static u64 get_route(struct tb_cfg_header header)
+{
+ return (u64) header.route_hi << 32 | header.route_lo;
+}
+
+static struct tb_cfg_header make_header(u64 route)
+{
+ struct tb_cfg_header header = {
+ .route_hi = route >> 32,
+ .route_lo = route,
+ };
+ /* check for overflow, route_hi is not 32 bits! */
+ WARN_ON(get_route(header) != route);
+ return header;
+}
+
+static int check_header(struct ctl_pkg *pkg, u32 len, enum tb_cfg_pkg_type type,
+ u64 route)
+{
+ struct tb_cfg_header *header = pkg->buffer;
+
+ /* check frame, TODO: frame flags */
+ if (WARN(len != pkg->frame.size,
+ "wrong framesize (expected %#x, got %#x)\n",
+ len, pkg->frame.size))
+ return -EIO;
+ if (WARN(type != pkg->frame.eof, "wrong eof (expected %#x, got %#x)\n",
+ type, pkg->frame.eof))
+ return -EIO;
+ if (WARN(pkg->frame.sof, "wrong sof (expected 0x0, got %#x)\n",
+ pkg->frame.sof))
+ return -EIO;
+
+ /* check header */
+ if (WARN(header->unknown != 1 << 9,
+ "header->unknown is %#x\n", header->unknown))
+ return -EIO;
+ if (WARN(route != get_route(*header),
+ "wrong route (expected %llx, got %llx)",
+ route, get_route(*header)))
+ return -EIO;
+ return 0;
+}
+
+static int check_config_address(struct tb_cfg_address addr,
+ enum tb_cfg_space space, u32 offset,
+ u32 length)
+{
+ if (WARN(addr.zero, "addr.zero is %#x\n", addr.zero))
+ return -EIO;
+ if (WARN(space != addr.space, "wrong space (expected %x, got %x\n)",
+ space, addr.space))
+ return -EIO;
+ if (WARN(offset != addr.offset, "wrong offset (expected %x, got %x\n)",
+ offset, addr.offset))
+ return -EIO;
+ if (WARN(length != addr.length, "wrong space (expected %x, got %x\n)",
+ length, addr.length))
+ return -EIO;
+ if (WARN(addr.seq, "addr.seq is %#x\n", addr.seq))
+ return -EIO;
+ /*
+ * We cannot check addr->port as it is set to the upstream port of the
+ * sender.
+ */
+ return 0;
+}
+
+static struct tb_cfg_result decode_error(struct ctl_pkg *response)
+{
+ struct cfg_error_pkg *pkg = response->buffer;
+ struct tb_cfg_result res = { 0 };
+ res.response_route = get_route(pkg->header);
+ res.response_port = 0;
+ res.err = check_header(response, sizeof(*pkg), TB_CFG_PKG_ERROR,
+ get_route(pkg->header));
+ if (res.err)
+ return res;
+
+ WARN(pkg->zero1, "pkg->zero1 is %#x\n", pkg->zero1);
+ WARN(pkg->zero2, "pkg->zero1 is %#x\n", pkg->zero1);
+ WARN(pkg->zero3, "pkg->zero1 is %#x\n", pkg->zero1);
+ res.err = 1;
+ res.tb_error = pkg->error;
+ res.response_port = pkg->port;
+ return res;
+
+}
+
+static struct tb_cfg_result parse_header(struct ctl_pkg *pkg, u32 len,
+ enum tb_cfg_pkg_type type, u64 route)
+{
+ struct tb_cfg_header *header = pkg->buffer;
+ struct tb_cfg_result res = { 0 };
+
+ if (pkg->frame.eof == TB_CFG_PKG_ERROR)
+ return decode_error(pkg);
+
+ res.response_port = 0; /* will be updated later for cfg_read/write */
+ res.response_route = get_route(*header);
+ res.err = check_header(pkg, len, type, route);
+ return res;
+}
+
+static void tb_cfg_print_error(struct tb_ctl *ctl,
+ const struct tb_cfg_result *res)
+{
+ WARN_ON(res->err != 1);
+ switch (res->tb_error) {
+ case TB_CFG_ERROR_PORT_NOT_CONNECTED:
+ /* Port is not connected. This can happen during surprise
+ * removal. Do not warn. */
+ return;
+ case TB_CFG_ERROR_INVALID_CONFIG_SPACE:
+ /*
+ * Invalid cfg_space/offset/length combination in
+ * cfg_read/cfg_write.
+ */
+ tb_ctl_WARN(ctl,
+ "CFG_ERROR(%llx:%x): Invalid config space of offset\n",
+ res->response_route, res->response_port);
+ return;
+ case TB_CFG_ERROR_NO_SUCH_PORT:
+ /*
+ * - The route contains a non-existent port.
+ * - The route contains a non-PHY port (e.g. PCIe).
+ * - The port in cfg_read/cfg_write does not exist.
+ */
+ tb_ctl_WARN(ctl, "CFG_ERROR(%llx:%x): Invalid port\n",
+ res->response_route, res->response_port);
+ return;
+ case TB_CFG_ERROR_LOOP:
+ tb_ctl_WARN(ctl, "CFG_ERROR(%llx:%x): Route contains a loop\n",
+ res->response_route, res->response_port);
+ return;
+ default:
+ /* 5,6,7,9 and 11 are also valid error codes */
+ tb_ctl_WARN(ctl, "CFG_ERROR(%llx:%x): Unknown error\n",
+ res->response_route, res->response_port);
+ return;
+ }
+}
+
+static void cpu_to_be32_array(__be32 *dst, u32 *src, size_t len)
+{
+ int i;
+ for (i = 0; i < len; i++)
+ dst[i] = cpu_to_be32(src[i]);
+}
+
+static void be32_to_cpu_array(u32 *dst, __be32 *src, size_t len)
+{
+ int i;
+ for (i = 0; i < len; i++)
+ dst[i] = be32_to_cpu(src[i]);
+}
+
+static __be32 tb_crc(void *data, size_t len)
+{
+ return cpu_to_be32(~__crc32c_le(~0, data, len));
+}
+
+static void tb_ctl_pkg_free(struct ctl_pkg *pkg)
+{
+ if (pkg) {
+ dma_pool_free(pkg->ctl->frame_pool,
+ pkg->buffer, pkg->frame.buffer_phy);
+ kfree(pkg);
+ }
+}
+
+static struct ctl_pkg *tb_ctl_pkg_alloc(struct tb_ctl *ctl)
+{
+ struct ctl_pkg *pkg = kzalloc(sizeof(*pkg), GFP_KERNEL);
+ if (!pkg)
+ return 0;
+ pkg->ctl = ctl;
+ pkg->buffer = dma_pool_alloc(ctl->frame_pool, GFP_KERNEL,
+ &pkg->frame.buffer_phy);
+ if (!pkg->buffer) {
+ kfree(pkg);
+ return 0;
+ }
+ return pkg;
+}
+
+
+/* RX/TX handling */
+
+static void tb_ctl_tx_callback(struct tb_ring *ring, struct ring_frame *frame,
+ bool canceled)
+{
+ struct ctl_pkg *pkg = container_of(frame, typeof(*pkg), frame);
+ tb_ctl_pkg_free(pkg);
+}
+
+/**
+ * tb_cfg_tx() - transmit a packet on the control channel
+ *
+ * len must be a multiple of four.
+ *
+ * Return: Returns 0 on success or an error code on failure.
+ */
+static int tb_ctl_tx(struct tb_ctl *ctl, void *data, size_t len,
+ enum tb_cfg_pkg_type type)
+{
+ int res;
+ struct ctl_pkg *pkg;
+ if (len % 4 != 0) { /* required for le->be conversion */
+ tb_ctl_WARN(ctl, "TX: invalid size: %zu\n", len);
+ return -EINVAL;
+ }
+ if (len > TB_FRAME_SIZE - 4) { /* checksum is 4 bytes */
+ tb_ctl_WARN(ctl, "TX: packet too large: %zu/%d\n",
+ len, TB_FRAME_SIZE - 4);
+ return -EINVAL;
+ }
+ pkg = tb_ctl_pkg_alloc(ctl);
+ if (!pkg)
+ return -ENOMEM;
+ pkg->frame.callback = tb_ctl_tx_callback;
+ pkg->frame.size = len + 4;
+ pkg->frame.sof = type;
+ pkg->frame.eof = type;
+ cpu_to_be32_array(pkg->buffer, data, len / 4);
+ *(u32 *) (pkg->buffer + len) = tb_crc(pkg->buffer, len);
+
+ res = ring_tx(ctl->tx, &pkg->frame);
+ if (res) /* ring is stopped */
+ tb_ctl_pkg_free(pkg);
+ return res;
+}
+
+/**
+ * tb_ctl_handle_plug_event() - acknowledge a plug event, invoke ctl->callback
+ */
+static void tb_ctl_handle_plug_event(struct tb_ctl *ctl,
+ struct ctl_pkg *response)
+{
+ struct cfg_event_pkg *pkg = response->buffer;
+ u64 route = get_route(pkg->header);
+
+ if (check_header(response, sizeof(*pkg), TB_CFG_PKG_EVENT, route)) {
+ tb_ctl_warn(ctl, "malformed TB_CFG_PKG_EVENT\n");
+ return;
+ }
+
+ if (tb_cfg_error(ctl, route, pkg->port, TB_CFG_ERROR_ACK_PLUG_EVENT))
+ tb_ctl_warn(ctl, "could not ack plug event on %llx:%x\n",
+ route, pkg->port);
+ WARN(pkg->zero, "pkg->zero is %#x\n", pkg->zero);
+ ctl->callback(ctl->callback_data, route, pkg->port, pkg->unplug);
+}
+
+static void tb_ctl_rx_submit(struct ctl_pkg *pkg)
+{
+ ring_rx(pkg->ctl->rx, &pkg->frame); /*
+ * We ignore failures during stop.
+ * All rx packets are referenced
+ * from ctl->rx_packets, so we do
+ * not loose them.
+ */
+}
+
+static void tb_ctl_rx_callback(struct tb_ring *ring, struct ring_frame *frame,
+ bool canceled)
+{
+ struct ctl_pkg *pkg = container_of(frame, typeof(*pkg), frame);
+
+ if (canceled)
+ return; /*
+ * ring is stopped, packet is referenced from
+ * ctl->rx_packets.
+ */
+
+ if (frame->size < 4 || frame->size % 4 != 0) {
+ tb_ctl_err(pkg->ctl, "RX: invalid size %#x, dropping packet\n",
+ frame->size);
+ goto rx;
+ }
+
+ frame->size -= 4; /* remove checksum */
+ if (*(u32 *) (pkg->buffer + frame->size)
+ != tb_crc(pkg->buffer, frame->size)) {
+ tb_ctl_err(pkg->ctl,
+ "RX: checksum mismatch, dropping packet\n");
+ goto rx;
+ }
+ be32_to_cpu_array(pkg->buffer, pkg->buffer, frame->size / 4);
+
+ if (frame->eof == TB_CFG_PKG_EVENT) {
+ tb_ctl_handle_plug_event(pkg->ctl, pkg);
+ goto rx;
+ }
+ if (!kfifo_put(&pkg->ctl->response_fifo, pkg)) {
+ tb_ctl_err(pkg->ctl, "RX: fifo is full\n");
+ goto rx;
+ }
+ complete(&pkg->ctl->response_ready);
+ return;
+rx:
+ tb_ctl_rx_submit(pkg);
+}
+
+/**
+ * tb_ctl_rx() - receive a packet from the control channel
+ */
+static struct tb_cfg_result tb_ctl_rx(struct tb_ctl *ctl, void *buffer,
+ size_t length, int timeout_msec,
+ u8 route, enum tb_cfg_pkg_type type)
+{
+ struct tb_cfg_result res;
+ struct ctl_pkg *pkg;
+
+ if (!wait_for_completion_timeout(&ctl->response_ready,
+ msecs_to_jiffies(timeout_msec))) {
+ tb_ctl_WARN(ctl, "RX: timeout\n");
+ return (struct tb_cfg_result) { .err = -ETIMEDOUT };
+ }
+ if (!kfifo_get(&ctl->response_fifo, &pkg)) {
+ tb_ctl_WARN(ctl, "empty kfifo\n");
+ return (struct tb_cfg_result) { .err = -EIO };
+ }
+
+ res = parse_header(pkg, length, type, route);
+ if (!res.err)
+ memcpy(buffer, pkg->buffer, length);
+ tb_ctl_rx_submit(pkg);
+ return res;
+}
+
+
+/* public interface, alloc/start/stop/free */
+
+/**
+ * tb_ctl_alloc() - allocate a control channel
+ *
+ * cb will be invoked once for every hot plug event.
+ *
+ * Return: Returns a pointer on success or NULL on failure.
+ */
+struct tb_ctl *tb_ctl_alloc(struct tb_nhi *nhi, hotplug_cb cb, void *cb_data)
+{
+ int i;
+ struct tb_ctl *ctl = kzalloc(sizeof(*ctl), GFP_KERNEL);
+ if (!ctl)
+ return NULL;
+ ctl->nhi = nhi;
+ ctl->callback = cb;
+ ctl->callback_data = cb_data;
+
+ init_completion(&ctl->response_ready);
+ INIT_KFIFO(ctl->response_fifo);
+ ctl->frame_pool = dma_pool_create("thunderbolt_ctl", &nhi->pdev->dev,
+ TB_FRAME_SIZE, 4, 0);
+ if (!ctl->frame_pool)
+ goto err;
+
+ ctl->tx = ring_alloc_tx(nhi, 0, 10);
+ if (!ctl->tx)
+ goto err;
+
+ ctl->rx = ring_alloc_rx(nhi, 0, 10);
+ if (!ctl->rx)
+ goto err;
+
+ for (i = 0; i < TB_CTL_RX_PKG_COUNT; i++) {
+ ctl->rx_packets[i] = tb_ctl_pkg_alloc(ctl);
+ if (!ctl->rx_packets[i])
+ goto err;
+ ctl->rx_packets[i]->frame.callback = tb_ctl_rx_callback;
+ }
+
+ tb_ctl_info(ctl, "control channel created\n");
+ return ctl;
+err:
+ tb_ctl_free(ctl);
+ return NULL;
+}
+
+/**
+ * tb_ctl_free() - free a control channel
+ *
+ * Must be called after tb_ctl_stop.
+ *
+ * Must NOT be called from ctl->callback.
+ */
+void tb_ctl_free(struct tb_ctl *ctl)
+{
+ int i;
+ if (ctl->rx)
+ ring_free(ctl->rx);
+ if (ctl->tx)
+ ring_free(ctl->tx);
+
+ /* free RX packets */
+ for (i = 0; i < TB_CTL_RX_PKG_COUNT; i++)
+ tb_ctl_pkg_free(ctl->rx_packets[i]);
+
+
+ if (ctl->frame_pool)
+ dma_pool_destroy(ctl->frame_pool);
+ kfree(ctl);
+}
+
+/**
+ * tb_cfg_start() - start/resume the control channel
+ */
+void tb_ctl_start(struct tb_ctl *ctl)
+{
+ int i;
+ tb_ctl_info(ctl, "control channel starting...\n");
+ ring_start(ctl->tx); /* is used to ack hotplug packets, start first */
+ ring_start(ctl->rx);
+ for (i = 0; i < TB_CTL_RX_PKG_COUNT; i++)
+ tb_ctl_rx_submit(ctl->rx_packets[i]);
+}
+
+/**
+ * control() - pause the control channel
+ *
+ * All invocations of ctl->callback will have finished after this method
+ * returns.
+ *
+ * Must NOT be called from ctl->callback.
+ */
+void tb_ctl_stop(struct tb_ctl *ctl)
+{
+ ring_stop(ctl->rx);
+ ring_stop(ctl->tx);
+
+ if (!kfifo_is_empty(&ctl->response_fifo))
+ tb_ctl_WARN(ctl, "dangling response in response_fifo\n");
+ kfifo_reset(&ctl->response_fifo);
+ tb_ctl_info(ctl, "control channel stopped\n");
+}
+
+/* public interface, commands */
+
+/**
+ * tb_cfg_error() - send error packet
+ *
+ * Return: Returns 0 on success or an error code on failure.
+ */
+int tb_cfg_error(struct tb_ctl *ctl, u64 route, u32 port,
+ enum tb_cfg_error error)
+{
+ struct cfg_error_pkg pkg = {
+ .header = make_header(route),
+ .port = port,
+ .error = error,
+ };
+ tb_ctl_info(ctl, "resetting error on %llx:%x.\n", route, port);
+ return tb_ctl_tx(ctl, &pkg, sizeof(pkg), TB_CFG_PKG_ERROR);
+}
+
+/**
+ * tb_cfg_reset() - send a reset packet and wait for a response
+ *
+ * If the switch at route is incorrectly configured then we will not receive a
+ * reply (even though the switch will reset). The caller should check for
+ * -ETIMEDOUT and attempt to reconfigure the switch.
+ */
+struct tb_cfg_result tb_cfg_reset(struct tb_ctl *ctl, u64 route,
+ int timeout_msec)
+{
+ int err;
+ struct cfg_reset_pkg request = { .header = make_header(route) };
+ struct tb_cfg_header reply;
+
+ err = tb_ctl_tx(ctl, &request, sizeof(request), TB_CFG_PKG_RESET);
+ if (err)
+ return (struct tb_cfg_result) { .err = err };
+
+ return tb_ctl_rx(ctl, &reply, sizeof(reply), timeout_msec, route,
+ TB_CFG_PKG_RESET);
+}
+
+/**
+ * tb_cfg_read() - read from config space into buffer
+ *
+ * Offset and length are in dwords.
+ */
+struct tb_cfg_result tb_cfg_read_raw(struct tb_ctl *ctl, void *buffer,
+ u64 route, u32 port, enum tb_cfg_space space,
+ u32 offset, u32 length, int timeout_msec)
+{
+ struct tb_cfg_result res = { 0 };
+ struct cfg_read_pkg request = {
+ .header = make_header(route),
+ .addr = {
+ .port = port,
+ .space = space,
+ .offset = offset,
+ .length = length,
+ },
+ };
+ struct cfg_write_pkg reply;
+
+ res.err = tb_ctl_tx(ctl, &request, sizeof(request), TB_CFG_PKG_READ);
+ if (res.err)
+ return res;
+
+ res = tb_ctl_rx(ctl, &reply, 12 + 4 * length, timeout_msec, route,
+ TB_CFG_PKG_READ);
+ if (res.err)
+ return res;
+
+ res.response_port = reply.addr.port;
+ res.err = check_config_address(reply.addr, space, offset, length);
+ if (!res.err)
+ memcpy(buffer, &reply.data, 4 * length);
+ return res;
+}
+
+/**
+ * tb_cfg_write() - write from buffer into config space
+ *
+ * Offset and length are in dwords.
+ */
+struct tb_cfg_result tb_cfg_write_raw(struct tb_ctl *ctl, void *buffer,
+ u64 route, u32 port, enum tb_cfg_space space,
+ u32 offset, u32 length, int timeout_msec)
+{
+ struct tb_cfg_result res = { 0 };
+ struct cfg_write_pkg request = {
+ .header = make_header(route),
+ .addr = {
+ .port = port,
+ .space = space,
+ .offset = offset,
+ .length = length,
+ },
+ };
+ struct cfg_read_pkg reply;
+
+ memcpy(&request.data, buffer, length * 4);
+
+ res.err = tb_ctl_tx(ctl, &request, 12 + 4 * length, TB_CFG_PKG_WRITE);
+ if (res.err)
+ return res;
+
+ res = tb_ctl_rx(ctl, &reply, sizeof(reply), timeout_msec, route,
+ TB_CFG_PKG_WRITE);
+ if (res.err)
+ return res;
+
+ res.response_port = reply.addr.port;
+ res.err = check_config_address(reply.addr, space, offset, length);
+ return res;
+}
+
+int tb_cfg_read(struct tb_ctl *ctl, void *buffer, u64 route, u32 port,
+ enum tb_cfg_space space, u32 offset, u32 length)
+{
+ struct tb_cfg_result res = tb_cfg_read_raw(ctl, buffer, route, port,
+ space, offset, length, TB_CFG_DEFAULT_TIMEOUT);
+ if (res.err == 1) {
+ tb_cfg_print_error(ctl, &res);
+ return -EIO;
+ }
+ WARN(res.err, "tb_cfg_read: %d\n", res.err);
+ return res.err;
+}
+
+int tb_cfg_write(struct tb_ctl *ctl, void *buffer, u64 route, u32 port,
+ enum tb_cfg_space space, u32 offset, u32 length)
+{
+ struct tb_cfg_result res = tb_cfg_write_raw(ctl, buffer, route, port,
+ space, offset, length, TB_CFG_DEFAULT_TIMEOUT);
+ if (res.err == 1) {
+ tb_cfg_print_error(ctl, &res);
+ return -EIO;
+ }
+ WARN(res.err, "tb_cfg_write: %d\n", res.err);
+ return res.err;
+}
+
+/**
+ * tb_cfg_get_upstream_port() - get upstream port number of switch at route
+ *
+ * Reads the first dword from the switches TB_CFG_SWITCH config area and
+ * returns the port number from which the reply originated.
+ *
+ * Return: Returns the upstream port number on success or an error code on
+ * failure.
+ */
+int tb_cfg_get_upstream_port(struct tb_ctl *ctl, u64 route)
+{
+ u32 dummy;
+ struct tb_cfg_result res = tb_cfg_read_raw(ctl, &dummy, route, 0,
+ TB_CFG_SWITCH, 0, 1,
+ TB_CFG_DEFAULT_TIMEOUT);
+ if (res.err == 1)
+ return -EIO;
+ if (res.err)
+ return res.err;
+ return res.response_port;
+}
diff --git a/drivers/thunderbolt/ctl.h b/drivers/thunderbolt/ctl.h
new file mode 100644
index 0000000..ba87d6e
--- /dev/null
+++ b/drivers/thunderbolt/ctl.h
@@ -0,0 +1,75 @@
+/*
+ * Thunderbolt Cactus Ridge driver - control channel and configuration commands
+ *
+ * Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com>
+ */
+
+#ifndef _TB_CFG
+#define _TB_CFG
+
+#include "nhi.h"
+
+/* control channel */
+struct tb_ctl;
+
+typedef void (*hotplug_cb)(void *data, u64 route, u8 port, bool unplug);
+
+struct tb_ctl *tb_ctl_alloc(struct tb_nhi *nhi, hotplug_cb cb, void *cb_data);
+void tb_ctl_start(struct tb_ctl *ctl);
+void tb_ctl_stop(struct tb_ctl *ctl);
+void tb_ctl_free(struct tb_ctl *ctl);
+
+/* configuration commands */
+
+#define TB_CFG_DEFAULT_TIMEOUT 5000 /* msec */
+
+enum tb_cfg_space {
+ TB_CFG_HOPS = 0,
+ TB_CFG_PORT = 1,
+ TB_CFG_SWITCH = 2,
+ TB_CFG_COUNTERS = 3,
+};
+
+enum tb_cfg_error {
+ TB_CFG_ERROR_PORT_NOT_CONNECTED = 0,
+ TB_CFG_ERROR_INVALID_CONFIG_SPACE = 2,
+ TB_CFG_ERROR_NO_SUCH_PORT = 4,
+ TB_CFG_ERROR_ACK_PLUG_EVENT = 7, /* send as reply to TB_CFG_PKG_EVENT */
+ TB_CFG_ERROR_LOOP = 8,
+};
+
+struct tb_cfg_result {
+ u64 response_route;
+ u32 response_port; /*
+ * If err = 1 then this is the port that send the
+ * error.
+ * If err = 0 and if this was a cfg_read/write then
+ * this is the the upstream port of the responding
+ * switch.
+ * Otherwise the field is set to zero.
+ */
+ int err; /* negative errors, 0 for success, 1 for tb errors */
+ enum tb_cfg_error tb_error; /* valid if err == 1 */
+};
+
+
+int tb_cfg_error(struct tb_ctl *ctl, u64 route, u32 port,
+ enum tb_cfg_error error);
+struct tb_cfg_result tb_cfg_reset(struct tb_ctl *ctl, u64 route,
+ int timeout_msec);
+struct tb_cfg_result tb_cfg_read_raw(struct tb_ctl *ctl, void *buffer,
+ u64 route, u32 port,
+ enum tb_cfg_space space, u32 offset,
+ u32 length, int timeout_msec);
+struct tb_cfg_result tb_cfg_write_raw(struct tb_ctl *ctl, void *buffer,
+ u64 route, u32 port,
+ enum tb_cfg_space space, u32 offset,
+ u32 length, int timeout_msec);
+int tb_cfg_read(struct tb_ctl *ctl, void *buffer, u64 route, u32 port,
+ enum tb_cfg_space space, u32 offset, u32 length);
+int tb_cfg_write(struct tb_ctl *ctl, void *buffer, u64 route, u32 port,
+ enum tb_cfg_space space, u32 offset, u32 length);
+int tb_cfg_get_upstream_port(struct tb_ctl *ctl, u64 route);
+
+
+#endif

View File

@ -0,0 +1,262 @@
From: Andreas Noever <andreas.noever@gmail.com>
Date: Tue, 3 Jun 2014 22:04:00 +0200
Subject: [03/31] thunderbolt: Setup control channel
Origin: https://git.kernel.org/linus/d6cc51cd1a4aed1d9e2dd66d643d729acb4be560
Add struct tb which will contain our view of the thunderbolt bus. For
now it just contains a pointer to the control channel and a workqueue
for hotplug events.
Add thunderbolt_alloc_and_start() and thunderbolt_shutdown_and_free()
which are responsible for setup and teardown of struct tb.
Signed-off-by: Andreas Noever <andreas.noever@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
drivers/thunderbolt/Makefile | 2 +-
drivers/thunderbolt/nhi.c | 18 +++++-
drivers/thunderbolt/tb.c | 134 +++++++++++++++++++++++++++++++++++++++++++
drivers/thunderbolt/tb.h | 35 +++++++++++
4 files changed, 186 insertions(+), 3 deletions(-)
create mode 100644 drivers/thunderbolt/tb.c
create mode 100644 drivers/thunderbolt/tb.h
diff --git a/drivers/thunderbolt/Makefile b/drivers/thunderbolt/Makefile
index 4b21c99..1f996bb 100644
--- a/drivers/thunderbolt/Makefile
+++ b/drivers/thunderbolt/Makefile
@@ -1,3 +1,3 @@
obj-${CONFIG_THUNDERBOLT} := thunderbolt.o
-thunderbolt-objs := nhi.o ctl.o
+thunderbolt-objs := nhi.o ctl.o tb.o
diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c
index 11070ff..d2b9ce8 100644
--- a/drivers/thunderbolt/nhi.c
+++ b/drivers/thunderbolt/nhi.c
@@ -16,6 +16,7 @@
#include "nhi.h"
#include "nhi_regs.h"
+#include "tb.h"
#define RING_TYPE(ring) ((ring)->is_tx ? "TX ring" : "RX ring")
@@ -517,6 +518,7 @@ static void nhi_shutdown(struct tb_nhi *nhi)
static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
struct tb_nhi *nhi;
+ struct tb *tb;
int res;
res = pcim_enable_device(pdev);
@@ -575,14 +577,26 @@ static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id)
/* magic value - clock related? */
iowrite32(3906250 / 10000, nhi->iobase + 0x38c00);
- pci_set_drvdata(pdev, nhi);
+ dev_info(&nhi->pdev->dev, "NHI initialized, starting thunderbolt\n");
+ tb = thunderbolt_alloc_and_start(nhi);
+ if (!tb) {
+ /*
+ * At this point the RX/TX rings might already have been
+ * activated. Do a proper shutdown.
+ */
+ nhi_shutdown(nhi);
+ return -EIO;
+ }
+ pci_set_drvdata(pdev, tb);
return 0;
}
static void nhi_remove(struct pci_dev *pdev)
{
- struct tb_nhi *nhi = pci_get_drvdata(pdev);
+ struct tb *tb = pci_get_drvdata(pdev);
+ struct tb_nhi *nhi = tb->nhi;
+ thunderbolt_shutdown_and_free(tb);
nhi_shutdown(nhi);
}
diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c
new file mode 100644
index 0000000..6920979
--- /dev/null
+++ b/drivers/thunderbolt/tb.c
@@ -0,0 +1,134 @@
+/*
+ * Thunderbolt Cactus Ridge driver - bus logic (NHI independent)
+ *
+ * Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com>
+ */
+
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/delay.h>
+
+#include "tb.h"
+
+/* hotplug handling */
+
+struct tb_hotplug_event {
+ struct work_struct work;
+ struct tb *tb;
+ u64 route;
+ u8 port;
+ bool unplug;
+};
+
+/**
+ * tb_handle_hotplug() - handle hotplug event
+ *
+ * Executes on tb->wq.
+ */
+static void tb_handle_hotplug(struct work_struct *work)
+{
+ struct tb_hotplug_event *ev = container_of(work, typeof(*ev), work);
+ struct tb *tb = ev->tb;
+ mutex_lock(&tb->lock);
+ if (!tb->hotplug_active)
+ goto out; /* during init, suspend or shutdown */
+
+ /* do nothing for now */
+out:
+ mutex_unlock(&tb->lock);
+ kfree(ev);
+}
+
+/**
+ * tb_schedule_hotplug_handler() - callback function for the control channel
+ *
+ * Delegates to tb_handle_hotplug.
+ */
+static void tb_schedule_hotplug_handler(void *data, u64 route, u8 port,
+ bool unplug)
+{
+ struct tb *tb = data;
+ struct tb_hotplug_event *ev = kmalloc(sizeof(*ev), GFP_KERNEL);
+ if (!ev)
+ return;
+ INIT_WORK(&ev->work, tb_handle_hotplug);
+ ev->tb = tb;
+ ev->route = route;
+ ev->port = port;
+ ev->unplug = unplug;
+ queue_work(tb->wq, &ev->work);
+}
+
+/**
+ * thunderbolt_shutdown_and_free() - shutdown everything
+ *
+ * Free all switches and the config channel.
+ *
+ * Used in the error path of thunderbolt_alloc_and_start.
+ */
+void thunderbolt_shutdown_and_free(struct tb *tb)
+{
+ mutex_lock(&tb->lock);
+
+ if (tb->ctl) {
+ tb_ctl_stop(tb->ctl);
+ tb_ctl_free(tb->ctl);
+ }
+ tb->ctl = NULL;
+ tb->hotplug_active = false; /* signal tb_handle_hotplug to quit */
+
+ /* allow tb_handle_hotplug to acquire the lock */
+ mutex_unlock(&tb->lock);
+ if (tb->wq) {
+ flush_workqueue(tb->wq);
+ destroy_workqueue(tb->wq);
+ tb->wq = NULL;
+ }
+ mutex_destroy(&tb->lock);
+ kfree(tb);
+}
+
+/**
+ * thunderbolt_alloc_and_start() - setup the thunderbolt bus
+ *
+ * Allocates a tb_cfg control channel, initializes the root switch, enables
+ * plug events and activates pci devices.
+ *
+ * Return: Returns NULL on error.
+ */
+struct tb *thunderbolt_alloc_and_start(struct tb_nhi *nhi)
+{
+ struct tb *tb;
+
+ tb = kzalloc(sizeof(*tb), GFP_KERNEL);
+ if (!tb)
+ return NULL;
+
+ tb->nhi = nhi;
+ mutex_init(&tb->lock);
+ mutex_lock(&tb->lock);
+
+ tb->wq = alloc_ordered_workqueue("thunderbolt", 0);
+ if (!tb->wq)
+ goto err_locked;
+
+ tb->ctl = tb_ctl_alloc(tb->nhi, tb_schedule_hotplug_handler, tb);
+ if (!tb->ctl)
+ goto err_locked;
+ /*
+ * tb_schedule_hotplug_handler may be called as soon as the config
+ * channel is started. Thats why we have to hold the lock here.
+ */
+ tb_ctl_start(tb->ctl);
+
+ /* Allow tb_handle_hotplug to progress events */
+ tb->hotplug_active = true;
+ mutex_unlock(&tb->lock);
+ return tb;
+
+err_locked:
+ mutex_unlock(&tb->lock);
+ thunderbolt_shutdown_and_free(tb);
+ return NULL;
+}
+
diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h
new file mode 100644
index 0000000..6055dfd
--- /dev/null
+++ b/drivers/thunderbolt/tb.h
@@ -0,0 +1,35 @@
+/*
+ * Thunderbolt Cactus Ridge driver - bus logic (NHI independent)
+ *
+ * Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com>
+ */
+
+#ifndef TB_H_
+#define TB_H_
+
+#include "ctl.h"
+
+/**
+ * struct tb - main thunderbolt bus structure
+ */
+struct tb {
+ struct mutex lock; /*
+ * Big lock. Must be held when accessing cfg or
+ * any struct tb_switch / struct tb_port.
+ */
+ struct tb_nhi *nhi;
+ struct tb_ctl *ctl;
+ struct workqueue_struct *wq; /* ordered workqueue for plug events */
+ bool hotplug_active; /*
+ * tb_handle_hotplug will stop progressing plug
+ * events and exit if this is not set (it needs to
+ * acquire the lock one more time). Used to drain
+ * wq after cfg has been paused.
+ */
+
+};
+
+struct tb *thunderbolt_alloc_and_start(struct tb_nhi *nhi);
+void thunderbolt_shutdown_and_free(struct tb *tb);
+
+#endif

View File

@ -0,0 +1,262 @@
From: Andreas Noever <andreas.noever@gmail.com>
Date: Tue, 3 Jun 2014 22:04:01 +0200
Subject: [04/31] thunderbolt: Add tb_regs.h
Origin: https://git.kernel.org/linus/7adf60972c692b0b3d0958cd7322e22a67187111
Every thunderbolt device consists (logically) of a switch with multiple
ports. Every port contains up to four config regions (HOPS, PORT,
SWITCH, COUNTERS) which are used to configure the device.
The tb_regs.h file contains all known registers and capabilities from
these config regions.
Signed-off-by: Andreas Noever <andreas.noever@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
drivers/thunderbolt/tb.c | 5 +
drivers/thunderbolt/tb_regs.h | 213 ++++++++++++++++++++++++++++++++++++++++++
2 files changed, 218 insertions(+)
create mode 100644 drivers/thunderbolt/tb_regs.h
diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c
index 6920979..164dea0 100644
--- a/drivers/thunderbolt/tb.c
+++ b/drivers/thunderbolt/tb.c
@@ -9,6 +9,7 @@
#include <linux/delay.h>
#include "tb.h"
+#include "tb_regs.h"
/* hotplug handling */
@@ -100,6 +101,10 @@ struct tb *thunderbolt_alloc_and_start(struct tb_nhi *nhi)
{
struct tb *tb;
+ BUILD_BUG_ON(sizeof(struct tb_regs_switch_header) != 5 * 4);
+ BUILD_BUG_ON(sizeof(struct tb_regs_port_header) != 8 * 4);
+ BUILD_BUG_ON(sizeof(struct tb_regs_hop) != 2 * 4);
+
tb = kzalloc(sizeof(*tb), GFP_KERNEL);
if (!tb)
return NULL;
diff --git a/drivers/thunderbolt/tb_regs.h b/drivers/thunderbolt/tb_regs.h
new file mode 100644
index 0000000..6577af7
--- /dev/null
+++ b/drivers/thunderbolt/tb_regs.h
@@ -0,0 +1,213 @@
+/*
+ * Thunderbolt Cactus Ridge driver - Port/Switch config area registers
+ *
+ * Every thunderbolt device consists (logically) of a switch with multiple
+ * ports. Every port contains up to four config regions (HOPS, PORT, SWITCH,
+ * COUNTERS) which are used to configure the device.
+ *
+ * Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com>
+ */
+
+#ifndef _TB_REGS
+#define _TB_REGS
+
+#include <linux/types.h>
+
+
+#define TB_ROUTE_SHIFT 8 /* number of bits in a port entry of a route */
+
+
+/*
+ * TODO: should be 63? But we do not know how to receive frames larger than 256
+ * bytes at the frame level. (header + checksum = 16, 60*4 = 240)
+ */
+#define TB_MAX_CONFIG_RW_LENGTH 60
+
+enum tb_cap {
+ TB_CAP_PHY = 0x0001,
+ TB_CAP_TIME1 = 0x0003,
+ TB_CAP_PCIE = 0x0004,
+ TB_CAP_I2C = 0x0005,
+ TB_CAP_PLUG_EVENTS = 0x0105, /* also EEPROM */
+ TB_CAP_TIME2 = 0x0305,
+ TB_CAL_IECS = 0x0405,
+ TB_CAP_LINK_CONTROLLER = 0x0605, /* also IECS */
+};
+
+enum tb_port_state {
+ TB_PORT_DISABLED = 0, /* tb_cap_phy.disable == 1 */
+ TB_PORT_CONNECTING = 1, /* retry */
+ TB_PORT_UP = 2,
+ TB_PORT_UNPLUGGED = 7,
+};
+
+/* capability headers */
+
+struct tb_cap_basic {
+ u8 next;
+ /* enum tb_cap cap:8; prevent "narrower than values of its type" */
+ u8 cap; /* if cap == 0x05 then we have a extended capability */
+} __packed;
+
+struct tb_cap_extended_short {
+ u8 next; /* if next and length are zero then we have a long cap */
+ enum tb_cap cap:16;
+ u8 length;
+} __packed;
+
+struct tb_cap_extended_long {
+ u8 zero1;
+ enum tb_cap cap:16;
+ u8 zero2;
+ u16 next;
+ u16 length;
+} __packed;
+
+/* capabilities */
+
+struct tb_cap_link_controller {
+ struct tb_cap_extended_long cap_header;
+ u32 count:4; /* number of link controllers */
+ u32 unknown1:4;
+ u32 base_offset:8; /*
+ * offset (into this capability) of the configuration
+ * area of the first link controller
+ */
+ u32 length:12; /* link controller configuration area length */
+ u32 unknown2:4; /* TODO check that length is correct */
+} __packed;
+
+struct tb_cap_phy {
+ struct tb_cap_basic cap_header;
+ u32 unknown1:16;
+ u32 unknown2:14;
+ bool disable:1;
+ u32 unknown3:11;
+ enum tb_port_state state:4;
+ u32 unknown4:2;
+} __packed;
+
+struct tb_eeprom_ctl {
+ bool clock:1; /* send pulse to transfer one bit */
+ bool access_low:1; /* set to 0 before access */
+ bool data_out:1; /* to eeprom */
+ bool data_in:1; /* from eeprom */
+ bool access_high:1; /* set to 1 before access */
+ bool not_present:1; /* should be 0 */
+ bool unknown1:1;
+ bool present:1; /* should be 1 */
+ u32 unknown2:24;
+} __packed;
+
+struct tb_cap_plug_events {
+ struct tb_cap_extended_short cap_header;
+ u32 __unknown1:2;
+ u32 plug_events:5;
+ u32 __unknown2:25;
+ u32 __unknown3;
+ u32 __unknown4;
+ struct tb_eeprom_ctl eeprom_ctl;
+ u32 __unknown5[7];
+ u32 drom_offset; /* 32 bit register, but eeprom addresses are 16 bit */
+} __packed;
+
+/* device headers */
+
+/* Present on port 0 in TB_CFG_SWITCH at address zero. */
+struct tb_regs_switch_header {
+ /* DWORD 0 */
+ u16 vendor_id;
+ u16 device_id;
+ /* DWORD 1 */
+ u32 first_cap_offset:8;
+ u32 upstream_port_number:6;
+ u32 max_port_number:6;
+ u32 depth:3;
+ u32 __unknown1:1;
+ u32 revision:8;
+ /* DWORD 2 */
+ u32 route_lo;
+ /* DWORD 3 */
+ u32 route_hi:31;
+ bool enabled:1;
+ /* DWORD 4 */
+ u32 plug_events_delay:8; /*
+ * RW, pause between plug events in
+ * milliseconds. Writing 0x00 is interpreted
+ * as 255ms.
+ */
+ u32 __unknown4:16;
+ u32 thunderbolt_version:8;
+} __packed;
+
+enum tb_port_type {
+ TB_TYPE_INACTIVE = 0x000000,
+ TB_TYPE_PORT = 0x000001,
+ TB_TYPE_NHI = 0x000002,
+ /* TB_TYPE_ETHERNET = 0x020000, lower order bits are not known */
+ /* TB_TYPE_SATA = 0x080000, lower order bits are not known */
+ TB_TYPE_DP_HDMI_IN = 0x0e0101,
+ TB_TYPE_DP_HDMI_OUT = 0x0e0102,
+ TB_TYPE_PCIE_DOWN = 0x100101,
+ TB_TYPE_PCIE_UP = 0x100102,
+ /* TB_TYPE_USB = 0x200000, lower order bits are not known */
+};
+
+/* Present on every port in TB_CF_PORT at address zero. */
+struct tb_regs_port_header {
+ /* DWORD 0 */
+ u16 vendor_id;
+ u16 device_id;
+ /* DWORD 1 */
+ u32 first_cap_offset:8;
+ u32 max_counters:11;
+ u32 __unknown1:5;
+ u32 revision:8;
+ /* DWORD 2 */
+ enum tb_port_type type:24;
+ u32 thunderbolt_version:8;
+ /* DWORD 3 */
+ u32 __unknown2:20;
+ u32 port_number:6;
+ u32 __unknown3:6;
+ /* DWORD 4 */
+ u32 nfc_credits;
+ /* DWORD 5 */
+ u32 max_in_hop_id:11;
+ u32 max_out_hop_id:11;
+ u32 __unkown4:10;
+ /* DWORD 6 */
+ u32 __unknown5;
+ /* DWORD 7 */
+ u32 __unknown6;
+
+} __packed;
+
+/* Hop register from TB_CFG_HOPS. 8 byte per entry. */
+struct tb_regs_hop {
+ /* DWORD 0 */
+ u32 next_hop:11; /*
+ * hop to take after sending the packet through
+ * out_port (on the incoming port of the next switch)
+ */
+ u32 out_port:6; /* next port of the path (on the same switch) */
+ u32 initial_credits:8;
+ u32 unknown1:6; /* set to zero */
+ bool enable:1;
+
+ /* DWORD 1 */
+ u32 weight:4;
+ u32 unknown2:4; /* set to zero */
+ u32 priority:3;
+ bool drop_packages:1;
+ u32 counter:11; /* index into TB_CFG_COUNTERS on this port */
+ bool counter_enable:1;
+ bool ingress_fc:1;
+ bool egress_fc:1;
+ bool ingress_shared_buffer:1;
+ bool egress_shared_buffer:1;
+ u32 unknown3:4; /* set to zero */
+} __packed;
+
+
+#endif

View File

@ -0,0 +1,412 @@
From: Andreas Noever <andreas.noever@gmail.com>
Date: Tue, 3 Jun 2014 22:04:02 +0200
Subject: [05/31] thunderbolt: Initialize root switch and ports
Origin: https://git.kernel.org/linus/a25c8b2fc9636aaf29d9d9d89f92cdfd27a2a23d
This patch adds the structures tb_switch and tb_port as well as code to
initialize the root switch.
Signed-off-by: Andreas Noever <andreas.noever@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
drivers/thunderbolt/Makefile | 2 +-
drivers/thunderbolt/switch.c | 186 +++++++++++++++++++++++++++++++++++++++++++
drivers/thunderbolt/tb.c | 8 ++
drivers/thunderbolt/tb.h | 138 ++++++++++++++++++++++++++++++++
4 files changed, 333 insertions(+), 1 deletion(-)
create mode 100644 drivers/thunderbolt/switch.c
diff --git a/drivers/thunderbolt/Makefile b/drivers/thunderbolt/Makefile
index 1f996bb..4ac18d9 100644
--- a/drivers/thunderbolt/Makefile
+++ b/drivers/thunderbolt/Makefile
@@ -1,3 +1,3 @@
obj-${CONFIG_THUNDERBOLT} := thunderbolt.o
-thunderbolt-objs := nhi.o ctl.o tb.o
+thunderbolt-objs := nhi.o ctl.o tb.o switch.o
diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c
new file mode 100644
index 0000000..e89121f
--- /dev/null
+++ b/drivers/thunderbolt/switch.c
@@ -0,0 +1,186 @@
+/*
+ * Thunderbolt Cactus Ridge driver - switch/port utility functions
+ *
+ * Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com>
+ */
+
+#include <linux/delay.h>
+
+#include "tb.h"
+
+/* port utility functions */
+
+static const char *tb_port_type(struct tb_regs_port_header *port)
+{
+ switch (port->type >> 16) {
+ case 0:
+ switch ((u8) port->type) {
+ case 0:
+ return "Inactive";
+ case 1:
+ return "Port";
+ case 2:
+ return "NHI";
+ default:
+ return "unknown";
+ }
+ case 0x2:
+ return "Ethernet";
+ case 0x8:
+ return "SATA";
+ case 0xe:
+ return "DP/HDMI";
+ case 0x10:
+ return "PCIe";
+ case 0x20:
+ return "USB";
+ default:
+ return "unknown";
+ }
+}
+
+static void tb_dump_port(struct tb *tb, struct tb_regs_port_header *port)
+{
+ tb_info(tb,
+ " Port %d: %x:%x (Revision: %d, TB Version: %d, Type: %s (%#x))\n",
+ port->port_number, port->vendor_id, port->device_id,
+ port->revision, port->thunderbolt_version, tb_port_type(port),
+ port->type);
+ tb_info(tb, " Max hop id (in/out): %d/%d\n",
+ port->max_in_hop_id, port->max_out_hop_id);
+ tb_info(tb, " Max counters: %d\n", port->max_counters);
+ tb_info(tb, " NFC Credits: %#x\n", port->nfc_credits);
+}
+
+/**
+ * tb_init_port() - initialize a port
+ *
+ * This is a helper method for tb_switch_alloc. Does not check or initialize
+ * any downstream switches.
+ *
+ * Return: Returns 0 on success or an error code on failure.
+ */
+static int tb_init_port(struct tb_switch *sw, u8 port_nr)
+{
+ int res;
+ struct tb_port *port = &sw->ports[port_nr];
+ port->sw = sw;
+ port->port = port_nr;
+ port->remote = NULL;
+ res = tb_port_read(port, &port->config, TB_CFG_PORT, 0, 8);
+ if (res)
+ return res;
+
+ tb_dump_port(sw->tb, &port->config);
+
+ /* TODO: Read dual link port, DP port and more from EEPROM. */
+ return 0;
+
+}
+
+/* switch utility functions */
+
+static void tb_dump_switch(struct tb *tb, struct tb_regs_switch_header *sw)
+{
+ tb_info(tb,
+ " Switch: %x:%x (Revision: %d, TB Version: %d)\n",
+ sw->vendor_id, sw->device_id, sw->revision,
+ sw->thunderbolt_version);
+ tb_info(tb, " Max Port Number: %d\n", sw->max_port_number);
+ tb_info(tb, " Config:\n");
+ tb_info(tb,
+ " Upstream Port Number: %d Depth: %d Route String: %#llx Enabled: %d, PlugEventsDelay: %dms\n",
+ sw->upstream_port_number, sw->depth,
+ (((u64) sw->route_hi) << 32) | sw->route_lo,
+ sw->enabled, sw->plug_events_delay);
+ tb_info(tb,
+ " unknown1: %#x unknown4: %#x\n",
+ sw->__unknown1, sw->__unknown4);
+}
+
+/**
+ * tb_switch_free() - free a tb_switch and all downstream switches
+ */
+void tb_switch_free(struct tb_switch *sw)
+{
+ int i;
+ /* port 0 is the switch itself and never has a remote */
+ for (i = 1; i <= sw->config.max_port_number; i++) {
+ if (tb_is_upstream_port(&sw->ports[i]))
+ continue;
+ if (sw->ports[i].remote)
+ tb_switch_free(sw->ports[i].remote->sw);
+ sw->ports[i].remote = NULL;
+ }
+
+ kfree(sw->ports);
+ kfree(sw);
+}
+
+/**
+ * tb_switch_alloc() - allocate and initialize a switch
+ *
+ * Return: Returns a NULL on failure.
+ */
+struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route)
+{
+ int i;
+ struct tb_switch *sw;
+ int upstream_port = tb_cfg_get_upstream_port(tb->ctl, route);
+ if (upstream_port < 0)
+ return NULL;
+
+ sw = kzalloc(sizeof(*sw), GFP_KERNEL);
+ if (!sw)
+ return NULL;
+
+ sw->tb = tb;
+ if (tb_cfg_read(tb->ctl, &sw->config, route, 0, 2, 0, 5))
+ goto err;
+ tb_info(tb,
+ "initializing Switch at %#llx (depth: %d, up port: %d)\n",
+ route, tb_route_length(route), upstream_port);
+ tb_info(tb, "old switch config:\n");
+ tb_dump_switch(tb, &sw->config);
+
+ /* configure switch */
+ sw->config.upstream_port_number = upstream_port;
+ sw->config.depth = tb_route_length(route);
+ sw->config.route_lo = route;
+ sw->config.route_hi = route >> 32;
+ sw->config.enabled = 1;
+ /* from here on we may use the tb_sw_* functions & macros */
+
+ if (sw->config.vendor_id != 0x8086)
+ tb_sw_warn(sw, "unknown switch vendor id %#x\n",
+ sw->config.vendor_id);
+
+ if (sw->config.device_id != 0x1547 && sw->config.device_id != 0x1549)
+ tb_sw_warn(sw, "unsupported switch device id %#x\n",
+ sw->config.device_id);
+
+ /* upload configuration */
+ if (tb_sw_write(sw, 1 + (u32 *) &sw->config, TB_CFG_SWITCH, 1, 3))
+ goto err;
+
+ /* initialize ports */
+ sw->ports = kcalloc(sw->config.max_port_number + 1, sizeof(*sw->ports),
+ GFP_KERNEL);
+ if (!sw->ports)
+ goto err;
+
+ for (i = 0; i <= sw->config.max_port_number; i++) {
+ if (tb_init_port(sw, i))
+ goto err;
+ /* TODO: check if port is disabled (EEPROM) */
+ }
+
+ /* TODO: I2C, IECS, EEPROM, link controller */
+
+ return sw;
+err:
+ kfree(sw->ports);
+ kfree(sw);
+ return NULL;
+}
+
diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c
index 164dea0..f1b6100 100644
--- a/drivers/thunderbolt/tb.c
+++ b/drivers/thunderbolt/tb.c
@@ -71,6 +71,10 @@ void thunderbolt_shutdown_and_free(struct tb *tb)
{
mutex_lock(&tb->lock);
+ if (tb->root_switch)
+ tb_switch_free(tb->root_switch);
+ tb->root_switch = NULL;
+
if (tb->ctl) {
tb_ctl_stop(tb->ctl);
tb_ctl_free(tb->ctl);
@@ -126,6 +130,10 @@ struct tb *thunderbolt_alloc_and_start(struct tb_nhi *nhi)
*/
tb_ctl_start(tb->ctl);
+ tb->root_switch = tb_switch_alloc(tb, 0);
+ if (!tb->root_switch)
+ goto err_locked;
+
/* Allow tb_handle_hotplug to progress events */
tb->hotplug_active = true;
mutex_unlock(&tb->lock);
diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h
index 6055dfd..389dbb4 100644
--- a/drivers/thunderbolt/tb.h
+++ b/drivers/thunderbolt/tb.h
@@ -7,9 +7,31 @@
#ifndef TB_H_
#define TB_H_
+#include <linux/pci.h>
+
+#include "tb_regs.h"
#include "ctl.h"
/**
+ * struct tb_switch - a thunderbolt switch
+ */
+struct tb_switch {
+ struct tb_regs_switch_header config;
+ struct tb_port *ports;
+ struct tb *tb;
+};
+
+/**
+ * struct tb_port - a thunderbolt port, part of a tb_switch
+ */
+struct tb_port {
+ struct tb_regs_port_header config;
+ struct tb_switch *sw;
+ struct tb_port *remote; /* remote port, NULL if not connected */
+ u8 port; /* port number on switch */
+};
+
+/**
* struct tb - main thunderbolt bus structure
*/
struct tb {
@@ -20,6 +42,7 @@ struct tb {
struct tb_nhi *nhi;
struct tb_ctl *ctl;
struct workqueue_struct *wq; /* ordered workqueue for plug events */
+ struct tb_switch *root_switch;
bool hotplug_active; /*
* tb_handle_hotplug will stop progressing plug
* events and exit if this is not set (it needs to
@@ -29,7 +52,122 @@ struct tb {
};
+/* helper functions & macros */
+
+/**
+ * tb_upstream_port() - return the upstream port of a switch
+ *
+ * Every switch has an upstream port (for the root switch it is the NHI).
+ *
+ * During switch alloc/init tb_upstream_port()->remote may be NULL, even for
+ * non root switches (on the NHI port remote is always NULL).
+ *
+ * Return: Returns the upstream port of the switch.
+ */
+static inline struct tb_port *tb_upstream_port(struct tb_switch *sw)
+{
+ return &sw->ports[sw->config.upstream_port_number];
+}
+
+static inline u64 tb_route(struct tb_switch *sw)
+{
+ return ((u64) sw->config.route_hi) << 32 | sw->config.route_lo;
+}
+
+static inline int tb_sw_read(struct tb_switch *sw, void *buffer,
+ enum tb_cfg_space space, u32 offset, u32 length)
+{
+ return tb_cfg_read(sw->tb->ctl,
+ buffer,
+ tb_route(sw),
+ 0,
+ space,
+ offset,
+ length);
+}
+
+static inline int tb_sw_write(struct tb_switch *sw, void *buffer,
+ enum tb_cfg_space space, u32 offset, u32 length)
+{
+ return tb_cfg_write(sw->tb->ctl,
+ buffer,
+ tb_route(sw),
+ 0,
+ space,
+ offset,
+ length);
+}
+
+static inline int tb_port_read(struct tb_port *port, void *buffer,
+ enum tb_cfg_space space, u32 offset, u32 length)
+{
+ return tb_cfg_read(port->sw->tb->ctl,
+ buffer,
+ tb_route(port->sw),
+ port->port,
+ space,
+ offset,
+ length);
+}
+
+static inline int tb_port_write(struct tb_port *port, void *buffer,
+ enum tb_cfg_space space, u32 offset, u32 length)
+{
+ return tb_cfg_write(port->sw->tb->ctl,
+ buffer,
+ tb_route(port->sw),
+ port->port,
+ space,
+ offset,
+ length);
+}
+
+#define tb_err(tb, fmt, arg...) dev_err(&(tb)->nhi->pdev->dev, fmt, ## arg)
+#define tb_WARN(tb, fmt, arg...) dev_WARN(&(tb)->nhi->pdev->dev, fmt, ## arg)
+#define tb_warn(tb, fmt, arg...) dev_warn(&(tb)->nhi->pdev->dev, fmt, ## arg)
+#define tb_info(tb, fmt, arg...) dev_info(&(tb)->nhi->pdev->dev, fmt, ## arg)
+
+
+#define __TB_SW_PRINT(level, sw, fmt, arg...) \
+ do { \
+ struct tb_switch *__sw = (sw); \
+ level(__sw->tb, "%llx: " fmt, \
+ tb_route(__sw), ## arg); \
+ } while (0)
+#define tb_sw_WARN(sw, fmt, arg...) __TB_SW_PRINT(tb_WARN, sw, fmt, ##arg)
+#define tb_sw_warn(sw, fmt, arg...) __TB_SW_PRINT(tb_warn, sw, fmt, ##arg)
+#define tb_sw_info(sw, fmt, arg...) __TB_SW_PRINT(tb_info, sw, fmt, ##arg)
+
+
+#define __TB_PORT_PRINT(level, _port, fmt, arg...) \
+ do { \
+ struct tb_port *__port = (_port); \
+ level(__port->sw->tb, "%llx:%x: " fmt, \
+ tb_route(__port->sw), __port->port, ## arg); \
+ } while (0)
+#define tb_port_WARN(port, fmt, arg...) \
+ __TB_PORT_PRINT(tb_WARN, port, fmt, ##arg)
+#define tb_port_warn(port, fmt, arg...) \
+ __TB_PORT_PRINT(tb_warn, port, fmt, ##arg)
+#define tb_port_info(port, fmt, arg...) \
+ __TB_PORT_PRINT(tb_info, port, fmt, ##arg)
+
+
struct tb *thunderbolt_alloc_and_start(struct tb_nhi *nhi);
void thunderbolt_shutdown_and_free(struct tb *tb);
+struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route);
+void tb_switch_free(struct tb_switch *sw);
+
+
+static inline int tb_route_length(u64 route)
+{
+ return (fls64(route) + TB_ROUTE_SHIFT - 1) / TB_ROUTE_SHIFT;
+}
+
+static inline bool tb_is_upstream_port(struct tb_port *port)
+{
+ return port == tb_upstream_port(port->sw);
+}
+
#endif

View File

@ -0,0 +1,162 @@
From: Andreas Noever <andreas.noever@gmail.com>
Date: Tue, 3 Jun 2014 22:04:03 +0200
Subject: [06/31] thunderbolt: Add thunderbolt capability handling
Origin: https://git.kernel.org/linus/e2b8785ed312dad3a18279a3e88435fc269658c1
Thunderbolt config areas contain capability lists similar to those found
on pci devices. This patch introduces a tb_find_cap utility method to
search for capabilities.
Signed-off-by: Andreas Noever <andreas.noever@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
drivers/thunderbolt/Makefile | 2 +-
drivers/thunderbolt/cap.c | 116 +++++++++++++++++++++++++++++++++++++++++++
drivers/thunderbolt/tb.h | 2 +
3 files changed, 119 insertions(+), 1 deletion(-)
create mode 100644 drivers/thunderbolt/cap.c
diff --git a/drivers/thunderbolt/Makefile b/drivers/thunderbolt/Makefile
index 4ac18d9..617b314 100644
--- a/drivers/thunderbolt/Makefile
+++ b/drivers/thunderbolt/Makefile
@@ -1,3 +1,3 @@
obj-${CONFIG_THUNDERBOLT} := thunderbolt.o
-thunderbolt-objs := nhi.o ctl.o tb.o switch.o
+thunderbolt-objs := nhi.o ctl.o tb.o switch.o cap.o
diff --git a/drivers/thunderbolt/cap.c b/drivers/thunderbolt/cap.c
new file mode 100644
index 0000000..a7b47e7
--- /dev/null
+++ b/drivers/thunderbolt/cap.c
@@ -0,0 +1,116 @@
+/*
+ * Thunderbolt Cactus Ridge driver - capabilities lookup
+ *
+ * Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com>
+ */
+
+#include <linux/slab.h>
+#include <linux/errno.h>
+
+#include "tb.h"
+
+
+struct tb_cap_any {
+ union {
+ struct tb_cap_basic basic;
+ struct tb_cap_extended_short extended_short;
+ struct tb_cap_extended_long extended_long;
+ };
+} __packed;
+
+static bool tb_cap_is_basic(struct tb_cap_any *cap)
+{
+ /* basic.cap is u8. This checks only the lower 8 bit of cap. */
+ return cap->basic.cap != 5;
+}
+
+static bool tb_cap_is_long(struct tb_cap_any *cap)
+{
+ return !tb_cap_is_basic(cap)
+ && cap->extended_short.next == 0
+ && cap->extended_short.length == 0;
+}
+
+static enum tb_cap tb_cap(struct tb_cap_any *cap)
+{
+ if (tb_cap_is_basic(cap))
+ return cap->basic.cap;
+ else
+ /* extended_short/long have cap at the same offset. */
+ return cap->extended_short.cap;
+}
+
+static u32 tb_cap_next(struct tb_cap_any *cap, u32 offset)
+{
+ int next;
+ if (offset == 1) {
+ /*
+ * The first pointer is part of the switch header and always
+ * a simple pointer.
+ */
+ next = cap->basic.next;
+ } else {
+ /*
+ * Somehow Intel decided to use 3 different types of capability
+ * headers. It is not like anyone could have predicted that
+ * single byte offsets are not enough...
+ */
+ if (tb_cap_is_basic(cap))
+ next = cap->basic.next;
+ else if (!tb_cap_is_long(cap))
+ next = cap->extended_short.next;
+ else
+ next = cap->extended_long.next;
+ }
+ /*
+ * "Hey, we could terminate some capability lists with a null offset
+ * and others with a pointer to the last element." - "Great idea!"
+ */
+ if (next == offset)
+ return 0;
+ return next;
+}
+
+/**
+ * tb_find_cap() - find a capability
+ *
+ * Return: Returns a positive offset if the capability was found and 0 if not.
+ * Returns an error code on failure.
+ */
+int tb_find_cap(struct tb_port *port, enum tb_cfg_space space, enum tb_cap cap)
+{
+ u32 offset = 1;
+ struct tb_cap_any header;
+ int res;
+ int retries = 10;
+ while (retries--) {
+ res = tb_port_read(port, &header, space, offset, 1);
+ if (res) {
+ /* Intel needs some help with linked lists. */
+ if (space == TB_CFG_PORT && offset == 0xa
+ && port->config.type == TB_TYPE_DP_HDMI_OUT) {
+ offset = 0x39;
+ continue;
+ }
+ return res;
+ }
+ if (offset != 1) {
+ if (tb_cap(&header) == cap)
+ return offset;
+ if (tb_cap_is_long(&header)) {
+ /* tb_cap_extended_long is 2 dwords */
+ res = tb_port_read(port, &header, space,
+ offset, 2);
+ if (res)
+ return res;
+ }
+ }
+ offset = tb_cap_next(&header, offset);
+ if (!offset)
+ return 0;
+ }
+ tb_port_WARN(port,
+ "run out of retries while looking for cap %#x in config space %d, last offset: %#x\n",
+ cap, space, offset);
+ return -EIO;
+}
diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h
index 389dbb4..38e4c23 100644
--- a/drivers/thunderbolt/tb.h
+++ b/drivers/thunderbolt/tb.h
@@ -159,6 +159,8 @@ void thunderbolt_shutdown_and_free(struct tb *tb);
struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route);
void tb_switch_free(struct tb_switch *sw);
+int tb_find_cap(struct tb_port *port, enum tb_cfg_space space, u32 value);
+
static inline int tb_route_length(u64 route)
{

View File

@ -0,0 +1,112 @@
From: Andreas Noever <andreas.noever@gmail.com>
Date: Tue, 3 Jun 2014 22:04:04 +0200
Subject: [07/31] thunderbolt: Enable plug events
Origin: https://git.kernel.org/linus/ca389f716f6140d5349583a716bc629d63b06b1f
Thunderbolt switches have a plug events capability. This patch adds the
tb_plug_events_active method and uses it to activate plug events during
switch allocation.
Signed-off-by: Andreas Noever <andreas.noever@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
drivers/thunderbolt/switch.c | 52 ++++++++++++++++++++++++++++++++++++++++++++
drivers/thunderbolt/tb.h | 1 +
2 files changed, 53 insertions(+)
diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c
index e89121f..6d193a2 100644
--- a/drivers/thunderbolt/switch.c
+++ b/drivers/thunderbolt/switch.c
@@ -99,6 +99,45 @@ static void tb_dump_switch(struct tb *tb, struct tb_regs_switch_header *sw)
}
/**
+ * tb_plug_events_active() - enable/disable plug events on a switch
+ *
+ * Also configures a sane plug_events_delay of 255ms.
+ *
+ * Return: Returns 0 on success or an error code on failure.
+ */
+static int tb_plug_events_active(struct tb_switch *sw, bool active)
+{
+ u32 data;
+ int res;
+
+ sw->config.plug_events_delay = 0xff;
+ res = tb_sw_write(sw, ((u32 *) &sw->config) + 4, TB_CFG_SWITCH, 4, 1);
+ if (res)
+ return res;
+
+ res = tb_sw_read(sw, &data, TB_CFG_SWITCH, sw->cap_plug_events + 1, 1);
+ if (res)
+ return res;
+
+ if (active) {
+ data = data & 0xFFFFFF83;
+ switch (sw->config.device_id) {
+ case 0x1513:
+ case 0x151a:
+ case 0x1549:
+ break;
+ default:
+ data |= 4;
+ }
+ } else {
+ data = data | 0x7c;
+ }
+ return tb_sw_write(sw, &data, TB_CFG_SWITCH,
+ sw->cap_plug_events + 1, 1);
+}
+
+
+/**
* tb_switch_free() - free a tb_switch and all downstream switches
*/
void tb_switch_free(struct tb_switch *sw)
@@ -113,6 +152,8 @@ void tb_switch_free(struct tb_switch *sw)
sw->ports[i].remote = NULL;
}
+ tb_plug_events_active(sw, false);
+
kfree(sw->ports);
kfree(sw);
}
@@ -125,6 +166,7 @@ void tb_switch_free(struct tb_switch *sw)
struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route)
{
int i;
+ int cap;
struct tb_switch *sw;
int upstream_port = tb_cfg_get_upstream_port(tb->ctl, route);
if (upstream_port < 0)
@@ -177,6 +219,16 @@ struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route)
/* TODO: I2C, IECS, EEPROM, link controller */
+ cap = tb_find_cap(&sw->ports[0], TB_CFG_SWITCH, TB_CAP_PLUG_EVENTS);
+ if (cap < 0) {
+ tb_sw_warn(sw, "cannot find TB_CAP_PLUG_EVENTS aborting\n");
+ goto err;
+ }
+ sw->cap_plug_events = cap;
+
+ if (tb_plug_events_active(sw, true))
+ goto err;
+
return sw;
err:
kfree(sw->ports);
diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h
index 38e4c23..af123c4 100644
--- a/drivers/thunderbolt/tb.h
+++ b/drivers/thunderbolt/tb.h
@@ -19,6 +19,7 @@ struct tb_switch {
struct tb_regs_switch_header config;
struct tb_port *ports;
struct tb *tb;
+ int cap_plug_events; /* offset, zero if not found */
};
/**

View File

@ -0,0 +1,239 @@
From: Andreas Noever <andreas.noever@gmail.com>
Date: Tue, 3 Jun 2014 22:04:05 +0200
Subject: [08/31] thunderbolt: Scan for downstream switches
Origin: https://git.kernel.org/linus/9da672a42878c58af5c50d7389dbae17bea9df38
Add utility methods tb_port_state and tb_wait_for_port. Add
tb_scan_switch which recursively checks for downstream switches.
Signed-off-by: Andreas Noever <andreas.noever@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
drivers/thunderbolt/switch.c | 97 ++++++++++++++++++++++++++++++++++++++++++++
drivers/thunderbolt/tb.c | 44 ++++++++++++++++++++
drivers/thunderbolt/tb.h | 16 ++++++++
3 files changed, 157 insertions(+)
diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c
index 6d193a2..b31b8ce 100644
--- a/drivers/thunderbolt/switch.c
+++ b/drivers/thunderbolt/switch.c
@@ -53,6 +53,92 @@ static void tb_dump_port(struct tb *tb, struct tb_regs_port_header *port)
}
/**
+ * tb_port_state() - get connectedness state of a port
+ *
+ * The port must have a TB_CAP_PHY (i.e. it should be a real port).
+ *
+ * Return: Returns an enum tb_port_state on success or an error code on failure.
+ */
+static int tb_port_state(struct tb_port *port)
+{
+ struct tb_cap_phy phy;
+ int res;
+ if (port->cap_phy == 0) {
+ tb_port_WARN(port, "does not have a PHY\n");
+ return -EINVAL;
+ }
+ res = tb_port_read(port, &phy, TB_CFG_PORT, port->cap_phy, 2);
+ if (res)
+ return res;
+ return phy.state;
+}
+
+/**
+ * tb_wait_for_port() - wait for a port to become ready
+ *
+ * Wait up to 1 second for a port to reach state TB_PORT_UP. If
+ * wait_if_unplugged is set then we also wait if the port is in state
+ * TB_PORT_UNPLUGGED (it takes a while for the device to be registered after
+ * switch resume). Otherwise we only wait if a device is registered but the link
+ * has not yet been established.
+ *
+ * Return: Returns an error code on failure. Returns 0 if the port is not
+ * connected or failed to reach state TB_PORT_UP within one second. Returns 1
+ * if the port is connected and in state TB_PORT_UP.
+ */
+int tb_wait_for_port(struct tb_port *port, bool wait_if_unplugged)
+{
+ int retries = 10;
+ int state;
+ if (!port->cap_phy) {
+ tb_port_WARN(port, "does not have PHY\n");
+ return -EINVAL;
+ }
+ if (tb_is_upstream_port(port)) {
+ tb_port_WARN(port, "is the upstream port\n");
+ return -EINVAL;
+ }
+
+ while (retries--) {
+ state = tb_port_state(port);
+ if (state < 0)
+ return state;
+ if (state == TB_PORT_DISABLED) {
+ tb_port_info(port, "is disabled (state: 0)\n");
+ return 0;
+ }
+ if (state == TB_PORT_UNPLUGGED) {
+ if (wait_if_unplugged) {
+ /* used during resume */
+ tb_port_info(port,
+ "is unplugged (state: 7), retrying...\n");
+ msleep(100);
+ continue;
+ }
+ tb_port_info(port, "is unplugged (state: 7)\n");
+ return 0;
+ }
+ if (state == TB_PORT_UP) {
+ tb_port_info(port,
+ "is connected, link is up (state: 2)\n");
+ return 1;
+ }
+
+ /*
+ * After plug-in the state is TB_PORT_CONNECTING. Give it some
+ * time.
+ */
+ tb_port_info(port,
+ "is connected, link is not up (state: %d), retrying...\n",
+ state);
+ msleep(100);
+ }
+ tb_port_warn(port,
+ "failed to reach state TB_PORT_UP. Ignoring port...\n");
+ return 0;
+}
+
+/**
* tb_init_port() - initialize a port
*
* This is a helper method for tb_switch_alloc. Does not check or initialize
@@ -63,6 +149,7 @@ static void tb_dump_port(struct tb *tb, struct tb_regs_port_header *port)
static int tb_init_port(struct tb_switch *sw, u8 port_nr)
{
int res;
+ int cap;
struct tb_port *port = &sw->ports[port_nr];
port->sw = sw;
port->port = port_nr;
@@ -71,6 +158,16 @@ static int tb_init_port(struct tb_switch *sw, u8 port_nr)
if (res)
return res;
+ /* Port 0 is the switch itself and has no PHY. */
+ if (port->config.type == TB_TYPE_PORT && port_nr != 0) {
+ cap = tb_find_cap(port, TB_CFG_PORT, TB_CAP_PHY);
+
+ if (cap > 0)
+ port->cap_phy = cap;
+ else
+ tb_port_WARN(port, "non switch port without a PHY\n");
+ }
+
tb_dump_port(sw->tb, &port->config);
/* TODO: Read dual link port, DP port and more from EEPROM. */
diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c
index f1b6100..3b716fd 100644
--- a/drivers/thunderbolt/tb.c
+++ b/drivers/thunderbolt/tb.c
@@ -11,6 +11,47 @@
#include "tb.h"
#include "tb_regs.h"
+
+/* enumeration & hot plug handling */
+
+
+static void tb_scan_port(struct tb_port *port);
+
+/**
+ * tb_scan_switch() - scan for and initialize downstream switches
+ */
+static void tb_scan_switch(struct tb_switch *sw)
+{
+ int i;
+ for (i = 1; i <= sw->config.max_port_number; i++)
+ tb_scan_port(&sw->ports[i]);
+}
+
+/**
+ * tb_scan_port() - check for and initialize switches below port
+ */
+static void tb_scan_port(struct tb_port *port)
+{
+ struct tb_switch *sw;
+ if (tb_is_upstream_port(port))
+ return;
+ if (port->config.type != TB_TYPE_PORT)
+ return;
+ if (tb_wait_for_port(port, false) <= 0)
+ return;
+ if (port->remote) {
+ tb_port_WARN(port, "port already has a remote!\n");
+ return;
+ }
+ sw = tb_switch_alloc(port->sw->tb, tb_downstream_route(port));
+ if (!sw)
+ return;
+ port->remote = tb_upstream_port(sw);
+ tb_upstream_port(sw)->remote = port;
+ tb_scan_switch(sw);
+}
+
+
/* hotplug handling */
struct tb_hotplug_event {
@@ -134,6 +175,9 @@ struct tb *thunderbolt_alloc_and_start(struct tb_nhi *nhi)
if (!tb->root_switch)
goto err_locked;
+ /* Full scan to discover devices added before the driver was loaded. */
+ tb_scan_switch(tb->root_switch);
+
/* Allow tb_handle_hotplug to progress events */
tb->hotplug_active = true;
mutex_unlock(&tb->lock);
diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h
index af123c4..70a66fe 100644
--- a/drivers/thunderbolt/tb.h
+++ b/drivers/thunderbolt/tb.h
@@ -29,6 +29,7 @@ struct tb_port {
struct tb_regs_port_header config;
struct tb_switch *sw;
struct tb_port *remote; /* remote port, NULL if not connected */
+ int cap_phy; /* offset, zero if not found */
u8 port; /* port number on switch */
};
@@ -160,6 +161,8 @@ void thunderbolt_shutdown_and_free(struct tb *tb);
struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route);
void tb_switch_free(struct tb_switch *sw);
+int tb_wait_for_port(struct tb_port *port, bool wait_if_unplugged);
+
int tb_find_cap(struct tb_port *port, enum tb_cfg_space space, u32 value);
@@ -173,4 +176,17 @@ static inline bool tb_is_upstream_port(struct tb_port *port)
return port == tb_upstream_port(port->sw);
}
+/**
+ * tb_downstream_route() - get route to downstream switch
+ *
+ * Port must not be the upstream port (otherwise a loop is created).
+ *
+ * Return: Returns a route to the switch behind @port.
+ */
+static inline u64 tb_downstream_route(struct tb_port *port)
+{
+ return tb_route(port->sw)
+ | ((u64) port->port << (port->sw->config.depth * 8));
+}
+
#endif

View File

@ -0,0 +1,163 @@
From: Andreas Noever <andreas.noever@gmail.com>
Date: Tue, 3 Jun 2014 22:04:06 +0200
Subject: [09/31] thunderbolt: Handle hotplug events
Origin: https://git.kernel.org/linus/053596d9e26c86352c4b2b372f43f2746b97de45
We receive a plug event callback whenever a thunderbolt device is added
or removed. This patch fills in the tb_handle_hotplug method and starts
reacting to these events by adding/removing switches from the hierarchy.
Signed-off-by: Andreas Noever <andreas.noever@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
drivers/thunderbolt/switch.c | 42 +++++++++++++++++++++++++++++++++++++++-
drivers/thunderbolt/tb.c | 46 +++++++++++++++++++++++++++++++++++++++++++-
drivers/thunderbolt/tb.h | 3 +++
3 files changed, 89 insertions(+), 2 deletions(-)
diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c
index b31b8ce..d6c32e1 100644
--- a/drivers/thunderbolt/switch.c
+++ b/drivers/thunderbolt/switch.c
@@ -195,6 +195,24 @@ static void tb_dump_switch(struct tb *tb, struct tb_regs_switch_header *sw)
sw->__unknown1, sw->__unknown4);
}
+struct tb_switch *get_switch_at_route(struct tb_switch *sw, u64 route)
+{
+ u8 next_port = route; /*
+ * Routes use a stride of 8 bits,
+ * eventhough a port index has 6 bits at most.
+ * */
+ if (route == 0)
+ return sw;
+ if (next_port > sw->config.max_port_number)
+ return 0;
+ if (tb_is_upstream_port(&sw->ports[next_port]))
+ return 0;
+ if (!sw->ports[next_port].remote)
+ return 0;
+ return get_switch_at_route(sw->ports[next_port].remote->sw,
+ route >> TB_ROUTE_SHIFT);
+}
+
/**
* tb_plug_events_active() - enable/disable plug events on a switch
*
@@ -249,7 +267,8 @@ void tb_switch_free(struct tb_switch *sw)
sw->ports[i].remote = NULL;
}
- tb_plug_events_active(sw, false);
+ if (!sw->is_unplugged)
+ tb_plug_events_active(sw, false);
kfree(sw->ports);
kfree(sw);
@@ -333,3 +352,24 @@ err:
return NULL;
}
+/**
+ * tb_sw_set_unpplugged() - set is_unplugged on switch and downstream switches
+ */
+void tb_sw_set_unpplugged(struct tb_switch *sw)
+{
+ int i;
+ if (sw == sw->tb->root_switch) {
+ tb_sw_WARN(sw, "cannot unplug root switch\n");
+ return;
+ }
+ if (sw->is_unplugged) {
+ tb_sw_WARN(sw, "is_unplugged already set\n");
+ return;
+ }
+ sw->is_unplugged = true;
+ for (i = 0; i <= sw->config.max_port_number; i++) {
+ if (!tb_is_upstream_port(&sw->ports[i]) && sw->ports[i].remote)
+ tb_sw_set_unpplugged(sw->ports[i].remote->sw);
+ }
+}
+
diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c
index 3b716fd..1efcacc 100644
--- a/drivers/thunderbolt/tb.c
+++ b/drivers/thunderbolt/tb.c
@@ -71,11 +71,55 @@ static void tb_handle_hotplug(struct work_struct *work)
{
struct tb_hotplug_event *ev = container_of(work, typeof(*ev), work);
struct tb *tb = ev->tb;
+ struct tb_switch *sw;
+ struct tb_port *port;
mutex_lock(&tb->lock);
if (!tb->hotplug_active)
goto out; /* during init, suspend or shutdown */
- /* do nothing for now */
+ sw = get_switch_at_route(tb->root_switch, ev->route);
+ if (!sw) {
+ tb_warn(tb,
+ "hotplug event from non existent switch %llx:%x (unplug: %d)\n",
+ ev->route, ev->port, ev->unplug);
+ goto out;
+ }
+ if (ev->port > sw->config.max_port_number) {
+ tb_warn(tb,
+ "hotplug event from non existent port %llx:%x (unplug: %d)\n",
+ ev->route, ev->port, ev->unplug);
+ goto out;
+ }
+ port = &sw->ports[ev->port];
+ if (tb_is_upstream_port(port)) {
+ tb_warn(tb,
+ "hotplug event for upstream port %llx:%x (unplug: %d)\n",
+ ev->route, ev->port, ev->unplug);
+ goto out;
+ }
+ if (ev->unplug) {
+ if (port->remote) {
+ tb_port_info(port, "unplugged\n");
+ tb_sw_set_unpplugged(port->remote->sw);
+ tb_switch_free(port->remote->sw);
+ port->remote = NULL;
+ } else {
+ tb_port_info(port,
+ "got unplug event for disconnected port, ignoring\n");
+ }
+ } else if (port->remote) {
+ tb_port_info(port,
+ "got plug event for connected port, ignoring\n");
+ } else {
+ tb_port_info(port, "hotplug: scanning\n");
+ tb_scan_port(port);
+ if (!port->remote) {
+ tb_port_info(port, "hotplug: no switch found\n");
+ } else if (port->remote->sw->config.depth > 1) {
+ tb_sw_warn(port->remote->sw,
+ "hotplug: chaining not supported\n");
+ }
+ }
out:
mutex_unlock(&tb->lock);
kfree(ev);
diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h
index 70a66fe..661f182 100644
--- a/drivers/thunderbolt/tb.h
+++ b/drivers/thunderbolt/tb.h
@@ -20,6 +20,7 @@ struct tb_switch {
struct tb_port *ports;
struct tb *tb;
int cap_plug_events; /* offset, zero if not found */
+ bool is_unplugged; /* unplugged, will go away */
};
/**
@@ -160,6 +161,8 @@ void thunderbolt_shutdown_and_free(struct tb *tb);
struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route);
void tb_switch_free(struct tb_switch *sw);
+void tb_sw_set_unpplugged(struct tb_switch *sw);
+struct tb_switch *get_switch_at_route(struct tb_switch *sw, u64 route);
int tb_wait_for_port(struct tb_port *port, bool wait_if_unplugged);

View File

@ -0,0 +1,379 @@
From: Andreas Noever <andreas.noever@gmail.com>
Date: Tue, 3 Jun 2014 22:04:07 +0200
Subject: [10/31] thunderbolt: Add path setup code.
Origin: https://git.kernel.org/linus/520b670216a15fb949e6ec6a1af9b5dd55d219c7
A thunderbolt path is a unidirectional channel between two thunderbolt
ports. Two such paths are needed to establish a pci tunnel.
This patch introduces struct tb_path as well as a set of tb_path_*
methods which are used to activate & deactivate paths.
Signed-off-by: Andreas Noever <andreas.noever@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
drivers/thunderbolt/Makefile | 2 +-
drivers/thunderbolt/path.c | 215 +++++++++++++++++++++++++++++++++++++++++++
drivers/thunderbolt/switch.c | 34 +++++++
drivers/thunderbolt/tb.h | 62 +++++++++++++
4 files changed, 312 insertions(+), 1 deletion(-)
create mode 100644 drivers/thunderbolt/path.c
diff --git a/drivers/thunderbolt/Makefile b/drivers/thunderbolt/Makefile
index 617b314..3532f36 100644
--- a/drivers/thunderbolt/Makefile
+++ b/drivers/thunderbolt/Makefile
@@ -1,3 +1,3 @@
obj-${CONFIG_THUNDERBOLT} := thunderbolt.o
-thunderbolt-objs := nhi.o ctl.o tb.o switch.o cap.o
+thunderbolt-objs := nhi.o ctl.o tb.o switch.o cap.o path.o
diff --git a/drivers/thunderbolt/path.c b/drivers/thunderbolt/path.c
new file mode 100644
index 0000000..8fcf8a7
--- /dev/null
+++ b/drivers/thunderbolt/path.c
@@ -0,0 +1,215 @@
+/*
+ * Thunderbolt Cactus Ridge driver - path/tunnel functionality
+ *
+ * Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com>
+ */
+
+#include <linux/slab.h>
+#include <linux/errno.h>
+
+#include "tb.h"
+
+
+static void tb_dump_hop(struct tb_port *port, struct tb_regs_hop *hop)
+{
+ tb_port_info(port, " Hop through port %d to hop %d (%s)\n",
+ hop->out_port, hop->next_hop,
+ hop->enable ? "enabled" : "disabled");
+ tb_port_info(port, " Weight: %d Priority: %d Credits: %d Drop: %d\n",
+ hop->weight, hop->priority,
+ hop->initial_credits, hop->drop_packages);
+ tb_port_info(port, " Counter enabled: %d Counter index: %d\n",
+ hop->counter_enable, hop->counter);
+ tb_port_info(port, " Flow Control (In/Eg): %d/%d Shared Buffer (In/Eg): %d/%d\n",
+ hop->ingress_fc, hop->egress_fc,
+ hop->ingress_shared_buffer, hop->egress_shared_buffer);
+ tb_port_info(port, " Unknown1: %#x Unknown2: %#x Unknown3: %#x\n",
+ hop->unknown1, hop->unknown2, hop->unknown3);
+}
+
+/**
+ * tb_path_alloc() - allocate a thunderbolt path
+ *
+ * Return: Returns a tb_path on success or NULL on failure.
+ */
+struct tb_path *tb_path_alloc(struct tb *tb, int num_hops)
+{
+ struct tb_path *path = kzalloc(sizeof(*path), GFP_KERNEL);
+ if (!path)
+ return NULL;
+ path->hops = kcalloc(num_hops, sizeof(*path->hops), GFP_KERNEL);
+ if (!path->hops) {
+ kfree(path);
+ return NULL;
+ }
+ path->tb = tb;
+ path->path_length = num_hops;
+ return path;
+}
+
+/**
+ * tb_path_free() - free a deactivated path
+ */
+void tb_path_free(struct tb_path *path)
+{
+ if (path->activated) {
+ tb_WARN(path->tb, "trying to free an activated path\n")
+ return;
+ }
+ kfree(path->hops);
+ kfree(path);
+}
+
+static void __tb_path_deallocate_nfc(struct tb_path *path, int first_hop)
+{
+ int i, res;
+ for (i = first_hop; i < path->path_length; i++) {
+ res = tb_port_add_nfc_credits(path->hops[i].in_port,
+ -path->nfc_credits);
+ if (res)
+ tb_port_warn(path->hops[i].in_port,
+ "nfc credits deallocation failed for hop %d\n",
+ i);
+ }
+}
+
+static void __tb_path_deactivate_hops(struct tb_path *path, int first_hop)
+{
+ int i, res;
+ struct tb_regs_hop hop = { };
+ for (i = first_hop; i < path->path_length; i++) {
+ res = tb_port_write(path->hops[i].in_port, &hop, TB_CFG_HOPS,
+ 2 * path->hops[i].in_hop_index, 2);
+ if (res)
+ tb_port_warn(path->hops[i].in_port,
+ "hop deactivation failed for hop %d, index %d\n",
+ i, path->hops[i].in_hop_index);
+ }
+}
+
+void tb_path_deactivate(struct tb_path *path)
+{
+ if (!path->activated) {
+ tb_WARN(path->tb, "trying to deactivate an inactive path\n");
+ return;
+ }
+ tb_info(path->tb,
+ "deactivating path from %llx:%x to %llx:%x\n",
+ tb_route(path->hops[0].in_port->sw),
+ path->hops[0].in_port->port,
+ tb_route(path->hops[path->path_length - 1].out_port->sw),
+ path->hops[path->path_length - 1].out_port->port);
+ __tb_path_deactivate_hops(path, 0);
+ __tb_path_deallocate_nfc(path, 0);
+ path->activated = false;
+}
+
+/**
+ * tb_path_activate() - activate a path
+ *
+ * Activate a path starting with the last hop and iterating backwards. The
+ * caller must fill path->hops before calling tb_path_activate().
+ *
+ * Return: Returns 0 on success or an error code on failure.
+ */
+int tb_path_activate(struct tb_path *path)
+{
+ int i, res;
+ enum tb_path_port out_mask, in_mask;
+ if (path->activated) {
+ tb_WARN(path->tb, "trying to activate already activated path\n");
+ return -EINVAL;
+ }
+
+ tb_info(path->tb,
+ "activating path from %llx:%x to %llx:%x\n",
+ tb_route(path->hops[0].in_port->sw),
+ path->hops[0].in_port->port,
+ tb_route(path->hops[path->path_length - 1].out_port->sw),
+ path->hops[path->path_length - 1].out_port->port);
+
+ /* Clear counters. */
+ for (i = path->path_length - 1; i >= 0; i--) {
+ if (path->hops[i].in_counter_index == -1)
+ continue;
+ res = tb_port_clear_counter(path->hops[i].in_port,
+ path->hops[i].in_counter_index);
+ if (res)
+ goto err;
+ }
+
+ /* Add non flow controlled credits. */
+ for (i = path->path_length - 1; i >= 0; i--) {
+ res = tb_port_add_nfc_credits(path->hops[i].in_port,
+ path->nfc_credits);
+ if (res) {
+ __tb_path_deallocate_nfc(path, i);
+ goto err;
+ }
+ }
+
+ /* Activate hops. */
+ for (i = path->path_length - 1; i >= 0; i--) {
+ struct tb_regs_hop hop;
+
+ /* dword 0 */
+ hop.next_hop = path->hops[i].next_hop_index;
+ hop.out_port = path->hops[i].out_port->port;
+ /* TODO: figure out why these are good values */
+ hop.initial_credits = (i == path->path_length - 1) ? 16 : 7;
+ hop.unknown1 = 0;
+ hop.enable = 1;
+
+ /* dword 1 */
+ out_mask = (i == path->path_length - 1) ?
+ TB_PATH_DESTINATION : TB_PATH_INTERNAL;
+ in_mask = (i == 0) ? TB_PATH_SOURCE : TB_PATH_INTERNAL;
+ hop.weight = path->weight;
+ hop.unknown2 = 0;
+ hop.priority = path->priority;
+ hop.drop_packages = path->drop_packages;
+ hop.counter = path->hops[i].in_counter_index;
+ hop.counter_enable = path->hops[i].in_counter_index != -1;
+ hop.ingress_fc = path->ingress_fc_enable & in_mask;
+ hop.egress_fc = path->egress_fc_enable & out_mask;
+ hop.ingress_shared_buffer = path->ingress_shared_buffer
+ & in_mask;
+ hop.egress_shared_buffer = path->egress_shared_buffer
+ & out_mask;
+ hop.unknown3 = 0;
+
+ tb_port_info(path->hops[i].in_port, "Writing hop %d, index %d",
+ i, path->hops[i].in_hop_index);
+ tb_dump_hop(path->hops[i].in_port, &hop);
+ res = tb_port_write(path->hops[i].in_port, &hop, TB_CFG_HOPS,
+ 2 * path->hops[i].in_hop_index, 2);
+ if (res) {
+ __tb_path_deactivate_hops(path, i);
+ __tb_path_deallocate_nfc(path, 0);
+ goto err;
+ }
+ }
+ path->activated = true;
+ tb_info(path->tb, "path activation complete\n");
+ return 0;
+err:
+ tb_WARN(path->tb, "path activation failed\n");
+ return res;
+}
+
+/**
+ * tb_path_is_invalid() - check whether any ports on the path are invalid
+ *
+ * Return: Returns true if the path is invalid, false otherwise.
+ */
+bool tb_path_is_invalid(struct tb_path *path)
+{
+ int i = 0;
+ for (i = 0; i < path->path_length; i++) {
+ if (path->hops[i].in_port->sw->is_unplugged)
+ return true;
+ if (path->hops[i].out_port->sw->is_unplugged)
+ return true;
+ }
+ return false;
+}
diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c
index d6c32e1..667413f 100644
--- a/drivers/thunderbolt/switch.c
+++ b/drivers/thunderbolt/switch.c
@@ -139,6 +139,40 @@ int tb_wait_for_port(struct tb_port *port, bool wait_if_unplugged)
}
/**
+ * tb_port_add_nfc_credits() - add/remove non flow controlled credits to port
+ *
+ * Change the number of NFC credits allocated to @port by @credits. To remove
+ * NFC credits pass a negative amount of credits.
+ *
+ * Return: Returns 0 on success or an error code on failure.
+ */
+int tb_port_add_nfc_credits(struct tb_port *port, int credits)
+{
+ if (credits == 0)
+ return 0;
+ tb_port_info(port,
+ "adding %#x NFC credits (%#x -> %#x)",
+ credits,
+ port->config.nfc_credits,
+ port->config.nfc_credits + credits);
+ port->config.nfc_credits += credits;
+ return tb_port_write(port, &port->config.nfc_credits,
+ TB_CFG_PORT, 4, 1);
+}
+
+/**
+ * tb_port_clear_counter() - clear a counter in TB_CFG_COUNTER
+ *
+ * Return: Returns 0 on success or an error code on failure.
+ */
+int tb_port_clear_counter(struct tb_port *port, int counter)
+{
+ u32 zero[3] = { 0, 0, 0 };
+ tb_port_info(port, "clearing counter %d\n", counter);
+ return tb_port_write(port, zero, TB_CFG_COUNTERS, 3 * counter, 3);
+}
+
+/**
* tb_init_port() - initialize a port
*
* This is a helper method for tb_switch_alloc. Does not check or initialize
diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h
index 661f182..8bbdc2b 100644
--- a/drivers/thunderbolt/tb.h
+++ b/drivers/thunderbolt/tb.h
@@ -35,6 +35,60 @@ struct tb_port {
};
/**
+ * struct tb_path_hop - routing information for a tb_path
+ *
+ * Hop configuration is always done on the IN port of a switch.
+ * in_port and out_port have to be on the same switch. Packets arriving on
+ * in_port with "hop" = in_hop_index will get routed to through out_port. The
+ * next hop to take (on out_port->remote) is determined by next_hop_index.
+ *
+ * in_counter_index is the index of a counter (in TB_CFG_COUNTERS) on the in
+ * port.
+ */
+struct tb_path_hop {
+ struct tb_port *in_port;
+ struct tb_port *out_port;
+ int in_hop_index;
+ int in_counter_index; /* write -1 to disable counters for this hop. */
+ int next_hop_index;
+};
+
+/**
+ * enum tb_path_port - path options mask
+ */
+enum tb_path_port {
+ TB_PATH_NONE = 0,
+ TB_PATH_SOURCE = 1, /* activate on the first hop (out of src) */
+ TB_PATH_INTERNAL = 2, /* activate on other hops (not the first/last) */
+ TB_PATH_DESTINATION = 4, /* activate on the last hop (into dst) */
+ TB_PATH_ALL = 7,
+};
+
+/**
+ * struct tb_path - a unidirectional path between two ports
+ *
+ * A path consists of a number of hops (see tb_path_hop). To establish a PCIe
+ * tunnel two paths have to be created between the two PCIe ports.
+ *
+ */
+struct tb_path {
+ struct tb *tb;
+ int nfc_credits; /* non flow controlled credits */
+ enum tb_path_port ingress_shared_buffer;
+ enum tb_path_port egress_shared_buffer;
+ enum tb_path_port ingress_fc_enable;
+ enum tb_path_port egress_fc_enable;
+
+ int priority:3;
+ int weight:4;
+ bool drop_packages;
+ bool activated;
+ struct tb_path_hop *hops;
+ int path_length; /* number of hops */
+};
+
+
+/**
* struct tb - main thunderbolt bus structure
*/
struct tb {
@@ -165,9 +219,17 @@ void tb_sw_set_unpplugged(struct tb_switch *sw);
struct tb_switch *get_switch_at_route(struct tb_switch *sw, u64 route);
int tb_wait_for_port(struct tb_port *port, bool wait_if_unplugged);
+int tb_port_add_nfc_credits(struct tb_port *port, int credits);
+int tb_port_clear_counter(struct tb_port *port, int counter);
int tb_find_cap(struct tb_port *port, enum tb_cfg_space space, u32 value);
+struct tb_path *tb_path_alloc(struct tb *tb, int num_hops);
+void tb_path_free(struct tb_path *path);
+int tb_path_activate(struct tb_path *path);
+void tb_path_deactivate(struct tb_path *path);
+bool tb_path_is_invalid(struct tb_path *path);
+
static inline int tb_route_length(u64 route)
{

View File

@ -0,0 +1,509 @@
From: Andreas Noever <andreas.noever@gmail.com>
Date: Tue, 3 Jun 2014 22:04:08 +0200
Subject: [11/31] thunderbolt: Add support for simple pci tunnels
Origin: https://git.kernel.org/linus/3364f0c12795713e89ae1209081c60d64bfb4ca1
A pci downstream and pci upstream port can be connected through a
tunnel. To establish the tunnel we have to setup two unidirectional
paths between the two ports.
Right now we only support paths with two hops (i.e. no chaining) and at
most one pci device per thunderbolt device.
Signed-off-by: Andreas Noever <andreas.noever@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
drivers/thunderbolt/Makefile | 2 +-
drivers/thunderbolt/tb.c | 135 +++++++++++++++++++++++
drivers/thunderbolt/tb.h | 1 +
drivers/thunderbolt/tunnel_pci.c | 232 +++++++++++++++++++++++++++++++++++++++
drivers/thunderbolt/tunnel_pci.h | 30 +++++
5 files changed, 399 insertions(+), 1 deletion(-)
create mode 100644 drivers/thunderbolt/tunnel_pci.c
create mode 100644 drivers/thunderbolt/tunnel_pci.h
diff --git a/drivers/thunderbolt/Makefile b/drivers/thunderbolt/Makefile
index 3532f36..0122ca6 100644
--- a/drivers/thunderbolt/Makefile
+++ b/drivers/thunderbolt/Makefile
@@ -1,3 +1,3 @@
obj-${CONFIG_THUNDERBOLT} := thunderbolt.o
-thunderbolt-objs := nhi.o ctl.o tb.o switch.o cap.o path.o
+thunderbolt-objs := nhi.o ctl.o tb.o switch.o cap.o path.o tunnel_pci.o
diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c
index 1efcacc..177f61d 100644
--- a/drivers/thunderbolt/tb.c
+++ b/drivers/thunderbolt/tb.c
@@ -10,6 +10,7 @@
#include "tb.h"
#include "tb_regs.h"
+#include "tunnel_pci.h"
/* enumeration & hot plug handling */
@@ -51,6 +52,124 @@ static void tb_scan_port(struct tb_port *port)
tb_scan_switch(sw);
}
+/**
+ * tb_free_invalid_tunnels() - destroy tunnels of devices that have gone away
+ */
+static void tb_free_invalid_tunnels(struct tb *tb)
+{
+ struct tb_pci_tunnel *tunnel;
+ struct tb_pci_tunnel *n;
+ list_for_each_entry_safe(tunnel, n, &tb->tunnel_list, list)
+ {
+ if (tb_pci_is_invalid(tunnel)) {
+ tb_pci_deactivate(tunnel);
+ tb_pci_free(tunnel);
+ }
+ }
+}
+
+/**
+ * find_pci_up_port() - return the first PCIe up port on @sw or NULL
+ */
+static struct tb_port *tb_find_pci_up_port(struct tb_switch *sw)
+{
+ int i;
+ for (i = 1; i <= sw->config.max_port_number; i++)
+ if (sw->ports[i].config.type == TB_TYPE_PCIE_UP)
+ return &sw->ports[i];
+ return NULL;
+}
+
+/**
+ * find_unused_down_port() - return the first inactive PCIe down port on @sw
+ */
+static struct tb_port *tb_find_unused_down_port(struct tb_switch *sw)
+{
+ int i;
+ int cap;
+ int res;
+ int data;
+ for (i = 1; i <= sw->config.max_port_number; i++) {
+ if (tb_is_upstream_port(&sw->ports[i]))
+ continue;
+ if (sw->ports[i].config.type != TB_TYPE_PCIE_DOWN)
+ continue;
+ cap = tb_find_cap(&sw->ports[i], TB_CFG_PORT, TB_CAP_PCIE);
+ if (cap <= 0)
+ continue;
+ res = tb_port_read(&sw->ports[i], &data, TB_CFG_PORT, cap, 1);
+ if (res < 0)
+ continue;
+ if (data & 0x80000000)
+ continue;
+ return &sw->ports[i];
+ }
+ return NULL;
+}
+
+/**
+ * tb_activate_pcie_devices() - scan for and activate PCIe devices
+ *
+ * This method is somewhat ad hoc. For now it only supports one device
+ * per port and only devices at depth 1.
+ */
+static void tb_activate_pcie_devices(struct tb *tb)
+{
+ int i;
+ int cap;
+ u32 data;
+ struct tb_switch *sw;
+ struct tb_port *up_port;
+ struct tb_port *down_port;
+ struct tb_pci_tunnel *tunnel;
+ /* scan for pcie devices at depth 1*/
+ for (i = 1; i <= tb->root_switch->config.max_port_number; i++) {
+ if (tb_is_upstream_port(&tb->root_switch->ports[i]))
+ continue;
+ if (tb->root_switch->ports[i].config.type != TB_TYPE_PORT)
+ continue;
+ if (!tb->root_switch->ports[i].remote)
+ continue;
+ sw = tb->root_switch->ports[i].remote->sw;
+ up_port = tb_find_pci_up_port(sw);
+ if (!up_port) {
+ tb_sw_info(sw, "no PCIe devices found, aborting\n");
+ continue;
+ }
+
+ /* check whether port is already activated */
+ cap = tb_find_cap(up_port, TB_CFG_PORT, TB_CAP_PCIE);
+ if (cap <= 0)
+ continue;
+ if (tb_port_read(up_port, &data, TB_CFG_PORT, cap, 1))
+ continue;
+ if (data & 0x80000000) {
+ tb_port_info(up_port,
+ "PCIe port already activated, aborting\n");
+ continue;
+ }
+
+ down_port = tb_find_unused_down_port(tb->root_switch);
+ if (!down_port) {
+ tb_port_info(up_port,
+ "All PCIe down ports are occupied, aborting\n");
+ continue;
+ }
+ tunnel = tb_pci_alloc(tb, up_port, down_port);
+ if (!tunnel) {
+ tb_port_info(up_port,
+ "PCIe tunnel allocation failed, aborting\n");
+ continue;
+ }
+
+ if (tb_pci_activate(tunnel)) {
+ tb_port_info(up_port,
+ "PCIe tunnel activation failed, aborting\n");
+ tb_pci_free(tunnel);
+ }
+
+ }
+}
/* hotplug handling */
@@ -101,6 +220,7 @@ static void tb_handle_hotplug(struct work_struct *work)
if (port->remote) {
tb_port_info(port, "unplugged\n");
tb_sw_set_unpplugged(port->remote->sw);
+ tb_free_invalid_tunnels(tb);
tb_switch_free(port->remote->sw);
port->remote = NULL;
} else {
@@ -118,6 +238,10 @@ static void tb_handle_hotplug(struct work_struct *work)
} else if (port->remote->sw->config.depth > 1) {
tb_sw_warn(port->remote->sw,
"hotplug: chaining not supported\n");
+ } else {
+ tb_sw_info(port->remote->sw,
+ "hotplug: activating pcie devices\n");
+ tb_activate_pcie_devices(tb);
}
}
out:
@@ -154,8 +278,17 @@ static void tb_schedule_hotplug_handler(void *data, u64 route, u8 port,
*/
void thunderbolt_shutdown_and_free(struct tb *tb)
{
+ struct tb_pci_tunnel *tunnel;
+ struct tb_pci_tunnel *n;
+
mutex_lock(&tb->lock);
+ /* tunnels are only present after everything has been initialized */
+ list_for_each_entry_safe(tunnel, n, &tb->tunnel_list, list) {
+ tb_pci_deactivate(tunnel);
+ tb_pci_free(tunnel);
+ }
+
if (tb->root_switch)
tb_switch_free(tb->root_switch);
tb->root_switch = NULL;
@@ -201,6 +334,7 @@ struct tb *thunderbolt_alloc_and_start(struct tb_nhi *nhi)
tb->nhi = nhi;
mutex_init(&tb->lock);
mutex_lock(&tb->lock);
+ INIT_LIST_HEAD(&tb->tunnel_list);
tb->wq = alloc_ordered_workqueue("thunderbolt", 0);
if (!tb->wq)
@@ -221,6 +355,7 @@ struct tb *thunderbolt_alloc_and_start(struct tb_nhi *nhi)
/* Full scan to discover devices added before the driver was loaded. */
tb_scan_switch(tb->root_switch);
+ tb_activate_pcie_devices(tb);
/* Allow tb_handle_hotplug to progress events */
tb->hotplug_active = true;
diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h
index 8bbdc2b..508abc4 100644
--- a/drivers/thunderbolt/tb.h
+++ b/drivers/thunderbolt/tb.h
@@ -100,6 +100,7 @@ struct tb {
struct tb_ctl *ctl;
struct workqueue_struct *wq; /* ordered workqueue for plug events */
struct tb_switch *root_switch;
+ struct list_head tunnel_list; /* list of active PCIe tunnels */
bool hotplug_active; /*
* tb_handle_hotplug will stop progressing plug
* events and exit if this is not set (it needs to
diff --git a/drivers/thunderbolt/tunnel_pci.c b/drivers/thunderbolt/tunnel_pci.c
new file mode 100644
index 0000000..baf1cd3
--- /dev/null
+++ b/drivers/thunderbolt/tunnel_pci.c
@@ -0,0 +1,232 @@
+/*
+ * Thunderbolt Cactus Ridge driver - PCIe tunnel
+ *
+ * Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com>
+ */
+
+#include <linux/slab.h>
+#include <linux/list.h>
+
+#include "tunnel_pci.h"
+#include "tb.h"
+
+#define __TB_TUNNEL_PRINT(level, tunnel, fmt, arg...) \
+ do { \
+ struct tb_pci_tunnel *__tunnel = (tunnel); \
+ level(__tunnel->tb, "%llx:%x <-> %llx:%x (PCI): " fmt, \
+ tb_route(__tunnel->down_port->sw), \
+ __tunnel->down_port->port, \
+ tb_route(__tunnel->up_port->sw), \
+ __tunnel->up_port->port, \
+ ## arg); \
+ } while (0)
+
+#define tb_tunnel_WARN(tunnel, fmt, arg...) \
+ __TB_TUNNEL_PRINT(tb_WARN, tunnel, fmt, ##arg)
+#define tb_tunnel_warn(tunnel, fmt, arg...) \
+ __TB_TUNNEL_PRINT(tb_warn, tunnel, fmt, ##arg)
+#define tb_tunnel_info(tunnel, fmt, arg...) \
+ __TB_TUNNEL_PRINT(tb_info, tunnel, fmt, ##arg)
+
+static void tb_pci_init_path(struct tb_path *path)
+{
+ path->egress_fc_enable = TB_PATH_SOURCE | TB_PATH_INTERNAL;
+ path->egress_shared_buffer = TB_PATH_NONE;
+ path->ingress_fc_enable = TB_PATH_ALL;
+ path->ingress_shared_buffer = TB_PATH_NONE;
+ path->priority = 3;
+ path->weight = 1;
+ path->drop_packages = 0;
+ path->nfc_credits = 0;
+}
+
+/**
+ * tb_pci_alloc() - allocate a pci tunnel
+ *
+ * Allocate a PCI tunnel. The ports must be of type TB_TYPE_PCIE_UP and
+ * TB_TYPE_PCIE_DOWN.
+ *
+ * Currently only paths consisting of two hops are supported (that is the
+ * ports must be on "adjacent" switches).
+ *
+ * The paths are hard-coded to use hop 8 (the only working hop id available on
+ * my thunderbolt devices). Therefore at most ONE path per device may be
+ * activated.
+ *
+ * Return: Returns a tb_pci_tunnel on success or NULL on failure.
+ */
+struct tb_pci_tunnel *tb_pci_alloc(struct tb *tb, struct tb_port *up,
+ struct tb_port *down)
+{
+ struct tb_pci_tunnel *tunnel = kzalloc(sizeof(*tunnel), GFP_KERNEL);
+ if (!tunnel)
+ goto err;
+ tunnel->tb = tb;
+ tunnel->down_port = down;
+ tunnel->up_port = up;
+ INIT_LIST_HEAD(&tunnel->list);
+ tunnel->path_to_up = tb_path_alloc(up->sw->tb, 2);
+ if (!tunnel->path_to_up)
+ goto err;
+ tunnel->path_to_down = tb_path_alloc(up->sw->tb, 2);
+ if (!tunnel->path_to_down)
+ goto err;
+ tb_pci_init_path(tunnel->path_to_up);
+ tb_pci_init_path(tunnel->path_to_down);
+
+ tunnel->path_to_up->hops[0].in_port = down;
+ tunnel->path_to_up->hops[0].in_hop_index = 8;
+ tunnel->path_to_up->hops[0].in_counter_index = -1;
+ tunnel->path_to_up->hops[0].out_port = tb_upstream_port(up->sw)->remote;
+ tunnel->path_to_up->hops[0].next_hop_index = 8;
+
+ tunnel->path_to_up->hops[1].in_port = tb_upstream_port(up->sw);
+ tunnel->path_to_up->hops[1].in_hop_index = 8;
+ tunnel->path_to_up->hops[1].in_counter_index = -1;
+ tunnel->path_to_up->hops[1].out_port = up;
+ tunnel->path_to_up->hops[1].next_hop_index = 8;
+
+ tunnel->path_to_down->hops[0].in_port = up;
+ tunnel->path_to_down->hops[0].in_hop_index = 8;
+ tunnel->path_to_down->hops[0].in_counter_index = -1;
+ tunnel->path_to_down->hops[0].out_port = tb_upstream_port(up->sw);
+ tunnel->path_to_down->hops[0].next_hop_index = 8;
+
+ tunnel->path_to_down->hops[1].in_port =
+ tb_upstream_port(up->sw)->remote;
+ tunnel->path_to_down->hops[1].in_hop_index = 8;
+ tunnel->path_to_down->hops[1].in_counter_index = -1;
+ tunnel->path_to_down->hops[1].out_port = down;
+ tunnel->path_to_down->hops[1].next_hop_index = 8;
+ return tunnel;
+
+err:
+ if (tunnel) {
+ if (tunnel->path_to_down)
+ tb_path_free(tunnel->path_to_down);
+ if (tunnel->path_to_up)
+ tb_path_free(tunnel->path_to_up);
+ kfree(tunnel);
+ }
+ return NULL;
+}
+
+/**
+ * tb_pci_free() - free a tunnel
+ *
+ * The tunnel must have been deactivated.
+ */
+void tb_pci_free(struct tb_pci_tunnel *tunnel)
+{
+ if (tunnel->path_to_up->activated || tunnel->path_to_down->activated) {
+ tb_tunnel_WARN(tunnel, "trying to free an activated tunnel\n");
+ return;
+ }
+ tb_path_free(tunnel->path_to_up);
+ tb_path_free(tunnel->path_to_down);
+ kfree(tunnel);
+}
+
+/**
+ * tb_pci_is_invalid - check whether an activated path is still valid
+ */
+bool tb_pci_is_invalid(struct tb_pci_tunnel *tunnel)
+{
+ WARN_ON(!tunnel->path_to_up->activated);
+ WARN_ON(!tunnel->path_to_down->activated);
+
+ return tb_path_is_invalid(tunnel->path_to_up)
+ || tb_path_is_invalid(tunnel->path_to_down);
+}
+
+/**
+ * tb_pci_port_active() - activate/deactivate PCI capability
+ *
+ * Return: Returns 0 on success or an error code on failure.
+ */
+static int tb_pci_port_active(struct tb_port *port, bool active)
+{
+ u32 word = active ? 0x80000000 : 0x0;
+ int cap = tb_find_cap(port, TB_CFG_PORT, TB_CAP_PCIE);
+ if (cap <= 0) {
+ tb_port_warn(port, "TB_CAP_PCIE not found: %d\n", cap);
+ return cap ? cap : -ENXIO;
+ }
+ return tb_port_write(port, &word, TB_CFG_PORT, cap, 1);
+}
+
+/**
+ * tb_pci_restart() - activate a tunnel after a hardware reset
+ */
+int tb_pci_restart(struct tb_pci_tunnel *tunnel)
+{
+ int res;
+ tunnel->path_to_up->activated = false;
+ tunnel->path_to_down->activated = false;
+
+ tb_tunnel_info(tunnel, "activating\n");
+
+ res = tb_path_activate(tunnel->path_to_up);
+ if (res)
+ goto err;
+ res = tb_path_activate(tunnel->path_to_down);
+ if (res)
+ goto err;
+
+ res = tb_pci_port_active(tunnel->down_port, true);
+ if (res)
+ goto err;
+
+ res = tb_pci_port_active(tunnel->up_port, true);
+ if (res)
+ goto err;
+ return 0;
+err:
+ tb_tunnel_warn(tunnel, "activation failed\n");
+ tb_pci_deactivate(tunnel);
+ return res;
+}
+
+/**
+ * tb_pci_activate() - activate a tunnel
+ *
+ * Return: Returns 0 on success or an error code on failure.
+ */
+int tb_pci_activate(struct tb_pci_tunnel *tunnel)
+{
+ int res;
+ if (tunnel->path_to_up->activated || tunnel->path_to_down->activated) {
+ tb_tunnel_WARN(tunnel,
+ "trying to activate an already activated tunnel\n");
+ return -EINVAL;
+ }
+
+ res = tb_pci_restart(tunnel);
+ if (res)
+ return res;
+
+ list_add(&tunnel->list, &tunnel->tb->tunnel_list);
+ return 0;
+}
+
+
+
+/**
+ * tb_pci_deactivate() - deactivate a tunnel
+ */
+void tb_pci_deactivate(struct tb_pci_tunnel *tunnel)
+{
+ tb_tunnel_info(tunnel, "deactivating\n");
+ /*
+ * TODO: enable reset by writing 0x04000000 to TB_CAP_PCIE + 1 on up
+ * port. Seems to have no effect?
+ */
+ tb_pci_port_active(tunnel->up_port, false);
+ tb_pci_port_active(tunnel->down_port, false);
+ if (tunnel->path_to_down->activated)
+ tb_path_deactivate(tunnel->path_to_down);
+ if (tunnel->path_to_up->activated)
+ tb_path_deactivate(tunnel->path_to_up);
+ list_del_init(&tunnel->list);
+}
+
diff --git a/drivers/thunderbolt/tunnel_pci.h b/drivers/thunderbolt/tunnel_pci.h
new file mode 100644
index 0000000..a67f93c
--- /dev/null
+++ b/drivers/thunderbolt/tunnel_pci.h
@@ -0,0 +1,30 @@
+/*
+ * Thunderbolt Cactus Ridge driver - PCIe tunnel
+ *
+ * Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com>
+ */
+
+#ifndef TB_PCI_H_
+#define TB_PCI_H_
+
+#include "tb.h"
+
+struct tb_pci_tunnel {
+ struct tb *tb;
+ struct tb_port *up_port;
+ struct tb_port *down_port;
+ struct tb_path *path_to_up;
+ struct tb_path *path_to_down;
+ struct list_head list;
+};
+
+struct tb_pci_tunnel *tb_pci_alloc(struct tb *tb, struct tb_port *up,
+ struct tb_port *down);
+void tb_pci_free(struct tb_pci_tunnel *tunnel);
+int tb_pci_activate(struct tb_pci_tunnel *tunnel);
+int tb_pci_restart(struct tb_pci_tunnel *tunnel);
+void tb_pci_deactivate(struct tb_pci_tunnel *tunnel);
+bool tb_pci_is_invalid(struct tb_pci_tunnel *tunnel);
+
+#endif
+

View File

@ -0,0 +1,259 @@
From: Andreas Noever <andreas.noever@gmail.com>
Date: Tue, 3 Jun 2014 22:04:11 +0200
Subject: [12/31] thunderbolt: Read switch uid from EEPROM
Origin: https://git.kernel.org/linus/c90553b3c4ac2389a71a5c012b6e5bb1160d48a7
Add eeprom access code and read the uid during switch initialization.
The UID will be used to check device identity after suspend.
Signed-off-by: Andreas Noever <andreas.noever@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
drivers/thunderbolt/Makefile | 2 +-
drivers/thunderbolt/eeprom.c | 189 +++++++++++++++++++++++++++++++++++++++++++
drivers/thunderbolt/switch.c | 5 ++
drivers/thunderbolt/tb.h | 3 +
4 files changed, 198 insertions(+), 1 deletion(-)
create mode 100644 drivers/thunderbolt/eeprom.c
diff --git a/drivers/thunderbolt/Makefile b/drivers/thunderbolt/Makefile
index 0122ca6..5d1053c 100644
--- a/drivers/thunderbolt/Makefile
+++ b/drivers/thunderbolt/Makefile
@@ -1,3 +1,3 @@
obj-${CONFIG_THUNDERBOLT} := thunderbolt.o
-thunderbolt-objs := nhi.o ctl.o tb.o switch.o cap.o path.o tunnel_pci.o
+thunderbolt-objs := nhi.o ctl.o tb.o switch.o cap.o path.o tunnel_pci.o eeprom.o
diff --git a/drivers/thunderbolt/eeprom.c b/drivers/thunderbolt/eeprom.c
new file mode 100644
index 0000000..f28e402
--- /dev/null
+++ b/drivers/thunderbolt/eeprom.c
@@ -0,0 +1,189 @@
+/*
+ * Thunderbolt Cactus Ridge driver - eeprom access
+ *
+ * Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com>
+ */
+
+#include "tb.h"
+
+/**
+ * tb_eeprom_ctl_write() - write control word
+ */
+static int tb_eeprom_ctl_write(struct tb_switch *sw, struct tb_eeprom_ctl *ctl)
+{
+ return tb_sw_write(sw, ctl, TB_CFG_SWITCH, sw->cap_plug_events + 4, 1);
+}
+
+/**
+ * tb_eeprom_ctl_write() - read control word
+ */
+static int tb_eeprom_ctl_read(struct tb_switch *sw, struct tb_eeprom_ctl *ctl)
+{
+ return tb_sw_read(sw, ctl, TB_CFG_SWITCH, sw->cap_plug_events + 4, 1);
+}
+
+enum tb_eeprom_transfer {
+ TB_EEPROM_IN,
+ TB_EEPROM_OUT,
+};
+
+/**
+ * tb_eeprom_active - enable rom access
+ *
+ * WARNING: Always disable access after usage. Otherwise the controller will
+ * fail to reprobe.
+ */
+static int tb_eeprom_active(struct tb_switch *sw, bool enable)
+{
+ struct tb_eeprom_ctl ctl;
+ int res = tb_eeprom_ctl_read(sw, &ctl);
+ if (res)
+ return res;
+ if (enable) {
+ ctl.access_high = 1;
+ res = tb_eeprom_ctl_write(sw, &ctl);
+ if (res)
+ return res;
+ ctl.access_low = 0;
+ return tb_eeprom_ctl_write(sw, &ctl);
+ } else {
+ ctl.access_low = 1;
+ res = tb_eeprom_ctl_write(sw, &ctl);
+ if (res)
+ return res;
+ ctl.access_high = 0;
+ return tb_eeprom_ctl_write(sw, &ctl);
+ }
+}
+
+/**
+ * tb_eeprom_transfer - transfer one bit
+ *
+ * If TB_EEPROM_IN is passed, then the bit can be retrieved from ctl->data_in.
+ * If TB_EEPROM_OUT is passed, then ctl->data_out will be written.
+ */
+static int tb_eeprom_transfer(struct tb_switch *sw, struct tb_eeprom_ctl *ctl,
+ enum tb_eeprom_transfer direction)
+{
+ int res;
+ if (direction == TB_EEPROM_OUT) {
+ res = tb_eeprom_ctl_write(sw, ctl);
+ if (res)
+ return res;
+ }
+ ctl->clock = 1;
+ res = tb_eeprom_ctl_write(sw, ctl);
+ if (res)
+ return res;
+ if (direction == TB_EEPROM_IN) {
+ res = tb_eeprom_ctl_read(sw, ctl);
+ if (res)
+ return res;
+ }
+ ctl->clock = 0;
+ return tb_eeprom_ctl_write(sw, ctl);
+}
+
+/**
+ * tb_eeprom_out - write one byte to the bus
+ */
+static int tb_eeprom_out(struct tb_switch *sw, u8 val)
+{
+ struct tb_eeprom_ctl ctl;
+ int i;
+ int res = tb_eeprom_ctl_read(sw, &ctl);
+ if (res)
+ return res;
+ for (i = 0; i < 8; i++) {
+ ctl.data_out = val & 0x80;
+ res = tb_eeprom_transfer(sw, &ctl, TB_EEPROM_OUT);
+ if (res)
+ return res;
+ val <<= 1;
+ }
+ return 0;
+}
+
+/**
+ * tb_eeprom_in - read one byte from the bus
+ */
+static int tb_eeprom_in(struct tb_switch *sw, u8 *val)
+{
+ struct tb_eeprom_ctl ctl;
+ int i;
+ int res = tb_eeprom_ctl_read(sw, &ctl);
+ if (res)
+ return res;
+ *val = 0;
+ for (i = 0; i < 8; i++) {
+ *val <<= 1;
+ res = tb_eeprom_transfer(sw, &ctl, TB_EEPROM_IN);
+ if (res)
+ return res;
+ *val |= ctl.data_in;
+ }
+ return 0;
+}
+
+/**
+ * tb_eeprom_read_n - read count bytes from offset into val
+ */
+static int tb_eeprom_read_n(struct tb_switch *sw, u16 offset, u8 *val,
+ size_t count)
+{
+ int i, res;
+ res = tb_eeprom_active(sw, true);
+ if (res)
+ return res;
+ res = tb_eeprom_out(sw, 3);
+ if (res)
+ return res;
+ res = tb_eeprom_out(sw, offset >> 8);
+ if (res)
+ return res;
+ res = tb_eeprom_out(sw, offset);
+ if (res)
+ return res;
+ for (i = 0; i < count; i++) {
+ res = tb_eeprom_in(sw, val + i);
+ if (res)
+ return res;
+ }
+ return tb_eeprom_active(sw, false);
+}
+
+int tb_eeprom_read_uid(struct tb_switch *sw, u64 *uid)
+{
+ u8 data[9];
+ struct tb_cap_plug_events cap;
+ int res;
+ if (!sw->cap_plug_events) {
+ tb_sw_warn(sw, "no TB_CAP_PLUG_EVENTS, cannot read eeprom\n");
+ return -ENOSYS;
+ }
+ res = tb_sw_read(sw, &cap, TB_CFG_SWITCH, sw->cap_plug_events,
+ sizeof(cap) / 4);
+ if (res)
+ return res;
+ if (!cap.eeprom_ctl.present || cap.eeprom_ctl.not_present) {
+ tb_sw_warn(sw, "no NVM\n");
+ return -ENOSYS;
+ }
+
+ if (cap.drom_offset > 0xffff) {
+ tb_sw_warn(sw, "drom offset is larger than 0xffff: %#x\n",
+ cap.drom_offset);
+ return -ENXIO;
+ }
+
+ /* read uid */
+ res = tb_eeprom_read_n(sw, cap.drom_offset, data, 9);
+ if (res)
+ return res;
+ /* TODO: check checksum in data[0] */
+ *uid = *(u64 *)(data+1);
+ return 0;
+}
+
+
+
diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c
index 667413f..aeb5c30 100644
--- a/drivers/thunderbolt/switch.c
+++ b/drivers/thunderbolt/switch.c
@@ -376,6 +376,11 @@ struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route)
}
sw->cap_plug_events = cap;
+ if (tb_eeprom_read_uid(sw, &sw->uid))
+ tb_sw_warn(sw, "could not read uid from eeprom\n");
+ else
+ tb_sw_info(sw, "uid: %#llx\n", sw->uid);
+
if (tb_plug_events_active(sw, true))
goto err;
diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h
index 508abc4..a89087f 100644
--- a/drivers/thunderbolt/tb.h
+++ b/drivers/thunderbolt/tb.h
@@ -19,6 +19,7 @@ struct tb_switch {
struct tb_regs_switch_header config;
struct tb_port *ports;
struct tb *tb;
+ u64 uid;
int cap_plug_events; /* offset, zero if not found */
bool is_unplugged; /* unplugged, will go away */
};
@@ -231,6 +232,8 @@ int tb_path_activate(struct tb_path *path);
void tb_path_deactivate(struct tb_path *path);
bool tb_path_is_invalid(struct tb_path *path);
+int tb_eeprom_read_uid(struct tb_switch *sw, u64 *uid);
+
static inline int tb_route_length(u64 route)
{

View File

@ -0,0 +1,276 @@
From: Andreas Noever <andreas.noever@gmail.com>
Date: Tue, 3 Jun 2014 22:04:12 +0200
Subject: [13/31] thunderbolt: Add suspend/hibernate support
Origin: https://git.kernel.org/linus/23dd5bb49d986f37977ed80dd2ca65040ead4392
We use _noirq since we have to restore the pci tunnels before the pci
core wakes the tunneled devices.
Signed-off-by: Andreas Noever <andreas.noever@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
drivers/thunderbolt/nhi.c | 33 +++++++++++++++++
drivers/thunderbolt/switch.c | 84 ++++++++++++++++++++++++++++++++++++++++++++
drivers/thunderbolt/tb.c | 61 ++++++++++++++++++++++++++++++++
drivers/thunderbolt/tb.h | 5 +++
4 files changed, 183 insertions(+)
diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c
index d2b9ce8..346b41e 100644
--- a/drivers/thunderbolt/nhi.c
+++ b/drivers/thunderbolt/nhi.c
@@ -7,6 +7,7 @@
* Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com>
*/
+#include <linux/pm_runtime.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/pci.h>
@@ -492,6 +493,22 @@ static irqreturn_t nhi_msi(int irq, void *data)
return IRQ_HANDLED;
}
+static int nhi_suspend_noirq(struct device *dev)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+ struct tb *tb = pci_get_drvdata(pdev);
+ thunderbolt_suspend(tb);
+ return 0;
+}
+
+static int nhi_resume_noirq(struct device *dev)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+ struct tb *tb = pci_get_drvdata(pdev);
+ thunderbolt_resume(tb);
+ return 0;
+}
+
static void nhi_shutdown(struct tb_nhi *nhi)
{
int i;
@@ -600,6 +617,21 @@ static void nhi_remove(struct pci_dev *pdev)
nhi_shutdown(nhi);
}
+/*
+ * The tunneled pci bridges are siblings of us. Use resume_noirq to reenable
+ * the tunnels asap. A corresponding pci quirk blocks the downstream bridges
+ * resume_noirq until we are done.
+ */
+static const struct dev_pm_ops nhi_pm_ops = {
+ .suspend_noirq = nhi_suspend_noirq,
+ .resume_noirq = nhi_resume_noirq,
+ .freeze_noirq = nhi_suspend_noirq, /*
+ * we just disable hotplug, the
+ * pci-tunnels stay alive.
+ */
+ .restore_noirq = nhi_resume_noirq,
+};
+
struct pci_device_id nhi_ids[] = {
/*
* We have to specify class, the TB bridges use the same device and
@@ -626,6 +658,7 @@ static struct pci_driver nhi_driver = {
.id_table = nhi_ids,
.probe = nhi_probe,
.remove = nhi_remove,
+ .driver.pm = &nhi_pm_ops,
};
static int __init nhi_init(void)
diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c
index aeb5c30..c2a24b6 100644
--- a/drivers/thunderbolt/switch.c
+++ b/drivers/thunderbolt/switch.c
@@ -229,6 +229,30 @@ static void tb_dump_switch(struct tb *tb, struct tb_regs_switch_header *sw)
sw->__unknown1, sw->__unknown4);
}
+/**
+ * reset_switch() - reconfigure route, enable and send TB_CFG_PKG_RESET
+ *
+ * Return: Returns 0 on success or an error code on failure.
+ */
+int tb_switch_reset(struct tb *tb, u64 route)
+{
+ struct tb_cfg_result res;
+ struct tb_regs_switch_header header = {
+ header.route_hi = route >> 32,
+ header.route_lo = route,
+ header.enabled = true,
+ };
+ tb_info(tb, "resetting switch at %llx\n", route);
+ res.err = tb_cfg_write(tb->ctl, ((u32 *) &header) + 2, route,
+ 0, 2, 2, 2);
+ if (res.err)
+ return res.err;
+ res = tb_cfg_reset(tb->ctl, route, TB_CFG_DEFAULT_TIMEOUT);
+ if (res.err > 0)
+ return -EIO;
+ return res.err;
+}
+
struct tb_switch *get_switch_at_route(struct tb_switch *sw, u64 route)
{
u8 next_port = route; /*
@@ -412,3 +436,63 @@ void tb_sw_set_unpplugged(struct tb_switch *sw)
}
}
+int tb_switch_resume(struct tb_switch *sw)
+{
+ int i, err;
+ u64 uid;
+ tb_sw_info(sw, "resuming switch\n");
+
+ err = tb_eeprom_read_uid(sw, &uid);
+ if (err) {
+ tb_sw_warn(sw, "uid read failed\n");
+ return err;
+ }
+ if (sw->uid != uid) {
+ tb_sw_info(sw,
+ "changed while suspended (uid %#llx -> %#llx)\n",
+ sw->uid, uid);
+ return -ENODEV;
+ }
+
+ /* upload configuration */
+ err = tb_sw_write(sw, 1 + (u32 *) &sw->config, TB_CFG_SWITCH, 1, 3);
+ if (err)
+ return err;
+
+ err = tb_plug_events_active(sw, true);
+ if (err)
+ return err;
+
+ /* check for surviving downstream switches */
+ for (i = 1; i <= sw->config.max_port_number; i++) {
+ struct tb_port *port = &sw->ports[i];
+ if (tb_is_upstream_port(port))
+ continue;
+ if (!port->remote)
+ continue;
+ if (tb_wait_for_port(port, true) <= 0
+ || tb_switch_resume(port->remote->sw)) {
+ tb_port_warn(port,
+ "lost during suspend, disconnecting\n");
+ tb_sw_set_unpplugged(port->remote->sw);
+ }
+ }
+ return 0;
+}
+
+void tb_switch_suspend(struct tb_switch *sw)
+{
+ int i, err;
+ err = tb_plug_events_active(sw, false);
+ if (err)
+ return;
+
+ for (i = 1; i <= sw->config.max_port_number; i++) {
+ if (!tb_is_upstream_port(&sw->ports[i]) && sw->ports[i].remote)
+ tb_switch_suspend(sw->ports[i].remote->sw);
+ }
+ /*
+ * TODO: invoke tb_cfg_prepare_to_sleep here? does not seem to have any
+ * effect?
+ */
+}
diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c
index 177f61d..1aa6dd7 100644
--- a/drivers/thunderbolt/tb.c
+++ b/drivers/thunderbolt/tb.c
@@ -69,6 +69,28 @@ static void tb_free_invalid_tunnels(struct tb *tb)
}
/**
+ * tb_free_unplugged_children() - traverse hierarchy and free unplugged switches
+ */
+static void tb_free_unplugged_children(struct tb_switch *sw)
+{
+ int i;
+ for (i = 1; i <= sw->config.max_port_number; i++) {
+ struct tb_port *port = &sw->ports[i];
+ if (tb_is_upstream_port(port))
+ continue;
+ if (!port->remote)
+ continue;
+ if (port->remote->sw->is_unplugged) {
+ tb_switch_free(port->remote->sw);
+ port->remote = NULL;
+ } else {
+ tb_free_unplugged_children(port->remote->sw);
+ }
+ }
+}
+
+
+/**
* find_pci_up_port() - return the first PCIe up port on @sw or NULL
*/
static struct tb_port *tb_find_pci_up_port(struct tb_switch *sw)
@@ -368,3 +390,42 @@ err_locked:
return NULL;
}
+void thunderbolt_suspend(struct tb *tb)
+{
+ tb_info(tb, "suspending...\n");
+ mutex_lock(&tb->lock);
+ tb_switch_suspend(tb->root_switch);
+ tb_ctl_stop(tb->ctl);
+ tb->hotplug_active = false; /* signal tb_handle_hotplug to quit */
+ mutex_unlock(&tb->lock);
+ tb_info(tb, "suspend finished\n");
+}
+
+void thunderbolt_resume(struct tb *tb)
+{
+ struct tb_pci_tunnel *tunnel, *n;
+ tb_info(tb, "resuming...\n");
+ mutex_lock(&tb->lock);
+ tb_ctl_start(tb->ctl);
+
+ /* remove any pci devices the firmware might have setup */
+ tb_switch_reset(tb, 0);
+
+ tb_switch_resume(tb->root_switch);
+ tb_free_invalid_tunnels(tb);
+ tb_free_unplugged_children(tb->root_switch);
+ list_for_each_entry_safe(tunnel, n, &tb->tunnel_list, list)
+ tb_pci_restart(tunnel);
+ if (!list_empty(&tb->tunnel_list)) {
+ /*
+ * the pcie links need some time to get going.
+ * 100ms works for me...
+ */
+ tb_info(tb, "tunnels restarted, sleeping for 100ms\n");
+ msleep(100);
+ }
+ /* Allow tb_handle_hotplug to progress events */
+ tb->hotplug_active = true;
+ mutex_unlock(&tb->lock);
+ tb_info(tb, "resume finished\n");
+}
diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h
index a89087f..63e89d0 100644
--- a/drivers/thunderbolt/tb.h
+++ b/drivers/thunderbolt/tb.h
@@ -214,9 +214,14 @@ static inline int tb_port_write(struct tb_port *port, void *buffer,
struct tb *thunderbolt_alloc_and_start(struct tb_nhi *nhi);
void thunderbolt_shutdown_and_free(struct tb *tb);
+void thunderbolt_suspend(struct tb *tb);
+void thunderbolt_resume(struct tb *tb);
struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route);
void tb_switch_free(struct tb_switch *sw);
+void tb_switch_suspend(struct tb_switch *sw);
+int tb_switch_resume(struct tb_switch *sw);
+int tb_switch_reset(struct tb *tb, u64 route);
void tb_sw_set_unpplugged(struct tb_switch *sw);
struct tb_switch *get_switch_at_route(struct tb_switch *sw, u64 route);

View File

@ -0,0 +1,380 @@
From: Andreas Noever <andreas.noever@gmail.com>
Date: Thu, 12 Jun 2014 23:11:46 +0200
Subject: [14/31] thunderbolt: Read port configuration from eeprom.
Origin: https://git.kernel.org/linus/cd22e73bdf5eff7e68a0f8bdfbce123ad43651f6
All Thunderbolt switches (except the root switch) contain a drom which
contains information about the device. Right now we only read the UID.
Add code to read and parse this drom. For now we are only interested in
which ports are disabled and which ports are "dual link ports" (a
physical thunderbolt port/socket contains two such ports).
Signed-off-by: Andreas Noever <andreas.noever@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
drivers/thunderbolt/eeprom.c | 266 ++++++++++++++++++++++++++++++++++++++++++-
drivers/thunderbolt/switch.c | 4 +-
drivers/thunderbolt/tb.h | 7 +-
3 files changed, 270 insertions(+), 7 deletions(-)
diff --git a/drivers/thunderbolt/eeprom.c b/drivers/thunderbolt/eeprom.c
index f28e402..0d5a80b 100644
--- a/drivers/thunderbolt/eeprom.c
+++ b/drivers/thunderbolt/eeprom.c
@@ -4,6 +4,7 @@
* Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com>
*/
+#include <linux/crc32.h>
#include "tb.h"
/**
@@ -152,9 +153,86 @@ static int tb_eeprom_read_n(struct tb_switch *sw, u16 offset, u8 *val,
return tb_eeprom_active(sw, false);
}
-int tb_eeprom_read_uid(struct tb_switch *sw, u64 *uid)
+static u8 tb_crc8(u8 *data, int len)
+{
+ int i, j;
+ u8 val = 0xff;
+ for (i = 0; i < len; i++) {
+ val ^= data[i];
+ for (j = 0; j < 8; j++)
+ val = (val << 1) ^ ((val & 0x80) ? 7 : 0);
+ }
+ return val;
+}
+
+static u32 tb_crc32(void *data, size_t len)
+{
+ return ~__crc32c_le(~0, data, len);
+}
+
+#define TB_DROM_DATA_START 13
+struct tb_drom_header {
+ /* BYTE 0 */
+ u8 uid_crc8; /* checksum for uid */
+ /* BYTES 1-8 */
+ u64 uid;
+ /* BYTES 9-12 */
+ u32 data_crc32; /* checksum for data_len bytes starting at byte 13 */
+ /* BYTE 13 */
+ u8 device_rom_revision; /* should be <= 1 */
+ u16 data_len:10;
+ u8 __unknown1:6;
+ /* BYTES 16-21 */
+ u16 vendor_id;
+ u16 model_id;
+ u8 model_rev;
+ u8 eeprom_rev;
+} __packed;
+
+enum tb_drom_entry_type {
+ TB_DROM_ENTRY_GENERIC,
+ TB_DROM_ENTRY_PORT,
+};
+
+struct tb_drom_entry_header {
+ u8 len;
+ u8 index:6;
+ bool port_disabled:1; /* only valid if type is TB_DROM_ENTRY_PORT */
+ enum tb_drom_entry_type type:1;
+} __packed;
+
+struct tb_drom_entry_port {
+ /* BYTES 0-1 */
+ struct tb_drom_entry_header header;
+ /* BYTE 2 */
+ u8 dual_link_port_rid:4;
+ u8 link_nr:1;
+ u8 unknown1:2;
+ bool has_dual_link_port:1;
+
+ /* BYTE 3 */
+ u8 dual_link_port_nr:6;
+ u8 unknown2:2;
+
+ /* BYTES 4 - 5 TODO decode */
+ u8 micro2:4;
+ u8 micro1:4;
+ u8 micro3;
+
+ /* BYTES 5-6, TODO: verify (find hardware that has these set) */
+ u8 peer_port_rid:4;
+ u8 unknown3:3;
+ bool has_peer_port:1;
+ u8 peer_port_nr:6;
+ u8 unknown4:2;
+} __packed;
+
+
+/**
+ * tb_eeprom_get_drom_offset - get drom offset within eeprom
+ */
+int tb_eeprom_get_drom_offset(struct tb_switch *sw, u16 *offset)
{
- u8 data[9];
struct tb_cap_plug_events cap;
int res;
if (!sw->cap_plug_events) {
@@ -165,6 +243,7 @@ int tb_eeprom_read_uid(struct tb_switch *sw, u64 *uid)
sizeof(cap) / 4);
if (res)
return res;
+
if (!cap.eeprom_ctl.present || cap.eeprom_ctl.not_present) {
tb_sw_warn(sw, "no NVM\n");
return -ENOSYS;
@@ -175,15 +254,194 @@ int tb_eeprom_read_uid(struct tb_switch *sw, u64 *uid)
cap.drom_offset);
return -ENXIO;
}
+ *offset = cap.drom_offset;
+ return 0;
+}
+
+/**
+ * tb_drom_read_uid_only - read uid directly from drom
+ *
+ * Does not use the cached copy in sw->drom. Used during resume to check switch
+ * identity.
+ */
+int tb_drom_read_uid_only(struct tb_switch *sw, u64 *uid)
+{
+ u8 data[9];
+ u16 drom_offset;
+ u8 crc;
+ int res = tb_eeprom_get_drom_offset(sw, &drom_offset);
+ if (res)
+ return res;
/* read uid */
- res = tb_eeprom_read_n(sw, cap.drom_offset, data, 9);
+ res = tb_eeprom_read_n(sw, drom_offset, data, 9);
if (res)
return res;
- /* TODO: check checksum in data[0] */
+
+ crc = tb_crc8(data + 1, 8);
+ if (crc != data[0]) {
+ tb_sw_warn(sw, "uid crc8 missmatch (expected: %#x, got: %#x)\n",
+ data[0], crc);
+ return -EIO;
+ }
+
*uid = *(u64 *)(data+1);
return 0;
}
+static void tb_drom_parse_port_entry(struct tb_port *port,
+ struct tb_drom_entry_port *entry)
+{
+ port->link_nr = entry->link_nr;
+ if (entry->has_dual_link_port)
+ port->dual_link_port =
+ &port->sw->ports[entry->dual_link_port_nr];
+}
+
+static int tb_drom_parse_entry(struct tb_switch *sw,
+ struct tb_drom_entry_header *header)
+{
+ struct tb_port *port;
+ int res;
+ enum tb_port_type type;
+ if (header->type != TB_DROM_ENTRY_PORT)
+ return 0;
+ port = &sw->ports[header->index];
+ port->disabled = header->port_disabled;
+ if (port->disabled)
+ return 0;
+
+ res = tb_port_read(port, &type, TB_CFG_PORT, 2, 1);
+ if (res)
+ return res;
+ type &= 0xffffff;
+
+ if (type == TB_TYPE_PORT) {
+ struct tb_drom_entry_port *entry = (void *) header;
+ if (header->len != sizeof(*entry)) {
+ tb_sw_warn(sw,
+ "port entry has size %#x (expected %#lx)\n",
+ header->len, sizeof(struct tb_drom_entry_port));
+ return -EIO;
+ }
+ tb_drom_parse_port_entry(port, entry);
+ }
+ return 0;
+}
+
+/**
+ * tb_drom_parse_entries - parse the linked list of drom entries
+ *
+ * Drom must have been copied to sw->drom.
+ */
+static int tb_drom_parse_entries(struct tb_switch *sw)
+{
+ struct tb_drom_header *header = (void *) sw->drom;
+ u16 pos = sizeof(*header);
+ u16 drom_size = header->data_len + TB_DROM_DATA_START;
+
+ 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) {
+ tb_sw_warn(sw, "drom buffer overrun, aborting\n");
+ return -EIO;
+ }
+
+ tb_drom_parse_entry(sw, entry);
+
+ pos += entry->len;
+ }
+ return 0;
+}
+
+/**
+ * tb_drom_read - copy drom to sw->drom and parse it
+ */
+int tb_drom_read(struct tb_switch *sw)
+{
+ u16 drom_offset;
+ u16 size;
+ u32 crc;
+ struct tb_drom_header *header;
+ int res;
+ if (sw->drom)
+ return 0;
+
+ if (tb_route(sw) == 0) {
+ /*
+ * The root switch contains only a dummy drom (header only,
+ * no entries). Hardcode the configuration here.
+ */
+ tb_drom_read_uid_only(sw, &sw->uid);
+
+ sw->ports[1].link_nr = 0;
+ sw->ports[2].link_nr = 1;
+ sw->ports[1].dual_link_port = &sw->ports[2];
+ sw->ports[2].dual_link_port = &sw->ports[1];
+
+ sw->ports[3].link_nr = 0;
+ sw->ports[4].link_nr = 1;
+ sw->ports[3].dual_link_port = &sw->ports[4];
+ sw->ports[4].dual_link_port = &sw->ports[3];
+ return 0;
+ }
+
+ res = tb_eeprom_get_drom_offset(sw, &drom_offset);
+ if (res)
+ return res;
+
+ res = tb_eeprom_read_n(sw, drom_offset + 14, (u8 *) &size, 2);
+ if (res)
+ return res;
+ size &= 0x3ff;
+ size += TB_DROM_DATA_START;
+ tb_sw_info(sw, "reading drom (length: %#x)\n", size);
+ if (size < sizeof(*header)) {
+ tb_sw_warn(sw, "drom too small, aborting\n");
+ return -EIO;
+ }
+
+ sw->drom = kzalloc(size, GFP_KERNEL);
+ if (!sw->drom)
+ return -ENOMEM;
+ res = tb_eeprom_read_n(sw, drom_offset, sw->drom, size);
+ if (res)
+ goto err;
+
+ header = (void *) sw->drom;
+
+ if (header->data_len + TB_DROM_DATA_START != size) {
+ tb_sw_warn(sw, "drom size mismatch, aborting\n");
+ goto err;
+ }
+
+ crc = tb_crc8((u8 *) &header->uid, 8);
+ if (crc != header->uid_crc8) {
+ tb_sw_warn(sw,
+ "drom uid crc8 mismatch (expected: %#x, got: %#x), aborting\n",
+ header->uid_crc8, crc);
+ goto err;
+ }
+ sw->uid = header->uid;
+
+ crc = tb_crc32(sw->drom + TB_DROM_DATA_START, header->data_len);
+ if (crc != header->data_crc32) {
+ tb_sw_warn(sw,
+ "drom data crc32 mismatch (expected: %#x, got: %#x), aborting\n",
+ header->data_crc32, crc);
+ goto err;
+ }
+
+ if (header->device_rom_revision > 1)
+ tb_sw_warn(sw, "drom device_rom_revision %#x unknown\n",
+ header->device_rom_revision);
+
+ return tb_drom_parse_entries(sw);
+err:
+ kfree(sw->drom);
+ return -EIO;
+
+}
diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c
index c2a24b6..9dfb8e1 100644
--- a/drivers/thunderbolt/switch.c
+++ b/drivers/thunderbolt/switch.c
@@ -400,7 +400,7 @@ struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route)
}
sw->cap_plug_events = cap;
- if (tb_eeprom_read_uid(sw, &sw->uid))
+ if (tb_drom_read_uid_only(sw, &sw->uid))
tb_sw_warn(sw, "could not read uid from eeprom\n");
else
tb_sw_info(sw, "uid: %#llx\n", sw->uid);
@@ -442,7 +442,7 @@ int tb_switch_resume(struct tb_switch *sw)
u64 uid;
tb_sw_info(sw, "resuming switch\n");
- err = tb_eeprom_read_uid(sw, &uid);
+ err = tb_drom_read_uid_only(sw, &uid);
if (err) {
tb_sw_warn(sw, "uid read failed\n");
return err;
diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h
index 63e89d0..18ade5e 100644
--- a/drivers/thunderbolt/tb.h
+++ b/drivers/thunderbolt/tb.h
@@ -22,6 +22,7 @@ struct tb_switch {
u64 uid;
int cap_plug_events; /* offset, zero if not found */
bool is_unplugged; /* unplugged, will go away */
+ u8 *drom;
};
/**
@@ -33,6 +34,9 @@ struct tb_port {
struct tb_port *remote; /* remote port, NULL if not connected */
int cap_phy; /* offset, zero if not found */
u8 port; /* port number on switch */
+ bool disabled; /* disabled by eeprom */
+ struct tb_port *dual_link_port;
+ u8 link_nr:1;
};
/**
@@ -237,7 +241,8 @@ int tb_path_activate(struct tb_path *path);
void tb_path_deactivate(struct tb_path *path);
bool tb_path_is_invalid(struct tb_path *path);
-int tb_eeprom_read_uid(struct tb_switch *sw, u64 *uid);
+int tb_drom_read(struct tb_switch *sw);
+int tb_drom_read_uid_only(struct tb_switch *sw, u64 *uid);
static inline int tb_route_length(u64 route)

View File

@ -0,0 +1,159 @@
From: Andreas Noever <andreas.noever@gmail.com>
Date: Thu, 12 Jun 2014 23:11:47 +0200
Subject: [15/31] thunderbolt: Fix nontrivial endpoint devices.
Origin: https://git.kernel.org/linus/343fcb8c70d76967ba64493ca984e40baad9d0f6
Fix issues observed with the Startech docking station:
Fix the type of the route parameter in tb_ctl_rx. It should be u64 and not
u8 (which only worked for short routes).
A thunderbolt cable contains two lanes. If both endpoints support it a
connection will be established on both lanes. Previously we tried to
scan below both "dual link ports". Use the information extracted from
the drom to only scan behind ports with lane_nr == 0.
Endpoints with more complex thunderbolt controllers have some of their
ports disabled (for example the NHI port or one of the HDMI/DP ports).
Accessing them results in an error so we now ignore ports which are
marked as disabled in the drom.
Signed-off-by: Andreas Noever <andreas.noever@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
drivers/thunderbolt/ctl.c | 2 +-
drivers/thunderbolt/switch.c | 42 +++++++++++++++++++++++++-----------------
drivers/thunderbolt/tb.c | 5 +++++
3 files changed, 31 insertions(+), 18 deletions(-)
diff --git a/drivers/thunderbolt/ctl.c b/drivers/thunderbolt/ctl.c
index 9b0120b..d04fee4 100644
--- a/drivers/thunderbolt/ctl.c
+++ b/drivers/thunderbolt/ctl.c
@@ -439,7 +439,7 @@ rx:
*/
static struct tb_cfg_result tb_ctl_rx(struct tb_ctl *ctl, void *buffer,
size_t length, int timeout_msec,
- u8 route, enum tb_cfg_pkg_type type)
+ u64 route, enum tb_cfg_pkg_type type)
{
struct tb_cfg_result res;
struct ctl_pkg *pkg;
diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c
index 9dfb8e1..0d50e7e 100644
--- a/drivers/thunderbolt/switch.c
+++ b/drivers/thunderbolt/switch.c
@@ -180,20 +180,17 @@ int tb_port_clear_counter(struct tb_port *port, int counter)
*
* Return: Returns 0 on success or an error code on failure.
*/
-static int tb_init_port(struct tb_switch *sw, u8 port_nr)
+static int tb_init_port(struct tb_port *port)
{
int res;
int cap;
- struct tb_port *port = &sw->ports[port_nr];
- port->sw = sw;
- port->port = port_nr;
- port->remote = NULL;
+
res = tb_port_read(port, &port->config, TB_CFG_PORT, 0, 8);
if (res)
return res;
/* Port 0 is the switch itself and has no PHY. */
- if (port->config.type == TB_TYPE_PORT && port_nr != 0) {
+ if (port->config.type == TB_TYPE_PORT && port->port != 0) {
cap = tb_find_cap(port, TB_CFG_PORT, TB_CAP_PHY);
if (cap > 0)
@@ -202,7 +199,7 @@ static int tb_init_port(struct tb_switch *sw, u8 port_nr)
tb_port_WARN(port, "non switch port without a PHY\n");
}
- tb_dump_port(sw->tb, &port->config);
+ tb_dump_port(port->sw->tb, &port->config);
/* TODO: Read dual link port, DP port and more from EEPROM. */
return 0;
@@ -329,6 +326,7 @@ void tb_switch_free(struct tb_switch *sw)
tb_plug_events_active(sw, false);
kfree(sw->ports);
+ kfree(sw->drom);
kfree(sw);
}
@@ -381,18 +379,16 @@ struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route)
/* initialize ports */
sw->ports = kcalloc(sw->config.max_port_number + 1, sizeof(*sw->ports),
- GFP_KERNEL);
+ GFP_KERNEL);
if (!sw->ports)
goto err;
for (i = 0; i <= sw->config.max_port_number; i++) {
- if (tb_init_port(sw, i))
- goto err;
- /* TODO: check if port is disabled (EEPROM) */
+ /* minimum setup for tb_find_cap and tb_drom_read to work */
+ sw->ports[i].sw = sw;
+ sw->ports[i].port = i;
}
- /* TODO: I2C, IECS, EEPROM, link controller */
-
cap = tb_find_cap(&sw->ports[0], TB_CFG_SWITCH, TB_CAP_PLUG_EVENTS);
if (cap < 0) {
tb_sw_warn(sw, "cannot find TB_CAP_PLUG_EVENTS aborting\n");
@@ -400,10 +396,21 @@ struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route)
}
sw->cap_plug_events = cap;
- if (tb_drom_read_uid_only(sw, &sw->uid))
- tb_sw_warn(sw, "could not read uid from eeprom\n");
- else
- tb_sw_info(sw, "uid: %#llx\n", sw->uid);
+ /* read drom */
+ if (tb_drom_read(sw))
+ tb_sw_warn(sw, "tb_eeprom_read_rom failed, continuing\n");
+ tb_sw_info(sw, "uid: %#llx\n", sw->uid);
+
+ for (i = 0; i <= sw->config.max_port_number; i++) {
+ if (sw->ports[i].disabled) {
+ tb_port_info(&sw->ports[i], "disabled by eeprom\n");
+ continue;
+ }
+ if (tb_init_port(&sw->ports[i]))
+ goto err;
+ }
+
+ /* TODO: I2C, IECS, link controller */
if (tb_plug_events_active(sw, true))
goto err;
@@ -411,6 +418,7 @@ struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route)
return sw;
err:
kfree(sw->ports);
+ kfree(sw->drom);
kfree(sw);
return NULL;
}
diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c
index 1aa6dd7..d2c3fe3 100644
--- a/drivers/thunderbolt/tb.c
+++ b/drivers/thunderbolt/tb.c
@@ -38,6 +38,11 @@ static void tb_scan_port(struct tb_port *port)
return;
if (port->config.type != TB_TYPE_PORT)
return;
+ if (port->dual_link_port && port->link_nr)
+ return; /*
+ * Downstream switch is reachable through two ports.
+ * Only scan on the primary port (link_nr == 0).
+ */
if (tb_wait_for_port(port, false) <= 0)
return;
if (port->remote) {

View File

@ -0,0 +1,31 @@
From: Sachin Kamat <sachin.kamat@samsung.com>
Date: Fri, 20 Jun 2014 14:32:29 +0530
Subject: [16/31] thunderbolt: Fix build error in eeprom.c
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Origin: https://git.kernel.org/linus/2b35404ef7762af15ce138281c91b4cc0e2d0124
Fixes the below error:
drivers/thunderbolt/eeprom.c:407:2: error: implicit declaration of function kzalloc [-Werror=implicit-function-declaration]
drivers/thunderbolt/eeprom.c:444:2: error: implicit declaration of function kfree [-Werror=implicit-function-declaration]
Signed-off-by: Sachin Kamat <sachin.kamat@samsung.com>
Acked-by: Andreas Noever <andreas.noever@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
drivers/thunderbolt/eeprom.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/thunderbolt/eeprom.c b/drivers/thunderbolt/eeprom.c
index 0d5a80b..bc0449f 100644
--- a/drivers/thunderbolt/eeprom.c
+++ b/drivers/thunderbolt/eeprom.c
@@ -5,6 +5,7 @@
*/
#include <linux/crc32.h>
+#include <linux/slab.h>
#include "tb.h"
/**

View File

@ -0,0 +1,31 @@
From: Sachin Kamat <sachin.kamat@samsung.com>
Date: Fri, 20 Jun 2014 14:32:30 +0530
Subject: [17/31] thunderbolt: Fix build error in switch.c
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Origin: https://git.kernel.org/linus/10fefe56bba413fb0525207c65cf50cf2a5afaff
Fixes the below error:
drivers/thunderbolt/switch.c:347:2: error: implicit declaration of function kzalloc [-Werror=implicit-function-declaration]
drivers/thunderbolt/switch.c:381:2: error: implicit declaration of function kcalloc [-Werror=implicit-function-declaration]
Signed-off-by: Sachin Kamat <sachin.kamat@samsung.com>
Acked-by: Andreas Noever <andreas.noever@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
drivers/thunderbolt/switch.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c
index 0d50e7e..26e76e4 100644
--- a/drivers/thunderbolt/switch.c
+++ b/drivers/thunderbolt/switch.c
@@ -5,6 +5,7 @@
*/
#include <linux/delay.h>
+#include <linux/slab.h>
#include "tb.h"

View File

@ -0,0 +1,33 @@
From: Sachin Kamat <sachin.kamat@samsung.com>
Date: Fri, 20 Jun 2014 14:32:31 +0530
Subject: [18/31] thunderbolt: Use NULL instead of 0 in switch.c
Origin: https://git.kernel.org/linus/c9c2deef457c766a33c4862c9c198c20854d5fb6
The function returns a pointer. Hence return NULL instead of 0.
Signed-off-by: Sachin Kamat <sachin.kamat@samsung.com>
Acked-by: Andreas Noever <andreas.noever@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
drivers/thunderbolt/switch.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c
index 26e76e4..aeb9829 100644
--- a/drivers/thunderbolt/switch.c
+++ b/drivers/thunderbolt/switch.c
@@ -260,11 +260,11 @@ struct tb_switch *get_switch_at_route(struct tb_switch *sw, u64 route)
if (route == 0)
return sw;
if (next_port > sw->config.max_port_number)
- return 0;
+ return NULL;
if (tb_is_upstream_port(&sw->ports[next_port]))
- return 0;
+ return NULL;
if (!sw->ports[next_port].remote)
- return 0;
+ return NULL;
return get_switch_at_route(sw->ports[next_port].remote->sw,
route >> TB_ROUTE_SHIFT);
}

View File

@ -0,0 +1,34 @@
From: Sachin Kamat <sachin.kamat@samsung.com>
Date: Fri, 20 Jun 2014 14:32:32 +0530
Subject: [19/31] thunderbolt: Use NULL instead of 0 in ctl.c
Origin: https://git.kernel.org/linus/8db353bdd0975c220a62091f4fd28966478550fb
The function returns a pointer. Hence return NULL instead of 0.
Signed-off-by: Sachin Kamat <sachin.kamat@samsung.com>
Acked-by: Andreas Noever <andreas.noever@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
drivers/thunderbolt/ctl.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/thunderbolt/ctl.c b/drivers/thunderbolt/ctl.c
index d04fee4..4c6da92 100644
--- a/drivers/thunderbolt/ctl.c
+++ b/drivers/thunderbolt/ctl.c
@@ -305,13 +305,13 @@ static struct ctl_pkg *tb_ctl_pkg_alloc(struct tb_ctl *ctl)
{
struct ctl_pkg *pkg = kzalloc(sizeof(*pkg), GFP_KERNEL);
if (!pkg)
- return 0;
+ return NULL;
pkg->ctl = ctl;
pkg->buffer = dma_pool_alloc(ctl->frame_pool, GFP_KERNEL,
&pkg->frame.buffer_phy);
if (!pkg->buffer) {
kfree(pkg);
- return 0;
+ return NULL;
}
return pkg;
}

View File

@ -0,0 +1,27 @@
From: Sachin Kamat <sachin.kamat@samsung.com>
Date: Fri, 20 Jun 2014 14:32:33 +0530
Subject: [20/31] thunderbolt: Use NULL instead of 0 in nhi.c
Origin: https://git.kernel.org/linus/f19b72c6e8bb0bc257d09da6e324841d27a68528
'descriptors' is a pointer. Use NULL isntead of 0.
Signed-off-by: Sachin Kamat <sachin.kamat@samsung.com>
Acked-by: Andreas Noever <andreas.noever@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
drivers/thunderbolt/nhi.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c
index 346b41e..0fc137a 100644
--- a/drivers/thunderbolt/nhi.c
+++ b/drivers/thunderbolt/nhi.c
@@ -416,7 +416,7 @@ void ring_free(struct tb_ring *ring)
ring->size * sizeof(*ring->descriptors),
ring->descriptors, ring->descriptors_dma);
- ring->descriptors = 0;
+ ring->descriptors = NULL;
ring->descriptors_dma = 0;

View File

@ -0,0 +1,27 @@
From: Sachin Kamat <sachin.kamat@samsung.com>
Date: Fri, 20 Jun 2014 14:32:34 +0530
Subject: [21/31] thunderbolt: Staticize nhi_ids
Origin: https://git.kernel.org/linus/620863f71c46509e104729c75a199689e59cac47
'nhi_ids' is local to this file.
Signed-off-by: Sachin Kamat <sachin.kamat@samsung.com>
Acked-by: Andreas Noever <andreas.noever@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
drivers/thunderbolt/nhi.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c
index 0fc137a..2054fbf 100644
--- a/drivers/thunderbolt/nhi.c
+++ b/drivers/thunderbolt/nhi.c
@@ -632,7 +632,7 @@ static const struct dev_pm_ops nhi_pm_ops = {
.restore_noirq = nhi_resume_noirq,
};
-struct pci_device_id nhi_ids[] = {
+static struct pci_device_id nhi_ids[] = {
/*
* We have to specify class, the TB bridges use the same device and
* vendor (sub)id.

View File

@ -0,0 +1,29 @@
From: Arnd Bergmann <arnd@arndb.de>
Date: Fri, 20 Jun 2014 15:52:09 +0200
Subject: [22/31] thunderbolt: add PCI dependency
Origin: https://git.kernel.org/linus/0cb4e2be8bce5e176021a2e96b38e5d3727645a4
The thunderbolt drivers cannot be built if CONFIG_PCI is disabled,
better add an explicit Kconfig dependency.
The "default no" line is redundant and can be removed at the same
time.
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
Acked-by: Andreas Noever <andreas.noever@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
drivers/thunderbolt/Kconfig | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/thunderbolt/Kconfig b/drivers/thunderbolt/Kconfig
index 3a25529..5aab79b 100644
--- a/drivers/thunderbolt/Kconfig
+++ b/drivers/thunderbolt/Kconfig
@@ -1,6 +1,6 @@
menuconfig THUNDERBOLT
tristate "Thunderbolt support for Apple devices"
- default no
+ depends on PCI
help
Cactus Ridge Thunderbolt Controller driver
This driver is required if you want to hotplug Thunderbolt devices on

View File

@ -0,0 +1,29 @@
From: Arnd Bergmann <arnd@arndb.de>
Date: Fri, 20 Jun 2014 15:52:11 +0200
Subject: [23/31] thunderbolt: fix format string for size_t
Origin: https://git.kernel.org/linus/3543fb776d8e63934615bf7070ee5fa5a6a7382d
The result of "sizeof(struct tb_drom_entry_port)" is a size_t, which
is not necessarily the same as 'long', so we should use the appropriate
%z format string instead of %l.
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
Acked-by: Andreas Noever <andreas.noever@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
drivers/thunderbolt/eeprom.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/thunderbolt/eeprom.c b/drivers/thunderbolt/eeprom.c
index bc0449f..b133f3f 100644
--- a/drivers/thunderbolt/eeprom.c
+++ b/drivers/thunderbolt/eeprom.c
@@ -323,7 +323,7 @@ static int tb_drom_parse_entry(struct tb_switch *sw,
struct tb_drom_entry_port *entry = (void *) header;
if (header->len != sizeof(*entry)) {
tb_sw_warn(sw,
- "port entry has size %#x (expected %#lx)\n",
+ "port entry has size %#x (expected %#zx)\n",
header->len, sizeof(struct tb_drom_entry_port));
return -EIO;
}

View File

@ -0,0 +1,37 @@
From: Andreas Noever <andreas.noever@gmail.com>
Date: Fri, 20 Jun 2014 21:42:22 +0200
Subject: [24/31] thunderbolt: Add casts to prevent endianness warnings
Origin: https://git.kernel.org/linus/801dba53fef8bfc2f1424c33914a41810594bde2
Thunderbolt packets are big endian. Cast pkg->buffer to __be32* when
accessing the checksum.
Reported-by: kbuild test robot <fengguang.wu@intel.com>
Signed-off-by: Andreas Noever <andreas.noever@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
drivers/thunderbolt/ctl.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/thunderbolt/ctl.c b/drivers/thunderbolt/ctl.c
index 4c6da92..799634b 100644
--- a/drivers/thunderbolt/ctl.c
+++ b/drivers/thunderbolt/ctl.c
@@ -355,7 +355,7 @@ static int tb_ctl_tx(struct tb_ctl *ctl, void *data, size_t len,
pkg->frame.sof = type;
pkg->frame.eof = type;
cpu_to_be32_array(pkg->buffer, data, len / 4);
- *(u32 *) (pkg->buffer + len) = tb_crc(pkg->buffer, len);
+ *(__be32 *) (pkg->buffer + len) = tb_crc(pkg->buffer, len);
res = ring_tx(ctl->tx, &pkg->frame);
if (res) /* ring is stopped */
@@ -412,7 +412,7 @@ static void tb_ctl_rx_callback(struct tb_ring *ring, struct ring_frame *frame,
}
frame->size -= 4; /* remove checksum */
- if (*(u32 *) (pkg->buffer + frame->size)
+ if (*(__be32 *) (pkg->buffer + frame->size)
!= tb_crc(pkg->buffer, frame->size)) {
tb_ctl_err(pkg->ctl,
"RX: checksum mismatch, dropping packet\n");

View File

@ -0,0 +1,27 @@
From: Andreas Noever <andreas.noever@gmail.com>
Date: Fri, 20 Jun 2014 21:42:23 +0200
Subject: [25/31] thunderbolt: Fix header declaration of tb_find_cap
Origin: https://git.kernel.org/linus/7f2d5f7bc529114c2e67520427be6ebac694e78f
tb_find_cap in cap.c takes an enum tb_cap and not an u32. Fix the
declaration in tb.h.
Signed-off-by: Andreas Noever <andreas.noever@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
drivers/thunderbolt/tb.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h
index 18ade5e..8b0d7cf 100644
--- a/drivers/thunderbolt/tb.h
+++ b/drivers/thunderbolt/tb.h
@@ -233,7 +233,7 @@ int tb_wait_for_port(struct tb_port *port, bool wait_if_unplugged);
int tb_port_add_nfc_credits(struct tb_port *port, int credits);
int tb_port_clear_counter(struct tb_port *port, int counter);
-int tb_find_cap(struct tb_port *port, enum tb_cfg_space space, u32 value);
+int tb_find_cap(struct tb_port *port, enum tb_cfg_space space, enum tb_cap cap);
struct tb_path *tb_path_alloc(struct tb *tb, int num_hops);
void tb_path_free(struct tb_path *path);

View File

@ -0,0 +1,29 @@
From: Andreas Noever <andreas.noever@gmail.com>
Date: Fri, 20 Jun 2014 21:42:24 +0200
Subject: [26/31] thunderbolt: Make enum tb_drom_entry_type unsigned
Origin: https://git.kernel.org/linus/e7120778a4518a1c8f188ef9865058f7f5a36919
Force enum tb_drom_entry_type to unsigned to fix the following error:
drivers/thunderbolt/eeprom.c:202:39: error: dubious one-bit signed bitfield
Signed-off-by: Andreas Noever <andreas.noever@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
drivers/thunderbolt/eeprom.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/thunderbolt/eeprom.c b/drivers/thunderbolt/eeprom.c
index b133f3f..71f719b 100644
--- a/drivers/thunderbolt/eeprom.c
+++ b/drivers/thunderbolt/eeprom.c
@@ -191,7 +191,8 @@ struct tb_drom_header {
} __packed;
enum tb_drom_entry_type {
- TB_DROM_ENTRY_GENERIC,
+ /* force unsigned to prevent "one-bit signed bitfield" warning */
+ TB_DROM_ENTRY_GENERIC = 0U,
TB_DROM_ENTRY_PORT,
};

View File

@ -0,0 +1,26 @@
From: Andreas Noever <andreas.noever@gmail.com>
Date: Fri, 20 Jun 2014 21:42:25 +0200
Subject: [27/31] thunderbolt: Make tb_eeprom_get_drom_offset static
Origin: https://git.kernel.org/linus/e0f550141be3d4c401ae15a0cd1877d4d9665f16
tb_eeprom_get_drom_offset is local to this file.
Signed-off-by: Andreas Noever <andreas.noever@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
drivers/thunderbolt/eeprom.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/thunderbolt/eeprom.c b/drivers/thunderbolt/eeprom.c
index 71f719b..0dde34e 100644
--- a/drivers/thunderbolt/eeprom.c
+++ b/drivers/thunderbolt/eeprom.c
@@ -233,7 +233,7 @@ struct tb_drom_entry_port {
/**
* tb_eeprom_get_drom_offset - get drom offset within eeprom
*/
-int tb_eeprom_get_drom_offset(struct tb_switch *sw, u16 *offset)
+static int tb_eeprom_get_drom_offset(struct tb_switch *sw, u16 *offset)
{
struct tb_cap_plug_events cap;
int res;

View File

@ -0,0 +1,26 @@
From: Andreas Noever <andreas.noever@gmail.com>
Date: Sat, 21 Jun 2014 12:15:44 +0200
Subject: [28/31] thunderbolt: select CRC32 in Kconfig
Origin: https://git.kernel.org/linus/f34323b64ac6bb475e158f93c08ee677ecffe8a3
We use __crc32c_le in ctl.c. So make sure that the dependency is there.
Reported-by: kbuild test robot <fengguang.wu@intel.com>
Signed-off-by: Andreas Noever <andreas.noever@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
drivers/thunderbolt/Kconfig | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/thunderbolt/Kconfig b/drivers/thunderbolt/Kconfig
index 5aab79b..c121acc 100644
--- a/drivers/thunderbolt/Kconfig
+++ b/drivers/thunderbolt/Kconfig
@@ -1,6 +1,7 @@
menuconfig THUNDERBOLT
tristate "Thunderbolt support for Apple devices"
depends on PCI
+ select CRC32
help
Cactus Ridge Thunderbolt Controller driver
This driver is required if you want to hotplug Thunderbolt devices on

View File

@ -0,0 +1,49 @@
From: Himangi Saraogi <himangi774@gmail.com>
Date: Sun, 6 Jul 2014 21:43:42 +0530
Subject: [29/31] thunderbolt: Correct the size argument to devm_kzalloc
Origin: https://git.kernel.org/linus/fc51768ba24077c8148067036e1555a8a978bb99
nhi->rx_rings does not have type as struct tb_ring *, as it is a
double pointer so the elements of the array should have pointer type,
not structure type.
The Coccinelle semantic patch that makes this change is as follows:
// <smpl>
@disable sizeof_type_expr@
type T;
T **x;
@@
x =
<+...sizeof(
- T
+ *x
)...+>
// </smpl>
Signed-off-by: Himangi Saraogi <himangi774@gmail.com>
Acked-by: Julia Lawall <julia.lawall@lip6.fr>
Cc: Andreas Noever <andreas.noever@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
drivers/thunderbolt/nhi.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c
index 2054fbf..ce72f31 100644
--- a/drivers/thunderbolt/nhi.c
+++ b/drivers/thunderbolt/nhi.c
@@ -570,10 +570,10 @@ static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id)
INIT_WORK(&nhi->interrupt_work, nhi_interrupt_work);
nhi->tx_rings = devm_kzalloc(&pdev->dev,
- nhi->hop_count * sizeof(struct tb_ring),
+ nhi->hop_count * sizeof(*nhi->tx_rings),
GFP_KERNEL);
nhi->rx_rings = devm_kzalloc(&pdev->dev,
- nhi->hop_count * sizeof(struct tb_ring),
+ nhi->hop_count * sizeof(*nhi->rx_rings),
GFP_KERNEL);
if (!nhi->tx_rings || !nhi->rx_rings)
return -ENOMEM;

View File

@ -0,0 +1,38 @@
From: Himangi Saraogi <himangi774@gmail.com>
Date: Sat, 12 Jul 2014 01:12:43 +0530
Subject: [30/31] thunderbolt: Use kcalloc
Origin: https://git.kernel.org/linus/2a211f320ee3d86835b40efd2948642482d3c933
The advantage of kcalloc is, that will prevent integer overflows
which could result from the multiplication of number of elements
and size and it is also a bit nicer to read.
Signed-off-by: Himangi Saraogi <himangi774@gmail.com>
Acked-by: Julia Lawall <julia.lawall@lip6.fr>
Acked-by: Andreas Noever <andreas.noever@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
drivers/thunderbolt/nhi.c | 10 ++++------
1 file changed, 4 insertions(+), 6 deletions(-)
diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c
index ce72f31..c68fe12 100644
--- a/drivers/thunderbolt/nhi.c
+++ b/drivers/thunderbolt/nhi.c
@@ -569,12 +569,10 @@ static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id)
nhi->hop_count);
INIT_WORK(&nhi->interrupt_work, nhi_interrupt_work);
- nhi->tx_rings = devm_kzalloc(&pdev->dev,
- nhi->hop_count * sizeof(*nhi->tx_rings),
- GFP_KERNEL);
- nhi->rx_rings = devm_kzalloc(&pdev->dev,
- nhi->hop_count * sizeof(*nhi->rx_rings),
- GFP_KERNEL);
+ nhi->tx_rings = devm_kcalloc(&pdev->dev, nhi->hop_count,
+ sizeof(*nhi->tx_rings), GFP_KERNEL);
+ nhi->rx_rings = devm_kcalloc(&pdev->dev, nhi->hop_count,
+ sizeof(*nhi->rx_rings), GFP_KERNEL);
if (!nhi->tx_rings || !nhi->rx_rings)
return -ENOMEM;

View File

@ -0,0 +1,56 @@
From: Andreas Noever <andreas.noever@gmail.com>
Date: Tue, 26 Aug 2014 17:42:21 +0200
Subject: [31/31] thunderbolt: Clear hops before overwriting
Origin: https://git.kernel.org/linus/72ad366f687d45f30a82d8b6e70ce757b21b5aab
Zero hops in tb_path_activate before writing a new path.
This fixes the following scenario:
- Boot with a coldplugged device
- Unplug device
- Plug device back in
- PCI hotplug fails
The hotplug operation fails because our new path matches the (now
defunct) path which was setup by the firmware for the coldplugged
device. By writing zeros before writing our path configuration we can
force thunderbolt to retrain the path.
Signed-off-by: Andreas Noever <andreas.noever@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
drivers/thunderbolt/path.c | 21 ++++++++++++++++++++-
1 file changed, 20 insertions(+), 1 deletion(-)
diff --git a/drivers/thunderbolt/path.c b/drivers/thunderbolt/path.c
index 8fcf8a7..9562cd0 100644
--- a/drivers/thunderbolt/path.c
+++ b/drivers/thunderbolt/path.c
@@ -150,7 +150,26 @@ int tb_path_activate(struct tb_path *path)
/* Activate hops. */
for (i = path->path_length - 1; i >= 0; i--) {
- struct tb_regs_hop hop;
+ struct tb_regs_hop hop = { 0 };
+
+ /*
+ * We do (currently) not tear down paths setup by the firmeware.
+ * If a firmware device is unplugged and plugged in again then
+ * it can happen that we reuse some of the hops from the (now
+ * defunct) firmeware path. This causes the hotplug operation to
+ * fail (the pci device does not show up). Clearing the hop
+ * before overwriting it fixes the problem.
+ *
+ * Should be removed once we discover and tear down firmeware
+ * paths.
+ */
+ res = tb_port_write(path->hops[i].in_port, &hop, TB_CFG_HOPS,
+ 2 * path->hops[i].in_hop_index, 2);
+ if (res) {
+ __tb_path_deactivate_hops(path, i);
+ __tb_path_deallocate_nfc(path, 0);
+ goto err;
+ }
/* dword 0 */
hop.next_hop = path->hops[i].next_hop_index;

31
debian/patches/series vendored
View File

@ -418,3 +418,34 @@ bugfix/all/net-sctp-fix-skb_over_panic-when-receiving-malformed.patch
bugfix/all/net-sctp-fix-panic-on-duplicate-ASCONF-chunks.patch
bugfix/all/net-sctp-fix-remote-memory-pressure-from-excessive-q.patch
bugfix/all/mnt-Prevent-pivot_root-from-creating-a-loop-in-the-m.patch
features/x86/apple-tb/0001-thunderbolt-Add-initial-cactus-ridge-NHI-support.patch
features/x86/apple-tb/0002-thunderbolt-Add-control-channel-interface.patch
features/x86/apple-tb/0003-thunderbolt-Setup-control-channel.patch
features/x86/apple-tb/0004-thunderbolt-Add-tb_regs.h.patch
features/x86/apple-tb/0005-thunderbolt-Initialize-root-switch-and-ports.patch
features/x86/apple-tb/0006-thunderbolt-Add-thunderbolt-capability-handling.patch
features/x86/apple-tb/0007-thunderbolt-Enable-plug-events.patch
features/x86/apple-tb/0008-thunderbolt-Scan-for-downstream-switches.patch
features/x86/apple-tb/0009-thunderbolt-Handle-hotplug-events.patch
features/x86/apple-tb/0010-thunderbolt-Add-path-setup-code.patch
features/x86/apple-tb/0011-thunderbolt-Add-support-for-simple-pci-tunnels.patch
features/x86/apple-tb/0012-thunderbolt-Read-switch-uid-from-EEPROM.patch
features/x86/apple-tb/0013-thunderbolt-Add-suspend-hibernate-support.patch
features/x86/apple-tb/0014-thunderbolt-Read-port-configuration-from-eeprom.patch
features/x86/apple-tb/0015-thunderbolt-Fix-nontrivial-endpoint-devices.patch
features/x86/apple-tb/0016-thunderbolt-Fix-build-error-in-eeprom.c.patch
features/x86/apple-tb/0017-thunderbolt-Fix-build-error-in-switch.c.patch
features/x86/apple-tb/0018-thunderbolt-Use-NULL-instead-of-0-in-switch.c.patch
features/x86/apple-tb/0019-thunderbolt-Use-NULL-instead-of-0-in-ctl.c.patch
features/x86/apple-tb/0020-thunderbolt-Use-NULL-instead-of-0-in-nhi.c.patch
features/x86/apple-tb/0021-thunderbolt-Staticize-nhi_ids.patch
features/x86/apple-tb/0022-thunderbolt-add-PCI-dependency.patch
features/x86/apple-tb/0023-thunderbolt-fix-format-string-for-size_t.patch
features/x86/apple-tb/0024-thunderbolt-Add-casts-to-prevent-endianness-warnings.patch
features/x86/apple-tb/0025-thunderbolt-Fix-header-declaration-of-tb_find_cap.patch
features/x86/apple-tb/0026-thunderbolt-Make-enum-tb_drom_entry_type-unsigned.patch
features/x86/apple-tb/0027-thunderbolt-Make-tb_eeprom_get_drom_offset-static.patch
features/x86/apple-tb/0028-thunderbolt-select-CRC32-in-Kconfig.patch
features/x86/apple-tb/0029-thunderbolt-Correct-the-size-argument-to-devm_kzallo.patch
features/x86/apple-tb/0030-thunderbolt-Use-kcalloc.patch
features/x86/apple-tb/0031-thunderbolt-Clear-hops-before-overwriting.patch