[Feature]add MT2731_MP2_MR2_SVN388 baseline version
Change-Id: Ief04314834b31e27effab435d3ca8ba33b499059
diff --git a/src/kernel/linux/v4.14/drivers/thunderbolt/Kconfig b/src/kernel/linux/v4.14/drivers/thunderbolt/Kconfig
new file mode 100644
index 0000000..f4869c3
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/thunderbolt/Kconfig
@@ -0,0 +1,16 @@
+menuconfig THUNDERBOLT
+ tristate "Thunderbolt support"
+ depends on PCI
+ depends on X86 || COMPILE_TEST
+ select APPLE_PROPERTIES if EFI_STUB && X86
+ select CRC32
+ select CRYPTO
+ select CRYPTO_HASH
+ select NVMEM
+ help
+ Thunderbolt Controller driver. This driver is required if you
+ want to hotplug Thunderbolt devices on Apple hardware or on PCs
+ with Intel Falcon Ridge or newer.
+
+ To compile this driver a module, choose M here. The module will be
+ called thunderbolt.
diff --git a/src/kernel/linux/v4.14/drivers/thunderbolt/Makefile b/src/kernel/linux/v4.14/drivers/thunderbolt/Makefile
new file mode 100644
index 0000000..4900feb
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/thunderbolt/Makefile
@@ -0,0 +1,3 @@
+obj-${CONFIG_THUNDERBOLT} := thunderbolt.o
+thunderbolt-objs := nhi.o ctl.o tb.o switch.o cap.o path.o tunnel_pci.o eeprom.o
+thunderbolt-objs += domain.o dma_port.o icm.o
diff --git a/src/kernel/linux/v4.14/drivers/thunderbolt/cap.c b/src/kernel/linux/v4.14/drivers/thunderbolt/cap.c
new file mode 100644
index 0000000..c2277b8
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/thunderbolt/cap.c
@@ -0,0 +1,130 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * 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"
+
+#define CAP_OFFSET_MAX 0xff
+#define VSE_CAP_OFFSET_MAX 0xffff
+
+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;
+
+/**
+ * tb_port_find_cap() - Find port capability
+ * @port: Port to find the capability for
+ * @cap: Capability to look
+ *
+ * Returns offset to start of capability or %-ENOENT if no such
+ * capability was found. Negative errno is returned if there was an
+ * error.
+ */
+int tb_port_find_cap(struct tb_port *port, enum tb_port_cap cap)
+{
+ u32 offset;
+
+ /*
+ * DP out adapters claim to implement TMU capability but in
+ * reality they do not so we hard code the adapter specific
+ * capability offset here.
+ */
+ if (port->config.type == TB_TYPE_DP_HDMI_OUT)
+ offset = 0x39;
+ else
+ offset = 0x1;
+
+ do {
+ struct tb_cap_any header;
+ int ret;
+
+ ret = tb_port_read(port, &header, TB_CFG_PORT, offset, 1);
+ if (ret)
+ return ret;
+
+ if (header.basic.cap == cap)
+ return offset;
+
+ offset = header.basic.next;
+ } while (offset);
+
+ return -ENOENT;
+}
+
+static int tb_switch_find_cap(struct tb_switch *sw, enum tb_switch_cap cap)
+{
+ int offset = sw->config.first_cap_offset;
+
+ while (offset > 0 && offset < CAP_OFFSET_MAX) {
+ struct tb_cap_any header;
+ int ret;
+
+ ret = tb_sw_read(sw, &header, TB_CFG_SWITCH, offset, 1);
+ if (ret)
+ return ret;
+
+ if (header.basic.cap == cap)
+ return offset;
+
+ offset = header.basic.next;
+ }
+
+ return -ENOENT;
+}
+
+/**
+ * tb_switch_find_vse_cap() - Find switch vendor specific capability
+ * @sw: Switch to find the capability for
+ * @vsec: Vendor specific capability to look
+ *
+ * Functions enumerates vendor specific capabilities (VSEC) of a switch
+ * and returns offset when capability matching @vsec is found. If no
+ * such capability is found returns %-ENOENT. In case of error returns
+ * negative errno.
+ */
+int tb_switch_find_vse_cap(struct tb_switch *sw, enum tb_switch_vse_cap vsec)
+{
+ struct tb_cap_any header;
+ int offset;
+
+ offset = tb_switch_find_cap(sw, TB_SWITCH_CAP_VSE);
+ if (offset < 0)
+ return offset;
+
+ while (offset > 0 && offset < VSE_CAP_OFFSET_MAX) {
+ int ret;
+
+ ret = tb_sw_read(sw, &header, TB_CFG_SWITCH, offset, 2);
+ if (ret)
+ return ret;
+
+ /*
+ * Extended vendor specific capabilities come in two
+ * flavors: short and long. The latter is used when
+ * offset is over 0xff.
+ */
+ if (offset >= CAP_OFFSET_MAX) {
+ if (header.extended_long.vsec_id == vsec)
+ return offset;
+ offset = header.extended_long.next;
+ } else {
+ if (header.extended_short.vsec_id == vsec)
+ return offset;
+ if (!header.extended_short.length)
+ return -ENOENT;
+ offset = header.extended_short.next;
+ }
+ }
+
+ return -ENOENT;
+}
diff --git a/src/kernel/linux/v4.14/drivers/thunderbolt/ctl.c b/src/kernel/linux/v4.14/drivers/thunderbolt/ctl.c
new file mode 100644
index 0000000..423ae23
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/thunderbolt/ctl.c
@@ -0,0 +1,1017 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Thunderbolt Cactus Ridge driver - control channel and configuration commands
+ *
+ * Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com>
+ */
+
+#include <linux/crc32.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <linux/dmapool.h>
+#include <linux/workqueue.h>
+
+#include "ctl.h"
+
+
+#define TB_CTL_RX_PKG_COUNT 10
+#define TB_CTL_RETRIES 4
+
+/**
+ * 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];
+ struct mutex request_queue_lock;
+ struct list_head request_queue;
+ bool running;
+
+ event_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)
+
+#define tb_ctl_dbg(ctl, format, arg...) \
+ dev_dbg(&(ctl)->nhi->pdev->dev, format, ## arg)
+
+static DECLARE_WAIT_QUEUE_HEAD(tb_cfg_request_cancel_queue);
+/* Serializes access to request kref_get/put */
+static DEFINE_MUTEX(tb_cfg_request_lock);
+
+/**
+ * tb_cfg_request_alloc() - Allocates a new config request
+ *
+ * This is refcounted object so when you are done with this, call
+ * tb_cfg_request_put() to it.
+ */
+struct tb_cfg_request *tb_cfg_request_alloc(void)
+{
+ struct tb_cfg_request *req;
+
+ req = kzalloc(sizeof(*req), GFP_KERNEL);
+ if (!req)
+ return NULL;
+
+ kref_init(&req->kref);
+
+ return req;
+}
+
+/**
+ * tb_cfg_request_get() - Increase refcount of a request
+ * @req: Request whose refcount is increased
+ */
+void tb_cfg_request_get(struct tb_cfg_request *req)
+{
+ mutex_lock(&tb_cfg_request_lock);
+ kref_get(&req->kref);
+ mutex_unlock(&tb_cfg_request_lock);
+}
+
+static void tb_cfg_request_destroy(struct kref *kref)
+{
+ struct tb_cfg_request *req = container_of(kref, typeof(*req), kref);
+
+ kfree(req);
+}
+
+/**
+ * tb_cfg_request_put() - Decrease refcount and possibly release the request
+ * @req: Request whose refcount is decreased
+ *
+ * Call this function when you are done with the request. When refcount
+ * goes to %0 the object is released.
+ */
+void tb_cfg_request_put(struct tb_cfg_request *req)
+{
+ mutex_lock(&tb_cfg_request_lock);
+ kref_put(&req->kref, tb_cfg_request_destroy);
+ mutex_unlock(&tb_cfg_request_lock);
+}
+
+static int tb_cfg_request_enqueue(struct tb_ctl *ctl,
+ struct tb_cfg_request *req)
+{
+ WARN_ON(test_bit(TB_CFG_REQUEST_ACTIVE, &req->flags));
+ WARN_ON(req->ctl);
+
+ mutex_lock(&ctl->request_queue_lock);
+ if (!ctl->running) {
+ mutex_unlock(&ctl->request_queue_lock);
+ return -ENOTCONN;
+ }
+ req->ctl = ctl;
+ list_add_tail(&req->list, &ctl->request_queue);
+ set_bit(TB_CFG_REQUEST_ACTIVE, &req->flags);
+ mutex_unlock(&ctl->request_queue_lock);
+ return 0;
+}
+
+static void tb_cfg_request_dequeue(struct tb_cfg_request *req)
+{
+ struct tb_ctl *ctl = req->ctl;
+
+ mutex_lock(&ctl->request_queue_lock);
+ list_del(&req->list);
+ clear_bit(TB_CFG_REQUEST_ACTIVE, &req->flags);
+ if (test_bit(TB_CFG_REQUEST_CANCELED, &req->flags))
+ wake_up(&tb_cfg_request_cancel_queue);
+ mutex_unlock(&ctl->request_queue_lock);
+}
+
+static bool tb_cfg_request_is_active(struct tb_cfg_request *req)
+{
+ return test_bit(TB_CFG_REQUEST_ACTIVE, &req->flags);
+}
+
+static struct tb_cfg_request *
+tb_cfg_request_find(struct tb_ctl *ctl, struct ctl_pkg *pkg)
+{
+ struct tb_cfg_request *req;
+ bool found = false;
+
+ mutex_lock(&pkg->ctl->request_queue_lock);
+ list_for_each_entry(req, &pkg->ctl->request_queue, list) {
+ tb_cfg_request_get(req);
+ if (req->match(req, pkg)) {
+ found = true;
+ break;
+ }
+ tb_cfg_request_put(req);
+ }
+ mutex_unlock(&pkg->ctl->request_queue_lock);
+
+ return found ? req : NULL;
+}
+
+/* utility functions */
+
+
+static int check_header(const 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 != tb_cfg_get_route(header),
+ "wrong route (expected %llx, got %llx)",
+ route, tb_cfg_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;
+ /*
+ * 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(const struct ctl_pkg *response)
+{
+ struct cfg_error_pkg *pkg = response->buffer;
+ struct tb_cfg_result res = { 0 };
+ res.response_route = tb_cfg_get_route(&pkg->header);
+ res.response_port = 0;
+ res.err = check_header(response, sizeof(*pkg), TB_CFG_PKG_ERROR,
+ tb_cfg_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(const 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 = tb_cfg_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 or 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, const 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(const 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 NULL;
+ pkg->ctl = ctl;
+ pkg->buffer = dma_pool_alloc(ctl->frame_pool, GFP_KERNEL,
+ &pkg->frame.buffer_phy);
+ if (!pkg->buffer) {
+ kfree(pkg);
+ return NULL;
+ }
+ 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, const 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);
+ *(__be32 *) (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_event() - acknowledge a plug event, invoke ctl->callback
+ */
+static void tb_ctl_handle_event(struct tb_ctl *ctl, enum tb_cfg_pkg_type type,
+ struct ctl_pkg *pkg, size_t size)
+{
+ ctl->callback(ctl->callback_data, type, pkg->buffer, size);
+}
+
+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 int tb_async_error(const struct ctl_pkg *pkg)
+{
+ const struct cfg_error_pkg *error = (const struct cfg_error_pkg *)pkg;
+
+ if (pkg->frame.eof != TB_CFG_PKG_ERROR)
+ return false;
+
+ switch (error->error) {
+ case TB_CFG_ERROR_LINK_ERROR:
+ case TB_CFG_ERROR_HEC_ERROR_DETECTED:
+ case TB_CFG_ERROR_FLOW_CONTROL_ERROR:
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+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);
+ struct tb_cfg_request *req;
+ __be32 crc32;
+
+ 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 */
+ crc32 = tb_crc(pkg->buffer, frame->size);
+ be32_to_cpu_array(pkg->buffer, pkg->buffer, frame->size / 4);
+
+ switch (frame->eof) {
+ case TB_CFG_PKG_READ:
+ case TB_CFG_PKG_WRITE:
+ case TB_CFG_PKG_ERROR:
+ case TB_CFG_PKG_OVERRIDE:
+ case TB_CFG_PKG_RESET:
+ if (*(__be32 *)(pkg->buffer + frame->size) != crc32) {
+ tb_ctl_err(pkg->ctl,
+ "RX: checksum mismatch, dropping packet\n");
+ goto rx;
+ }
+ if (tb_async_error(pkg)) {
+ tb_ctl_handle_event(pkg->ctl, frame->eof,
+ pkg, frame->size);
+ goto rx;
+ }
+ break;
+
+ case TB_CFG_PKG_EVENT:
+ if (*(__be32 *)(pkg->buffer + frame->size) != crc32) {
+ tb_ctl_err(pkg->ctl,
+ "RX: checksum mismatch, dropping packet\n");
+ goto rx;
+ }
+ /* Fall through */
+ case TB_CFG_PKG_ICM_EVENT:
+ tb_ctl_handle_event(pkg->ctl, frame->eof, pkg, frame->size);
+ goto rx;
+
+ default:
+ break;
+ }
+
+ /*
+ * The received packet will be processed only if there is an
+ * active request and that the packet is what is expected. This
+ * prevents packets such as replies coming after timeout has
+ * triggered from messing with the active requests.
+ */
+ req = tb_cfg_request_find(pkg->ctl, pkg);
+ if (req) {
+ if (req->copy(req, pkg))
+ schedule_work(&req->work);
+ tb_cfg_request_put(req);
+ }
+
+rx:
+ tb_ctl_rx_submit(pkg);
+}
+
+static void tb_cfg_request_work(struct work_struct *work)
+{
+ struct tb_cfg_request *req = container_of(work, typeof(*req), work);
+
+ if (!test_bit(TB_CFG_REQUEST_CANCELED, &req->flags))
+ req->callback(req->callback_data);
+
+ tb_cfg_request_dequeue(req);
+ tb_cfg_request_put(req);
+}
+
+/**
+ * tb_cfg_request() - Start control request not waiting for it to complete
+ * @ctl: Control channel to use
+ * @req: Request to start
+ * @callback: Callback called when the request is completed
+ * @callback_data: Data to be passed to @callback
+ *
+ * This queues @req on the given control channel without waiting for it
+ * to complete. When the request completes @callback is called.
+ */
+int tb_cfg_request(struct tb_ctl *ctl, struct tb_cfg_request *req,
+ void (*callback)(void *), void *callback_data)
+{
+ int ret;
+
+ req->flags = 0;
+ req->callback = callback;
+ req->callback_data = callback_data;
+ INIT_WORK(&req->work, tb_cfg_request_work);
+ INIT_LIST_HEAD(&req->list);
+
+ tb_cfg_request_get(req);
+ ret = tb_cfg_request_enqueue(ctl, req);
+ if (ret)
+ goto err_put;
+
+ ret = tb_ctl_tx(ctl, req->request, req->request_size,
+ req->request_type);
+ if (ret)
+ goto err_dequeue;
+
+ if (!req->response)
+ schedule_work(&req->work);
+
+ return 0;
+
+err_dequeue:
+ tb_cfg_request_dequeue(req);
+err_put:
+ tb_cfg_request_put(req);
+
+ return ret;
+}
+
+/**
+ * tb_cfg_request_cancel() - Cancel a control request
+ * @req: Request to cancel
+ * @err: Error to assign to the request
+ *
+ * This function can be used to cancel ongoing request. It will wait
+ * until the request is not active anymore.
+ */
+void tb_cfg_request_cancel(struct tb_cfg_request *req, int err)
+{
+ set_bit(TB_CFG_REQUEST_CANCELED, &req->flags);
+ schedule_work(&req->work);
+ wait_event(tb_cfg_request_cancel_queue, !tb_cfg_request_is_active(req));
+ req->result.err = err;
+}
+
+static void tb_cfg_request_complete(void *data)
+{
+ complete(data);
+}
+
+/**
+ * tb_cfg_request_sync() - Start control request and wait until it completes
+ * @ctl: Control channel to use
+ * @req: Request to start
+ * @timeout_msec: Timeout how long to wait @req to complete
+ *
+ * Starts a control request and waits until it completes. If timeout
+ * triggers the request is canceled before function returns. Note the
+ * caller needs to make sure only one message for given switch is active
+ * at a time.
+ */
+struct tb_cfg_result tb_cfg_request_sync(struct tb_ctl *ctl,
+ struct tb_cfg_request *req,
+ int timeout_msec)
+{
+ unsigned long timeout = msecs_to_jiffies(timeout_msec);
+ struct tb_cfg_result res = { 0 };
+ DECLARE_COMPLETION_ONSTACK(done);
+ int ret;
+
+ ret = tb_cfg_request(ctl, req, tb_cfg_request_complete, &done);
+ if (ret) {
+ res.err = ret;
+ return res;
+ }
+
+ if (!wait_for_completion_timeout(&done, timeout))
+ tb_cfg_request_cancel(req, -ETIMEDOUT);
+
+ flush_work(&req->work);
+
+ return req->result;
+}
+
+/* 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, event_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;
+
+ mutex_init(&ctl->request_queue_lock);
+ INIT_LIST_HEAD(&ctl->request_queue);
+ 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, RING_FLAG_NO_SUSPEND);
+ if (!ctl->tx)
+ goto err;
+
+ ctl->rx = ring_alloc_rx(nhi, 0, 10, RING_FLAG_NO_SUSPEND);
+ 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)
+ return;
+
+ 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]);
+
+ ctl->running = true;
+}
+
+/**
+ * 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)
+{
+ mutex_lock(&ctl->request_queue_lock);
+ ctl->running = false;
+ mutex_unlock(&ctl->request_queue_lock);
+
+ ring_stop(ctl->rx);
+ ring_stop(ctl->tx);
+
+ if (!list_empty(&ctl->request_queue))
+ tb_ctl_WARN(ctl, "dangling request in request_queue\n");
+ INIT_LIST_HEAD(&ctl->request_queue);
+ 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 = tb_cfg_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);
+}
+
+static bool tb_cfg_match(const struct tb_cfg_request *req,
+ const struct ctl_pkg *pkg)
+{
+ u64 route = tb_cfg_get_route(pkg->buffer) & ~BIT_ULL(63);
+
+ if (pkg->frame.eof == TB_CFG_PKG_ERROR)
+ return true;
+
+ if (pkg->frame.eof != req->response_type)
+ return false;
+ if (route != tb_cfg_get_route(req->request))
+ return false;
+ if (pkg->frame.size != req->response_size)
+ return false;
+
+ if (pkg->frame.eof == TB_CFG_PKG_READ ||
+ pkg->frame.eof == TB_CFG_PKG_WRITE) {
+ const struct cfg_read_pkg *req_hdr = req->request;
+ const struct cfg_read_pkg *res_hdr = pkg->buffer;
+
+ if (req_hdr->addr.seq != res_hdr->addr.seq)
+ return false;
+ }
+
+ return true;
+}
+
+static bool tb_cfg_copy(struct tb_cfg_request *req, const struct ctl_pkg *pkg)
+{
+ struct tb_cfg_result res;
+
+ /* Now make sure it is in expected format */
+ res = parse_header(pkg, req->response_size, req->response_type,
+ tb_cfg_get_route(req->request));
+ if (!res.err)
+ memcpy(req->response, pkg->buffer, req->response_size);
+
+ req->result = res;
+
+ /* Always complete when first response is received */
+ return true;
+}
+
+/**
+ * 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)
+{
+ struct cfg_reset_pkg request = { .header = tb_cfg_make_header(route) };
+ struct tb_cfg_result res = { 0 };
+ struct tb_cfg_header reply;
+ struct tb_cfg_request *req;
+
+ req = tb_cfg_request_alloc();
+ if (!req) {
+ res.err = -ENOMEM;
+ return res;
+ }
+
+ req->match = tb_cfg_match;
+ req->copy = tb_cfg_copy;
+ req->request = &request;
+ req->request_size = sizeof(request);
+ req->request_type = TB_CFG_PKG_RESET;
+ req->response = &reply;
+ req->response_size = sizeof(reply);
+ req->response_type = TB_CFG_PKG_RESET;
+
+ res = tb_cfg_request_sync(ctl, req, timeout_msec);
+
+ tb_cfg_request_put(req);
+
+ return res;
+}
+
+/**
+ * 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 = tb_cfg_make_header(route),
+ .addr = {
+ .port = port,
+ .space = space,
+ .offset = offset,
+ .length = length,
+ },
+ };
+ struct cfg_write_pkg reply;
+ int retries = 0;
+
+ while (retries < TB_CTL_RETRIES) {
+ struct tb_cfg_request *req;
+
+ req = tb_cfg_request_alloc();
+ if (!req) {
+ res.err = -ENOMEM;
+ return res;
+ }
+
+ request.addr.seq = retries++;
+
+ req->match = tb_cfg_match;
+ req->copy = tb_cfg_copy;
+ req->request = &request;
+ req->request_size = sizeof(request);
+ req->request_type = TB_CFG_PKG_READ;
+ req->response = &reply;
+ req->response_size = 12 + 4 * length;
+ req->response_type = TB_CFG_PKG_READ;
+
+ res = tb_cfg_request_sync(ctl, req, timeout_msec);
+
+ tb_cfg_request_put(req);
+
+ if (res.err != -ETIMEDOUT)
+ break;
+
+ /* Wait a bit (arbitrary time) until we send a retry */
+ usleep_range(10, 100);
+ }
+
+ 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, const 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 = tb_cfg_make_header(route),
+ .addr = {
+ .port = port,
+ .space = space,
+ .offset = offset,
+ .length = length,
+ },
+ };
+ struct cfg_read_pkg reply;
+ int retries = 0;
+
+ memcpy(&request.data, buffer, length * 4);
+
+ while (retries < TB_CTL_RETRIES) {
+ struct tb_cfg_request *req;
+
+ req = tb_cfg_request_alloc();
+ if (!req) {
+ res.err = -ENOMEM;
+ return res;
+ }
+
+ request.addr.seq = retries++;
+
+ req->match = tb_cfg_match;
+ req->copy = tb_cfg_copy;
+ req->request = &request;
+ req->request_size = 12 + 4 * length;
+ req->request_type = TB_CFG_PKG_WRITE;
+ req->response = &reply;
+ req->response_size = sizeof(reply);
+ req->response_type = TB_CFG_PKG_WRITE;
+
+ res = tb_cfg_request_sync(ctl, req, timeout_msec);
+
+ tb_cfg_request_put(req);
+
+ if (res.err != -ETIMEDOUT)
+ break;
+
+ /* Wait a bit (arbitrary time) until we send a retry */
+ usleep_range(10, 100);
+ }
+
+ 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);
+ switch (res.err) {
+ case 0:
+ /* Success */
+ break;
+
+ case 1:
+ /* Thunderbolt error, tb_error holds the actual number */
+ tb_cfg_print_error(ctl, &res);
+ return -EIO;
+
+ case -ETIMEDOUT:
+ tb_ctl_warn(ctl, "timeout reading config space %u from %#x\n",
+ space, offset);
+ break;
+
+ default:
+ WARN(1, "tb_cfg_read: %d\n", res.err);
+ break;
+ }
+ return res.err;
+}
+
+int tb_cfg_write(struct tb_ctl *ctl, const 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);
+ switch (res.err) {
+ case 0:
+ /* Success */
+ break;
+
+ case 1:
+ /* Thunderbolt error, tb_error holds the actual number */
+ tb_cfg_print_error(ctl, &res);
+ return -EIO;
+
+ case -ETIMEDOUT:
+ tb_ctl_warn(ctl, "timeout writing config space %u to %#x\n",
+ space, offset);
+ break;
+
+ default:
+ WARN(1, "tb_cfg_write: %d\n", res.err);
+ break;
+ }
+ 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/src/kernel/linux/v4.14/drivers/thunderbolt/ctl.h b/src/kernel/linux/v4.14/drivers/thunderbolt/ctl.h
new file mode 100644
index 0000000..dd34f62
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/thunderbolt/ctl.h
@@ -0,0 +1,143 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * 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 <linux/kref.h>
+
+#include "nhi.h"
+#include "tb_msgs.h"
+
+/* control channel */
+struct tb_ctl;
+
+typedef void (*event_cb)(void *data, enum tb_cfg_pkg_type type,
+ const void *buf, size_t size);
+
+struct tb_ctl *tb_ctl_alloc(struct tb_nhi *nhi, event_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 */
+
+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 */
+};
+
+struct ctl_pkg {
+ struct tb_ctl *ctl;
+ void *buffer;
+ struct ring_frame frame;
+};
+
+/**
+ * struct tb_cfg_request - Control channel request
+ * @kref: Reference count
+ * @ctl: Pointer to the control channel structure. Only set when the
+ * request is queued.
+ * @request_size: Size of the request packet (in bytes)
+ * @request_type: Type of the request packet
+ * @response: Response is stored here
+ * @response_size: Maximum size of one response packet
+ * @response_type: Expected type of the response packet
+ * @npackets: Number of packets expected to be returned with this request
+ * @match: Function used to match the incoming packet
+ * @copy: Function used to copy the incoming packet to @response
+ * @callback: Callback called when the request is finished successfully
+ * @callback_data: Data to be passed to @callback
+ * @flags: Flags for the request
+ * @work: Work item used to complete the request
+ * @result: Result after the request has been completed
+ * @list: Requests are queued using this field
+ *
+ * An arbitrary request over Thunderbolt control channel. For standard
+ * control channel message, one should use tb_cfg_read/write() and
+ * friends if possible.
+ */
+struct tb_cfg_request {
+ struct kref kref;
+ struct tb_ctl *ctl;
+ const void *request;
+ size_t request_size;
+ enum tb_cfg_pkg_type request_type;
+ void *response;
+ size_t response_size;
+ enum tb_cfg_pkg_type response_type;
+ size_t npackets;
+ bool (*match)(const struct tb_cfg_request *req,
+ const struct ctl_pkg *pkg);
+ bool (*copy)(struct tb_cfg_request *req, const struct ctl_pkg *pkg);
+ void (*callback)(void *callback_data);
+ void *callback_data;
+ unsigned long flags;
+ struct work_struct work;
+ struct tb_cfg_result result;
+ struct list_head list;
+};
+
+#define TB_CFG_REQUEST_ACTIVE 0
+#define TB_CFG_REQUEST_CANCELED 1
+
+struct tb_cfg_request *tb_cfg_request_alloc(void);
+void tb_cfg_request_get(struct tb_cfg_request *req);
+void tb_cfg_request_put(struct tb_cfg_request *req);
+int tb_cfg_request(struct tb_ctl *ctl, struct tb_cfg_request *req,
+ void (*callback)(void *), void *callback_data);
+void tb_cfg_request_cancel(struct tb_cfg_request *req, int err);
+struct tb_cfg_result tb_cfg_request_sync(struct tb_ctl *ctl,
+ struct tb_cfg_request *req, int timeout_msec);
+
+static inline u64 tb_cfg_get_route(const struct tb_cfg_header *header)
+{
+ return (u64) header->route_hi << 32 | header->route_lo;
+}
+
+static inline struct tb_cfg_header tb_cfg_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(tb_cfg_get_route(&header) != route);
+ return header;
+}
+
+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, const 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, const 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
diff --git a/src/kernel/linux/v4.14/drivers/thunderbolt/dma_port.c b/src/kernel/linux/v4.14/drivers/thunderbolt/dma_port.c
new file mode 100644
index 0000000..af6dde3
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/thunderbolt/dma_port.c
@@ -0,0 +1,524 @@
+/*
+ * Thunderbolt DMA configuration based mailbox support
+ *
+ * Copyright (C) 2017, Intel Corporation
+ * Authors: Michael Jamet <michael.jamet@intel.com>
+ * Mika Westerberg <mika.westerberg@linux.intel.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/delay.h>
+#include <linux/slab.h>
+
+#include "dma_port.h"
+#include "tb_regs.h"
+
+#define DMA_PORT_CAP 0x3e
+
+#define MAIL_DATA 1
+#define MAIL_DATA_DWORDS 16
+
+#define MAIL_IN 17
+#define MAIL_IN_CMD_SHIFT 28
+#define MAIL_IN_CMD_MASK GENMASK(31, 28)
+#define MAIL_IN_CMD_FLASH_WRITE 0x0
+#define MAIL_IN_CMD_FLASH_UPDATE_AUTH 0x1
+#define MAIL_IN_CMD_FLASH_READ 0x2
+#define MAIL_IN_CMD_POWER_CYCLE 0x4
+#define MAIL_IN_DWORDS_SHIFT 24
+#define MAIL_IN_DWORDS_MASK GENMASK(27, 24)
+#define MAIL_IN_ADDRESS_SHIFT 2
+#define MAIL_IN_ADDRESS_MASK GENMASK(23, 2)
+#define MAIL_IN_CSS BIT(1)
+#define MAIL_IN_OP_REQUEST BIT(0)
+
+#define MAIL_OUT 18
+#define MAIL_OUT_STATUS_RESPONSE BIT(29)
+#define MAIL_OUT_STATUS_CMD_SHIFT 4
+#define MAIL_OUT_STATUS_CMD_MASK GENMASK(7, 4)
+#define MAIL_OUT_STATUS_MASK GENMASK(3, 0)
+#define MAIL_OUT_STATUS_COMPLETED 0
+#define MAIL_OUT_STATUS_ERR_AUTH 1
+#define MAIL_OUT_STATUS_ERR_ACCESS 2
+
+#define DMA_PORT_TIMEOUT 5000 /* ms */
+#define DMA_PORT_RETRIES 3
+
+/**
+ * struct tb_dma_port - DMA control port
+ * @sw: Switch the DMA port belongs to
+ * @port: Switch port number where DMA capability is found
+ * @base: Start offset of the mailbox registers
+ * @buf: Temporary buffer to store a single block
+ */
+struct tb_dma_port {
+ struct tb_switch *sw;
+ u8 port;
+ u32 base;
+ u8 *buf;
+};
+
+/*
+ * When the switch is in safe mode it supports very little functionality
+ * so we don't validate that much here.
+ */
+static bool dma_port_match(const struct tb_cfg_request *req,
+ const struct ctl_pkg *pkg)
+{
+ u64 route = tb_cfg_get_route(pkg->buffer) & ~BIT_ULL(63);
+
+ if (pkg->frame.eof == TB_CFG_PKG_ERROR)
+ return true;
+ if (pkg->frame.eof != req->response_type)
+ return false;
+ if (route != tb_cfg_get_route(req->request))
+ return false;
+ if (pkg->frame.size != req->response_size)
+ return false;
+
+ return true;
+}
+
+static bool dma_port_copy(struct tb_cfg_request *req, const struct ctl_pkg *pkg)
+{
+ memcpy(req->response, pkg->buffer, req->response_size);
+ return true;
+}
+
+static int dma_port_read(struct tb_ctl *ctl, void *buffer, u64 route,
+ u32 port, u32 offset, u32 length, int timeout_msec)
+{
+ struct cfg_read_pkg request = {
+ .header = tb_cfg_make_header(route),
+ .addr = {
+ .seq = 1,
+ .port = port,
+ .space = TB_CFG_PORT,
+ .offset = offset,
+ .length = length,
+ },
+ };
+ struct tb_cfg_request *req;
+ struct cfg_write_pkg reply;
+ struct tb_cfg_result res;
+
+ req = tb_cfg_request_alloc();
+ if (!req)
+ return -ENOMEM;
+
+ req->match = dma_port_match;
+ req->copy = dma_port_copy;
+ req->request = &request;
+ req->request_size = sizeof(request);
+ req->request_type = TB_CFG_PKG_READ;
+ req->response = &reply;
+ req->response_size = 12 + 4 * length;
+ req->response_type = TB_CFG_PKG_READ;
+
+ res = tb_cfg_request_sync(ctl, req, timeout_msec);
+
+ tb_cfg_request_put(req);
+
+ if (res.err)
+ return res.err;
+
+ memcpy(buffer, &reply.data, 4 * length);
+ return 0;
+}
+
+static int dma_port_write(struct tb_ctl *ctl, const void *buffer, u64 route,
+ u32 port, u32 offset, u32 length, int timeout_msec)
+{
+ struct cfg_write_pkg request = {
+ .header = tb_cfg_make_header(route),
+ .addr = {
+ .seq = 1,
+ .port = port,
+ .space = TB_CFG_PORT,
+ .offset = offset,
+ .length = length,
+ },
+ };
+ struct tb_cfg_request *req;
+ struct cfg_read_pkg reply;
+ struct tb_cfg_result res;
+
+ memcpy(&request.data, buffer, length * 4);
+
+ req = tb_cfg_request_alloc();
+ if (!req)
+ return -ENOMEM;
+
+ req->match = dma_port_match;
+ req->copy = dma_port_copy;
+ req->request = &request;
+ req->request_size = 12 + 4 * length;
+ req->request_type = TB_CFG_PKG_WRITE;
+ req->response = &reply;
+ req->response_size = sizeof(reply);
+ req->response_type = TB_CFG_PKG_WRITE;
+
+ res = tb_cfg_request_sync(ctl, req, timeout_msec);
+
+ tb_cfg_request_put(req);
+
+ return res.err;
+}
+
+static int dma_find_port(struct tb_switch *sw)
+{
+ int port, ret;
+ u32 type;
+
+ /*
+ * The DMA (NHI) port is either 3 or 5 depending on the
+ * controller. Try both starting from 5 which is more common.
+ */
+ port = 5;
+ ret = dma_port_read(sw->tb->ctl, &type, tb_route(sw), port, 2, 1,
+ DMA_PORT_TIMEOUT);
+ if (!ret && (type & 0xffffff) == TB_TYPE_NHI)
+ return port;
+
+ port = 3;
+ ret = dma_port_read(sw->tb->ctl, &type, tb_route(sw), port, 2, 1,
+ DMA_PORT_TIMEOUT);
+ if (!ret && (type & 0xffffff) == TB_TYPE_NHI)
+ return port;
+
+ return -ENODEV;
+}
+
+/**
+ * dma_port_alloc() - Finds DMA control port from a switch pointed by route
+ * @sw: Switch from where find the DMA port
+ *
+ * Function checks if the switch NHI port supports DMA configuration
+ * based mailbox capability and if it does, allocates and initializes
+ * DMA port structure. Returns %NULL if the capabity was not found.
+ *
+ * The DMA control port is functional also when the switch is in safe
+ * mode.
+ */
+struct tb_dma_port *dma_port_alloc(struct tb_switch *sw)
+{
+ struct tb_dma_port *dma;
+ int port;
+
+ port = dma_find_port(sw);
+ if (port < 0)
+ return NULL;
+
+ dma = kzalloc(sizeof(*dma), GFP_KERNEL);
+ if (!dma)
+ return NULL;
+
+ dma->buf = kmalloc_array(MAIL_DATA_DWORDS, sizeof(u32), GFP_KERNEL);
+ if (!dma->buf) {
+ kfree(dma);
+ return NULL;
+ }
+
+ dma->sw = sw;
+ dma->port = port;
+ dma->base = DMA_PORT_CAP;
+
+ return dma;
+}
+
+/**
+ * dma_port_free() - Release DMA control port structure
+ * @dma: DMA control port
+ */
+void dma_port_free(struct tb_dma_port *dma)
+{
+ if (dma) {
+ kfree(dma->buf);
+ kfree(dma);
+ }
+}
+
+static int dma_port_wait_for_completion(struct tb_dma_port *dma,
+ unsigned int timeout)
+{
+ unsigned long end = jiffies + msecs_to_jiffies(timeout);
+ struct tb_switch *sw = dma->sw;
+
+ do {
+ int ret;
+ u32 in;
+
+ ret = dma_port_read(sw->tb->ctl, &in, tb_route(sw), dma->port,
+ dma->base + MAIL_IN, 1, 50);
+ if (ret) {
+ if (ret != -ETIMEDOUT)
+ return ret;
+ } else if (!(in & MAIL_IN_OP_REQUEST)) {
+ return 0;
+ }
+
+ usleep_range(50, 100);
+ } while (time_before(jiffies, end));
+
+ return -ETIMEDOUT;
+}
+
+static int status_to_errno(u32 status)
+{
+ switch (status & MAIL_OUT_STATUS_MASK) {
+ case MAIL_OUT_STATUS_COMPLETED:
+ return 0;
+ case MAIL_OUT_STATUS_ERR_AUTH:
+ return -EINVAL;
+ case MAIL_OUT_STATUS_ERR_ACCESS:
+ return -EACCES;
+ }
+
+ return -EIO;
+}
+
+static int dma_port_request(struct tb_dma_port *dma, u32 in,
+ unsigned int timeout)
+{
+ struct tb_switch *sw = dma->sw;
+ u32 out;
+ int ret;
+
+ ret = dma_port_write(sw->tb->ctl, &in, tb_route(sw), dma->port,
+ dma->base + MAIL_IN, 1, DMA_PORT_TIMEOUT);
+ if (ret)
+ return ret;
+
+ ret = dma_port_wait_for_completion(dma, timeout);
+ if (ret)
+ return ret;
+
+ ret = dma_port_read(sw->tb->ctl, &out, tb_route(sw), dma->port,
+ dma->base + MAIL_OUT, 1, DMA_PORT_TIMEOUT);
+ if (ret)
+ return ret;
+
+ return status_to_errno(out);
+}
+
+static int dma_port_flash_read_block(struct tb_dma_port *dma, u32 address,
+ void *buf, u32 size)
+{
+ struct tb_switch *sw = dma->sw;
+ u32 in, dwaddress, dwords;
+ int ret;
+
+ dwaddress = address / 4;
+ dwords = size / 4;
+
+ in = MAIL_IN_CMD_FLASH_READ << MAIL_IN_CMD_SHIFT;
+ if (dwords < MAIL_DATA_DWORDS)
+ in |= (dwords << MAIL_IN_DWORDS_SHIFT) & MAIL_IN_DWORDS_MASK;
+ in |= (dwaddress << MAIL_IN_ADDRESS_SHIFT) & MAIL_IN_ADDRESS_MASK;
+ in |= MAIL_IN_OP_REQUEST;
+
+ ret = dma_port_request(dma, in, DMA_PORT_TIMEOUT);
+ if (ret)
+ return ret;
+
+ return dma_port_read(sw->tb->ctl, buf, tb_route(sw), dma->port,
+ dma->base + MAIL_DATA, dwords, DMA_PORT_TIMEOUT);
+}
+
+static int dma_port_flash_write_block(struct tb_dma_port *dma, u32 address,
+ const void *buf, u32 size)
+{
+ struct tb_switch *sw = dma->sw;
+ u32 in, dwaddress, dwords;
+ int ret;
+
+ dwords = size / 4;
+
+ /* Write the block to MAIL_DATA registers */
+ ret = dma_port_write(sw->tb->ctl, buf, tb_route(sw), dma->port,
+ dma->base + MAIL_DATA, dwords, DMA_PORT_TIMEOUT);
+
+ in = MAIL_IN_CMD_FLASH_WRITE << MAIL_IN_CMD_SHIFT;
+
+ /* CSS header write is always done to the same magic address */
+ if (address >= DMA_PORT_CSS_ADDRESS) {
+ dwaddress = DMA_PORT_CSS_ADDRESS;
+ in |= MAIL_IN_CSS;
+ } else {
+ dwaddress = address / 4;
+ }
+
+ in |= ((dwords - 1) << MAIL_IN_DWORDS_SHIFT) & MAIL_IN_DWORDS_MASK;
+ in |= (dwaddress << MAIL_IN_ADDRESS_SHIFT) & MAIL_IN_ADDRESS_MASK;
+ in |= MAIL_IN_OP_REQUEST;
+
+ return dma_port_request(dma, in, DMA_PORT_TIMEOUT);
+}
+
+/**
+ * dma_port_flash_read() - Read from active flash region
+ * @dma: DMA control port
+ * @address: Address relative to the start of active region
+ * @buf: Buffer where the data is read
+ * @size: Size of the buffer
+ */
+int dma_port_flash_read(struct tb_dma_port *dma, unsigned int address,
+ void *buf, size_t size)
+{
+ unsigned int retries = DMA_PORT_RETRIES;
+ unsigned int offset;
+
+ offset = address & 3;
+ address = address & ~3;
+
+ do {
+ u32 nbytes = min_t(u32, size, MAIL_DATA_DWORDS * 4);
+ int ret;
+
+ ret = dma_port_flash_read_block(dma, address, dma->buf,
+ ALIGN(nbytes, 4));
+ if (ret) {
+ if (ret == -ETIMEDOUT) {
+ if (retries--)
+ continue;
+ ret = -EIO;
+ }
+ return ret;
+ }
+
+ memcpy(buf, dma->buf + offset, nbytes);
+
+ size -= nbytes;
+ address += nbytes;
+ buf += nbytes;
+ } while (size > 0);
+
+ return 0;
+}
+
+/**
+ * dma_port_flash_write() - Write to non-active flash region
+ * @dma: DMA control port
+ * @address: Address relative to the start of non-active region
+ * @buf: Data to write
+ * @size: Size of the buffer
+ *
+ * Writes block of data to the non-active flash region of the switch. If
+ * the address is given as %DMA_PORT_CSS_ADDRESS the block is written
+ * using CSS command.
+ */
+int dma_port_flash_write(struct tb_dma_port *dma, unsigned int address,
+ const void *buf, size_t size)
+{
+ unsigned int retries = DMA_PORT_RETRIES;
+ unsigned int offset;
+
+ if (address >= DMA_PORT_CSS_ADDRESS) {
+ offset = 0;
+ if (size > DMA_PORT_CSS_MAX_SIZE)
+ return -E2BIG;
+ } else {
+ offset = address & 3;
+ address = address & ~3;
+ }
+
+ do {
+ u32 nbytes = min_t(u32, size, MAIL_DATA_DWORDS * 4);
+ int ret;
+
+ memcpy(dma->buf + offset, buf, nbytes);
+
+ ret = dma_port_flash_write_block(dma, address, buf, nbytes);
+ if (ret) {
+ if (ret == -ETIMEDOUT) {
+ if (retries--)
+ continue;
+ ret = -EIO;
+ }
+ return ret;
+ }
+
+ size -= nbytes;
+ address += nbytes;
+ buf += nbytes;
+ } while (size > 0);
+
+ return 0;
+}
+
+/**
+ * dma_port_flash_update_auth() - Starts flash authenticate cycle
+ * @dma: DMA control port
+ *
+ * Starts the flash update authentication cycle. If the image in the
+ * non-active area was valid, the switch starts upgrade process where
+ * active and non-active area get swapped in the end. Caller should call
+ * dma_port_flash_update_auth_status() to get status of this command.
+ * This is because if the switch in question is root switch the
+ * thunderbolt host controller gets reset as well.
+ */
+int dma_port_flash_update_auth(struct tb_dma_port *dma)
+{
+ u32 in;
+
+ in = MAIL_IN_CMD_FLASH_UPDATE_AUTH << MAIL_IN_CMD_SHIFT;
+ in |= MAIL_IN_OP_REQUEST;
+
+ return dma_port_request(dma, in, 150);
+}
+
+/**
+ * dma_port_flash_update_auth_status() - Reads status of update auth command
+ * @dma: DMA control port
+ * @status: Status code of the operation
+ *
+ * The function checks if there is status available from the last update
+ * auth command. Returns %0 if there is no status and no further
+ * action is required. If there is status, %1 is returned instead and
+ * @status holds the failure code.
+ *
+ * Negative return means there was an error reading status from the
+ * switch.
+ */
+int dma_port_flash_update_auth_status(struct tb_dma_port *dma, u32 *status)
+{
+ struct tb_switch *sw = dma->sw;
+ u32 out, cmd;
+ int ret;
+
+ ret = dma_port_read(sw->tb->ctl, &out, tb_route(sw), dma->port,
+ dma->base + MAIL_OUT, 1, DMA_PORT_TIMEOUT);
+ if (ret)
+ return ret;
+
+ /* Check if the status relates to flash update auth */
+ cmd = (out & MAIL_OUT_STATUS_CMD_MASK) >> MAIL_OUT_STATUS_CMD_SHIFT;
+ if (cmd == MAIL_IN_CMD_FLASH_UPDATE_AUTH) {
+ if (status)
+ *status = out & MAIL_OUT_STATUS_MASK;
+
+ /* Reset is needed in any case */
+ return 1;
+ }
+
+ return 0;
+}
+
+/**
+ * dma_port_power_cycle() - Power cycles the switch
+ * @dma: DMA control port
+ *
+ * Triggers power cycle to the switch.
+ */
+int dma_port_power_cycle(struct tb_dma_port *dma)
+{
+ u32 in;
+
+ in = MAIL_IN_CMD_POWER_CYCLE << MAIL_IN_CMD_SHIFT;
+ in |= MAIL_IN_OP_REQUEST;
+
+ return dma_port_request(dma, in, 150);
+}
diff --git a/src/kernel/linux/v4.14/drivers/thunderbolt/dma_port.h b/src/kernel/linux/v4.14/drivers/thunderbolt/dma_port.h
new file mode 100644
index 0000000..c4a69e0
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/thunderbolt/dma_port.h
@@ -0,0 +1,34 @@
+/*
+ * Thunderbolt DMA configuration based mailbox support
+ *
+ * Copyright (C) 2017, Intel Corporation
+ * Authors: Michael Jamet <michael.jamet@intel.com>
+ * Mika Westerberg <mika.westerberg@linux.intel.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef DMA_PORT_H_
+#define DMA_PORT_H_
+
+#include "tb.h"
+
+struct tb_switch;
+struct tb_dma_port;
+
+#define DMA_PORT_CSS_ADDRESS 0x3fffff
+#define DMA_PORT_CSS_MAX_SIZE SZ_128
+
+struct tb_dma_port *dma_port_alloc(struct tb_switch *sw);
+void dma_port_free(struct tb_dma_port *dma);
+int dma_port_flash_read(struct tb_dma_port *dma, unsigned int address,
+ void *buf, size_t size);
+int dma_port_flash_update_auth(struct tb_dma_port *dma);
+int dma_port_flash_update_auth_status(struct tb_dma_port *dma, u32 *status);
+int dma_port_flash_write(struct tb_dma_port *dma, unsigned int address,
+ const void *buf, size_t size);
+int dma_port_power_cycle(struct tb_dma_port *dma);
+
+#endif
diff --git a/src/kernel/linux/v4.14/drivers/thunderbolt/domain.c b/src/kernel/linux/v4.14/drivers/thunderbolt/domain.c
new file mode 100644
index 0000000..9f2dcd4
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/thunderbolt/domain.c
@@ -0,0 +1,456 @@
+/*
+ * Thunderbolt bus support
+ *
+ * Copyright (C) 2017, Intel Corporation
+ * Author: Mika Westerberg <mika.westerberg@linux.intel.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/device.h>
+#include <linux/idr.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/random.h>
+#include <crypto/hash.h>
+
+#include "tb.h"
+
+static DEFINE_IDA(tb_domain_ida);
+
+static const char * const tb_security_names[] = {
+ [TB_SECURITY_NONE] = "none",
+ [TB_SECURITY_USER] = "user",
+ [TB_SECURITY_SECURE] = "secure",
+ [TB_SECURITY_DPONLY] = "dponly",
+};
+
+static ssize_t security_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct tb *tb = container_of(dev, struct tb, dev);
+
+ return sprintf(buf, "%s\n", tb_security_names[tb->security_level]);
+}
+static DEVICE_ATTR_RO(security);
+
+static struct attribute *domain_attrs[] = {
+ &dev_attr_security.attr,
+ NULL,
+};
+
+static struct attribute_group domain_attr_group = {
+ .attrs = domain_attrs,
+};
+
+static const struct attribute_group *domain_attr_groups[] = {
+ &domain_attr_group,
+ NULL,
+};
+
+struct bus_type tb_bus_type = {
+ .name = "thunderbolt",
+};
+
+static void tb_domain_release(struct device *dev)
+{
+ struct tb *tb = container_of(dev, struct tb, dev);
+
+ tb_ctl_free(tb->ctl);
+ destroy_workqueue(tb->wq);
+ ida_simple_remove(&tb_domain_ida, tb->index);
+ mutex_destroy(&tb->lock);
+ kfree(tb);
+}
+
+struct device_type tb_domain_type = {
+ .name = "thunderbolt_domain",
+ .release = tb_domain_release,
+};
+
+/**
+ * tb_domain_alloc() - Allocate a domain
+ * @nhi: Pointer to the host controller
+ * @privsize: Size of the connection manager private data
+ *
+ * Allocates and initializes a new Thunderbolt domain. Connection
+ * managers are expected to call this and then fill in @cm_ops
+ * accordingly.
+ *
+ * Call tb_domain_put() to release the domain before it has been added
+ * to the system.
+ *
+ * Return: allocated domain structure on %NULL in case of error
+ */
+struct tb *tb_domain_alloc(struct tb_nhi *nhi, size_t privsize)
+{
+ struct tb *tb;
+
+ /*
+ * Make sure the structure sizes map with that the hardware
+ * expects because bit-fields are being used.
+ */
+ 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) + privsize, GFP_KERNEL);
+ if (!tb)
+ return NULL;
+
+ tb->nhi = nhi;
+ mutex_init(&tb->lock);
+
+ tb->index = ida_simple_get(&tb_domain_ida, 0, 0, GFP_KERNEL);
+ if (tb->index < 0)
+ goto err_free;
+
+ tb->wq = alloc_ordered_workqueue("thunderbolt%d", 0, tb->index);
+ if (!tb->wq)
+ goto err_remove_ida;
+
+ tb->dev.parent = &nhi->pdev->dev;
+ tb->dev.bus = &tb_bus_type;
+ tb->dev.type = &tb_domain_type;
+ tb->dev.groups = domain_attr_groups;
+ dev_set_name(&tb->dev, "domain%d", tb->index);
+ device_initialize(&tb->dev);
+
+ return tb;
+
+err_remove_ida:
+ ida_simple_remove(&tb_domain_ida, tb->index);
+err_free:
+ kfree(tb);
+
+ return NULL;
+}
+
+static void tb_domain_event_cb(void *data, enum tb_cfg_pkg_type type,
+ const void *buf, size_t size)
+{
+ struct tb *tb = data;
+
+ if (!tb->cm_ops->handle_event) {
+ tb_warn(tb, "domain does not have event handler\n");
+ return;
+ }
+
+ tb->cm_ops->handle_event(tb, type, buf, size);
+}
+
+/**
+ * tb_domain_add() - Add domain to the system
+ * @tb: Domain to add
+ *
+ * Starts the domain and adds it to the system. Hotplugging devices will
+ * work after this has been returned successfully. In order to remove
+ * and release the domain after this function has been called, call
+ * tb_domain_remove().
+ *
+ * Return: %0 in case of success and negative errno in case of error
+ */
+int tb_domain_add(struct tb *tb)
+{
+ int ret;
+
+ if (WARN_ON(!tb->cm_ops))
+ return -EINVAL;
+
+ mutex_lock(&tb->lock);
+
+ tb->ctl = tb_ctl_alloc(tb->nhi, tb_domain_event_cb, tb);
+ if (!tb->ctl) {
+ ret = -ENOMEM;
+ goto err_unlock;
+ }
+
+ /*
+ * 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);
+
+ if (tb->cm_ops->driver_ready) {
+ ret = tb->cm_ops->driver_ready(tb);
+ if (ret)
+ goto err_ctl_stop;
+ }
+
+ ret = device_add(&tb->dev);
+ if (ret)
+ goto err_ctl_stop;
+
+ /* Start the domain */
+ if (tb->cm_ops->start) {
+ ret = tb->cm_ops->start(tb);
+ if (ret)
+ goto err_domain_del;
+ }
+
+ /* This starts event processing */
+ mutex_unlock(&tb->lock);
+
+ return 0;
+
+err_domain_del:
+ device_del(&tb->dev);
+err_ctl_stop:
+ tb_ctl_stop(tb->ctl);
+err_unlock:
+ mutex_unlock(&tb->lock);
+
+ return ret;
+}
+
+/**
+ * tb_domain_remove() - Removes and releases a domain
+ * @tb: Domain to remove
+ *
+ * Stops the domain, removes it from the system and releases all
+ * resources once the last reference has been released.
+ */
+void tb_domain_remove(struct tb *tb)
+{
+ mutex_lock(&tb->lock);
+ if (tb->cm_ops->stop)
+ tb->cm_ops->stop(tb);
+ /* Stop the domain control traffic */
+ tb_ctl_stop(tb->ctl);
+ mutex_unlock(&tb->lock);
+
+ flush_workqueue(tb->wq);
+ device_unregister(&tb->dev);
+}
+
+/**
+ * tb_domain_suspend_noirq() - Suspend a domain
+ * @tb: Domain to suspend
+ *
+ * Suspends all devices in the domain and stops the control channel.
+ */
+int tb_domain_suspend_noirq(struct tb *tb)
+{
+ int ret = 0;
+
+ /*
+ * The control channel interrupt is left enabled during suspend
+ * and taking the lock here prevents any events happening before
+ * we actually have stopped the domain and the control channel.
+ */
+ mutex_lock(&tb->lock);
+ if (tb->cm_ops->suspend_noirq)
+ ret = tb->cm_ops->suspend_noirq(tb);
+ if (!ret)
+ tb_ctl_stop(tb->ctl);
+ mutex_unlock(&tb->lock);
+
+ return ret;
+}
+
+/**
+ * tb_domain_resume_noirq() - Resume a domain
+ * @tb: Domain to resume
+ *
+ * Re-starts the control channel, and resumes all devices connected to
+ * the domain.
+ */
+int tb_domain_resume_noirq(struct tb *tb)
+{
+ int ret = 0;
+
+ mutex_lock(&tb->lock);
+ tb_ctl_start(tb->ctl);
+ if (tb->cm_ops->resume_noirq)
+ ret = tb->cm_ops->resume_noirq(tb);
+ mutex_unlock(&tb->lock);
+
+ return ret;
+}
+
+int tb_domain_suspend(struct tb *tb)
+{
+ int ret;
+
+ mutex_lock(&tb->lock);
+ if (tb->cm_ops->suspend) {
+ ret = tb->cm_ops->suspend(tb);
+ if (ret) {
+ mutex_unlock(&tb->lock);
+ return ret;
+ }
+ }
+ mutex_unlock(&tb->lock);
+ return 0;
+}
+
+void tb_domain_complete(struct tb *tb)
+{
+ mutex_lock(&tb->lock);
+ if (tb->cm_ops->complete)
+ tb->cm_ops->complete(tb);
+ mutex_unlock(&tb->lock);
+}
+
+/**
+ * tb_domain_approve_switch() - Approve switch
+ * @tb: Domain the switch belongs to
+ * @sw: Switch to approve
+ *
+ * This will approve switch by connection manager specific means. In
+ * case of success the connection manager will create tunnels for all
+ * supported protocols.
+ */
+int tb_domain_approve_switch(struct tb *tb, struct tb_switch *sw)
+{
+ struct tb_switch *parent_sw;
+
+ if (!tb->cm_ops->approve_switch)
+ return -EPERM;
+
+ /* The parent switch must be authorized before this one */
+ parent_sw = tb_to_switch(sw->dev.parent);
+ if (!parent_sw || !parent_sw->authorized)
+ return -EINVAL;
+
+ return tb->cm_ops->approve_switch(tb, sw);
+}
+
+/**
+ * tb_domain_approve_switch_key() - Approve switch and add key
+ * @tb: Domain the switch belongs to
+ * @sw: Switch to approve
+ *
+ * For switches that support secure connect, this function first adds
+ * key to the switch NVM using connection manager specific means. If
+ * adding the key is successful, the switch is approved and connected.
+ *
+ * Return: %0 on success and negative errno in case of failure.
+ */
+int tb_domain_approve_switch_key(struct tb *tb, struct tb_switch *sw)
+{
+ struct tb_switch *parent_sw;
+ int ret;
+
+ if (!tb->cm_ops->approve_switch || !tb->cm_ops->add_switch_key)
+ return -EPERM;
+
+ /* The parent switch must be authorized before this one */
+ parent_sw = tb_to_switch(sw->dev.parent);
+ if (!parent_sw || !parent_sw->authorized)
+ return -EINVAL;
+
+ ret = tb->cm_ops->add_switch_key(tb, sw);
+ if (ret)
+ return ret;
+
+ return tb->cm_ops->approve_switch(tb, sw);
+}
+
+/**
+ * tb_domain_challenge_switch_key() - Challenge and approve switch
+ * @tb: Domain the switch belongs to
+ * @sw: Switch to approve
+ *
+ * For switches that support secure connect, this function generates
+ * random challenge and sends it to the switch. The switch responds to
+ * this and if the response matches our random challenge, the switch is
+ * approved and connected.
+ *
+ * Return: %0 on success and negative errno in case of failure.
+ */
+int tb_domain_challenge_switch_key(struct tb *tb, struct tb_switch *sw)
+{
+ u8 challenge[TB_SWITCH_KEY_SIZE];
+ u8 response[TB_SWITCH_KEY_SIZE];
+ u8 hmac[TB_SWITCH_KEY_SIZE];
+ struct tb_switch *parent_sw;
+ struct crypto_shash *tfm;
+ struct shash_desc *shash;
+ int ret;
+
+ if (!tb->cm_ops->approve_switch || !tb->cm_ops->challenge_switch_key)
+ return -EPERM;
+
+ /* The parent switch must be authorized before this one */
+ parent_sw = tb_to_switch(sw->dev.parent);
+ if (!parent_sw || !parent_sw->authorized)
+ return -EINVAL;
+
+ get_random_bytes(challenge, sizeof(challenge));
+ ret = tb->cm_ops->challenge_switch_key(tb, sw, challenge, response);
+ if (ret)
+ return ret;
+
+ tfm = crypto_alloc_shash("hmac(sha256)", 0, 0);
+ if (IS_ERR(tfm))
+ return PTR_ERR(tfm);
+
+ ret = crypto_shash_setkey(tfm, sw->key, TB_SWITCH_KEY_SIZE);
+ if (ret)
+ goto err_free_tfm;
+
+ shash = kzalloc(sizeof(*shash) + crypto_shash_descsize(tfm),
+ GFP_KERNEL);
+ if (!shash) {
+ ret = -ENOMEM;
+ goto err_free_tfm;
+ }
+
+ shash->tfm = tfm;
+ shash->flags = CRYPTO_TFM_REQ_MAY_SLEEP;
+
+ memset(hmac, 0, sizeof(hmac));
+ ret = crypto_shash_digest(shash, challenge, sizeof(hmac), hmac);
+ if (ret)
+ goto err_free_shash;
+
+ /* The returned HMAC must match the one we calculated */
+ if (memcmp(response, hmac, sizeof(hmac))) {
+ ret = -EKEYREJECTED;
+ goto err_free_shash;
+ }
+
+ crypto_free_shash(tfm);
+ kfree(shash);
+
+ return tb->cm_ops->approve_switch(tb, sw);
+
+err_free_shash:
+ kfree(shash);
+err_free_tfm:
+ crypto_free_shash(tfm);
+
+ return ret;
+}
+
+/**
+ * tb_domain_disconnect_pcie_paths() - Disconnect all PCIe paths
+ * @tb: Domain whose PCIe paths to disconnect
+ *
+ * This needs to be called in preparation for NVM upgrade of the host
+ * controller. Makes sure all PCIe paths are disconnected.
+ *
+ * Return %0 on success and negative errno in case of error.
+ */
+int tb_domain_disconnect_pcie_paths(struct tb *tb)
+{
+ if (!tb->cm_ops->disconnect_pcie_paths)
+ return -EPERM;
+
+ return tb->cm_ops->disconnect_pcie_paths(tb);
+}
+
+int tb_domain_init(void)
+{
+ return bus_register(&tb_bus_type);
+}
+
+void tb_domain_exit(void)
+{
+ bus_unregister(&tb_bus_type);
+ ida_destroy(&tb_domain_ida);
+ tb_switch_exit();
+}
diff --git a/src/kernel/linux/v4.14/drivers/thunderbolt/eeprom.c b/src/kernel/linux/v4.14/drivers/thunderbolt/eeprom.c
new file mode 100644
index 0000000..3e8caf2
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/thunderbolt/eeprom.c
@@ -0,0 +1,593 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Thunderbolt Cactus Ridge driver - eeprom access
+ *
+ * Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com>
+ */
+
+#include <linux/crc32.h>
+#include <linux/property.h>
+#include <linux/slab.h>
+#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);
+}
+
+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 {
+ /* force unsigned to prevent "one-bit signed bitfield" warning */
+ TB_DROM_ENTRY_GENERIC = 0U,
+ 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_generic {
+ struct tb_drom_entry_header header;
+ u8 data[0];
+} __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 6-7, 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
+ */
+static int tb_eeprom_get_drom_offset(struct tb_switch *sw, u16 *offset)
+{
+ 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;
+ }
+ *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;
+
+ if (drom_offset == 0)
+ return -ENODEV;
+
+ /* read uid */
+ res = tb_eeprom_read_n(sw, drom_offset, data, 9);
+ if (res)
+ return res;
+
+ crc = tb_crc8(data + 1, 8);
+ if (crc != data[0]) {
+ tb_sw_warn(sw, "uid crc8 mismatch (expected: %#x, got: %#x)\n",
+ data[0], crc);
+ return -EIO;
+ }
+
+ *uid = *(u64 *)(data+1);
+ return 0;
+}
+
+static int tb_drom_parse_entry_generic(struct tb_switch *sw,
+ struct tb_drom_entry_header *header)
+{
+ const struct tb_drom_entry_generic *entry =
+ (const struct tb_drom_entry_generic *)header;
+
+ switch (header->index) {
+ case 1:
+ /* Length includes 2 bytes header so remove it before copy */
+ sw->vendor_name = kstrndup(entry->data,
+ header->len - sizeof(*header), GFP_KERNEL);
+ if (!sw->vendor_name)
+ return -ENOMEM;
+ break;
+
+ case 2:
+ sw->device_name = kstrndup(entry->data,
+ header->len - sizeof(*header), GFP_KERNEL);
+ if (!sw->device_name)
+ return -ENOMEM;
+ break;
+ }
+
+ return 0;
+}
+
+static int tb_drom_parse_entry_port(struct tb_switch *sw,
+ struct tb_drom_entry_header *header)
+{
+ struct tb_port *port;
+ int res;
+ enum tb_port_type type;
+
+ /*
+ * Some DROMs list more ports than the controller actually has
+ * so we skip those but allow the parser to continue.
+ */
+ if (header->index > sw->config.max_port_number) {
+ dev_info_once(&sw->dev, "ignoring unnecessary extra entries in DROM\n");
+ 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 %#zx)\n",
+ header->len, sizeof(struct tb_drom_entry_port));
+ return -EIO;
+ }
+ port->link_nr = entry->link_nr;
+ if (entry->has_dual_link_port)
+ port->dual_link_port =
+ &port->sw->ports[entry->dual_link_port_nr];
+ }
+ 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;
+ int res;
+
+ while (pos < drom_size) {
+ struct tb_drom_entry_header *entry = (void *) (sw->drom + pos);
+ if (pos + 1 == drom_size || pos + entry->len > drom_size
+ || !entry->len) {
+ tb_sw_warn(sw, "drom buffer overrun, aborting\n");
+ return -EIO;
+ }
+
+ switch (entry->type) {
+ case TB_DROM_ENTRY_GENERIC:
+ res = tb_drom_parse_entry_generic(sw, entry);
+ break;
+ case TB_DROM_ENTRY_PORT:
+ res = tb_drom_parse_entry_port(sw, entry);
+ break;
+ }
+ if (res)
+ return res;
+
+ pos += entry->len;
+ }
+ return 0;
+}
+
+/**
+ * tb_drom_copy_efi - copy drom supplied by EFI to sw->drom if present
+ */
+static int tb_drom_copy_efi(struct tb_switch *sw, u16 *size)
+{
+ struct device *dev = &sw->tb->nhi->pdev->dev;
+ int len, res;
+
+ len = device_property_read_u8_array(dev, "ThunderboltDROM", NULL, 0);
+ if (len < 0 || len < sizeof(struct tb_drom_header))
+ return -EINVAL;
+
+ sw->drom = kmalloc(len, GFP_KERNEL);
+ if (!sw->drom)
+ return -ENOMEM;
+
+ res = device_property_read_u8_array(dev, "ThunderboltDROM", sw->drom,
+ len);
+ if (res)
+ goto err;
+
+ *size = ((struct tb_drom_header *)sw->drom)->data_len +
+ TB_DROM_DATA_START;
+ if (*size > len)
+ goto err;
+
+ return 0;
+
+err:
+ kfree(sw->drom);
+ sw->drom = NULL;
+ return -EINVAL;
+}
+
+static int tb_drom_copy_nvm(struct tb_switch *sw, u16 *size)
+{
+ u32 drom_offset;
+ int ret;
+
+ if (!sw->dma_port)
+ return -ENODEV;
+
+ ret = tb_sw_read(sw, &drom_offset, TB_CFG_SWITCH,
+ sw->cap_plug_events + 12, 1);
+ if (ret)
+ return ret;
+
+ if (!drom_offset)
+ return -ENODEV;
+
+ ret = dma_port_flash_read(sw->dma_port, drom_offset + 14, size,
+ sizeof(*size));
+ if (ret)
+ return ret;
+
+ /* Size includes CRC8 + UID + CRC32 */
+ *size += 1 + 8 + 4;
+ sw->drom = kzalloc(*size, GFP_KERNEL);
+ if (!sw->drom)
+ return -ENOMEM;
+
+ ret = dma_port_flash_read(sw->dma_port, drom_offset, sw->drom, *size);
+ if (ret)
+ goto err_free;
+
+ /*
+ * Read UID from the minimal DROM because the one in NVM is just
+ * a placeholder.
+ */
+ tb_drom_read_uid_only(sw, &sw->uid);
+ return 0;
+
+err_free:
+ kfree(sw->drom);
+ sw->drom = NULL;
+ return ret;
+}
+
+/**
+ * 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) {
+ /*
+ * Apple's NHI EFI driver supplies a DROM for the root switch
+ * in a device property. Use it if available.
+ */
+ if (tb_drom_copy_efi(sw, &size) == 0)
+ goto parse;
+
+ /* Non-Apple hardware has the DROM as part of NVM */
+ if (tb_drom_copy_nvm(sw, &size) == 0)
+ goto parse;
+
+ /*
+ * 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];
+
+ /* Port 5 is inaccessible on this gen 1 controller */
+ if (sw->config.device_id == PCI_DEVICE_ID_INTEL_LIGHT_RIDGE)
+ sw->ports[5].disabled = true;
+
+ 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;
+
+parse:
+ 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;
+ }
+ if (!sw->uid)
+ sw->uid = header->uid;
+ sw->vendor = header->vendor_id;
+ sw->device = header->model_id;
+
+ 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), continuing\n",
+ header->data_crc32, crc);
+ }
+
+ if (header->device_rom_revision > 2)
+ 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);
+ sw->drom = NULL;
+ return -EIO;
+
+}
diff --git a/src/kernel/linux/v4.14/drivers/thunderbolt/icm.c b/src/kernel/linux/v4.14/drivers/thunderbolt/icm.c
new file mode 100644
index 0000000..91830b1
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/thunderbolt/icm.c
@@ -0,0 +1,1097 @@
+/*
+ * Internal Thunderbolt Connection Manager. This is a firmware running on
+ * the Thunderbolt host controller performing most of the low-level
+ * handling.
+ *
+ * Copyright (C) 2017, Intel Corporation
+ * Authors: Michael Jamet <michael.jamet@intel.com>
+ * Mika Westerberg <mika.westerberg@linux.intel.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/delay.h>
+#include <linux/mutex.h>
+#include <linux/pci.h>
+#include <linux/platform_data/x86/apple.h>
+#include <linux/sizes.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+
+#include "ctl.h"
+#include "nhi_regs.h"
+#include "tb.h"
+
+#define PCIE2CIO_CMD 0x30
+#define PCIE2CIO_CMD_TIMEOUT BIT(31)
+#define PCIE2CIO_CMD_START BIT(30)
+#define PCIE2CIO_CMD_WRITE BIT(21)
+#define PCIE2CIO_CMD_CS_MASK GENMASK(20, 19)
+#define PCIE2CIO_CMD_CS_SHIFT 19
+#define PCIE2CIO_CMD_PORT_MASK GENMASK(18, 13)
+#define PCIE2CIO_CMD_PORT_SHIFT 13
+
+#define PCIE2CIO_WRDATA 0x34
+#define PCIE2CIO_RDDATA 0x38
+
+#define PHY_PORT_CS1 0x37
+#define PHY_PORT_CS1_LINK_DISABLE BIT(14)
+#define PHY_PORT_CS1_LINK_STATE_MASK GENMASK(29, 26)
+#define PHY_PORT_CS1_LINK_STATE_SHIFT 26
+
+#define ICM_TIMEOUT 5000 /* ms */
+#define ICM_MAX_LINK 4
+#define ICM_MAX_DEPTH 6
+
+/**
+ * struct icm - Internal connection manager private data
+ * @request_lock: Makes sure only one message is send to ICM at time
+ * @rescan_work: Work used to rescan the surviving switches after resume
+ * @upstream_port: Pointer to the PCIe upstream port this host
+ * controller is connected. This is only set for systems
+ * where ICM needs to be started manually
+ * @vnd_cap: Vendor defined capability where PCIe2CIO mailbox resides
+ * (only set when @upstream_port is not %NULL)
+ * @safe_mode: ICM is in safe mode
+ * @is_supported: Checks if we can support ICM on this controller
+ * @get_mode: Read and return the ICM firmware mode (optional)
+ * @get_route: Find a route string for given switch
+ * @device_connected: Handle device connected ICM message
+ * @device_disconnected: Handle device disconnected ICM message
+ */
+struct icm {
+ struct mutex request_lock;
+ struct delayed_work rescan_work;
+ struct pci_dev *upstream_port;
+ int vnd_cap;
+ bool safe_mode;
+ bool (*is_supported)(struct tb *tb);
+ int (*get_mode)(struct tb *tb);
+ int (*get_route)(struct tb *tb, u8 link, u8 depth, u64 *route);
+ void (*device_connected)(struct tb *tb,
+ const struct icm_pkg_header *hdr);
+ void (*device_disconnected)(struct tb *tb,
+ const struct icm_pkg_header *hdr);
+};
+
+struct icm_notification {
+ struct work_struct work;
+ struct icm_pkg_header *pkg;
+ struct tb *tb;
+};
+
+static inline struct tb *icm_to_tb(struct icm *icm)
+{
+ return ((void *)icm - sizeof(struct tb));
+}
+
+static inline u8 phy_port_from_route(u64 route, u8 depth)
+{
+ return tb_switch_phy_port_from_link(route >> ((depth - 1) * 8));
+}
+
+static inline u8 dual_link_from_link(u8 link)
+{
+ return link ? ((link - 1) ^ 0x01) + 1 : 0;
+}
+
+static inline u64 get_route(u32 route_hi, u32 route_lo)
+{
+ return (u64)route_hi << 32 | route_lo;
+}
+
+static bool icm_match(const struct tb_cfg_request *req,
+ const struct ctl_pkg *pkg)
+{
+ const struct icm_pkg_header *res_hdr = pkg->buffer;
+ const struct icm_pkg_header *req_hdr = req->request;
+
+ if (pkg->frame.eof != req->response_type)
+ return false;
+ if (res_hdr->code != req_hdr->code)
+ return false;
+
+ return true;
+}
+
+static bool icm_copy(struct tb_cfg_request *req, const struct ctl_pkg *pkg)
+{
+ const struct icm_pkg_header *hdr = pkg->buffer;
+
+ if (hdr->packet_id < req->npackets) {
+ size_t offset = hdr->packet_id * req->response_size;
+
+ memcpy(req->response + offset, pkg->buffer, req->response_size);
+ }
+
+ return hdr->packet_id == hdr->total_packets - 1;
+}
+
+static int icm_request(struct tb *tb, const void *request, size_t request_size,
+ void *response, size_t response_size, size_t npackets,
+ unsigned int timeout_msec)
+{
+ struct icm *icm = tb_priv(tb);
+ int retries = 3;
+
+ do {
+ struct tb_cfg_request *req;
+ struct tb_cfg_result res;
+
+ req = tb_cfg_request_alloc();
+ if (!req)
+ return -ENOMEM;
+
+ req->match = icm_match;
+ req->copy = icm_copy;
+ req->request = request;
+ req->request_size = request_size;
+ req->request_type = TB_CFG_PKG_ICM_CMD;
+ req->response = response;
+ req->npackets = npackets;
+ req->response_size = response_size;
+ req->response_type = TB_CFG_PKG_ICM_RESP;
+
+ mutex_lock(&icm->request_lock);
+ res = tb_cfg_request_sync(tb->ctl, req, timeout_msec);
+ mutex_unlock(&icm->request_lock);
+
+ tb_cfg_request_put(req);
+
+ if (res.err != -ETIMEDOUT)
+ return res.err == 1 ? -EIO : res.err;
+
+ usleep_range(20, 50);
+ } while (retries--);
+
+ return -ETIMEDOUT;
+}
+
+static bool icm_fr_is_supported(struct tb *tb)
+{
+ return !x86_apple_machine;
+}
+
+static inline int icm_fr_get_switch_index(u32 port)
+{
+ int index;
+
+ if ((port & ICM_PORT_TYPE_MASK) != TB_TYPE_PORT)
+ return 0;
+
+ index = port >> ICM_PORT_INDEX_SHIFT;
+ return index != 0xff ? index : 0;
+}
+
+static int icm_fr_get_route(struct tb *tb, u8 link, u8 depth, u64 *route)
+{
+ struct icm_fr_pkg_get_topology_response *switches, *sw;
+ struct icm_fr_pkg_get_topology request = {
+ .hdr = { .code = ICM_GET_TOPOLOGY },
+ };
+ size_t npackets = ICM_GET_TOPOLOGY_PACKETS;
+ int ret, index;
+ u8 i;
+
+ switches = kcalloc(npackets, sizeof(*switches), GFP_KERNEL);
+ if (!switches)
+ return -ENOMEM;
+
+ ret = icm_request(tb, &request, sizeof(request), switches,
+ sizeof(*switches), npackets, ICM_TIMEOUT);
+ if (ret)
+ goto err_free;
+
+ sw = &switches[0];
+ index = icm_fr_get_switch_index(sw->ports[link]);
+ if (!index) {
+ ret = -ENODEV;
+ goto err_free;
+ }
+
+ sw = &switches[index];
+ for (i = 1; i < depth; i++) {
+ unsigned int j;
+
+ if (!(sw->first_data & ICM_SWITCH_USED)) {
+ ret = -ENODEV;
+ goto err_free;
+ }
+
+ for (j = 0; j < ARRAY_SIZE(sw->ports); j++) {
+ index = icm_fr_get_switch_index(sw->ports[j]);
+ if (index > sw->switch_index) {
+ sw = &switches[index];
+ break;
+ }
+ }
+ }
+
+ *route = get_route(sw->route_hi, sw->route_lo);
+
+err_free:
+ kfree(switches);
+ return ret;
+}
+
+static int icm_fr_approve_switch(struct tb *tb, struct tb_switch *sw)
+{
+ struct icm_fr_pkg_approve_device request;
+ struct icm_fr_pkg_approve_device reply;
+ int ret;
+
+ memset(&request, 0, sizeof(request));
+ memcpy(&request.ep_uuid, sw->uuid, sizeof(request.ep_uuid));
+ request.hdr.code = ICM_APPROVE_DEVICE;
+ request.connection_id = sw->connection_id;
+ request.connection_key = sw->connection_key;
+
+ memset(&reply, 0, sizeof(reply));
+ /* Use larger timeout as establishing tunnels can take some time */
+ ret = icm_request(tb, &request, sizeof(request), &reply, sizeof(reply),
+ 1, 10000);
+ if (ret)
+ return ret;
+
+ if (reply.hdr.flags & ICM_FLAGS_ERROR) {
+ tb_warn(tb, "PCIe tunnel creation failed\n");
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int icm_fr_add_switch_key(struct tb *tb, struct tb_switch *sw)
+{
+ struct icm_fr_pkg_add_device_key request;
+ struct icm_fr_pkg_add_device_key_response reply;
+ int ret;
+
+ memset(&request, 0, sizeof(request));
+ memcpy(&request.ep_uuid, sw->uuid, sizeof(request.ep_uuid));
+ request.hdr.code = ICM_ADD_DEVICE_KEY;
+ request.connection_id = sw->connection_id;
+ request.connection_key = sw->connection_key;
+ memcpy(request.key, sw->key, TB_SWITCH_KEY_SIZE);
+
+ memset(&reply, 0, sizeof(reply));
+ ret = icm_request(tb, &request, sizeof(request), &reply, sizeof(reply),
+ 1, ICM_TIMEOUT);
+ if (ret)
+ return ret;
+
+ if (reply.hdr.flags & ICM_FLAGS_ERROR) {
+ tb_warn(tb, "Adding key to switch failed\n");
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int icm_fr_challenge_switch_key(struct tb *tb, struct tb_switch *sw,
+ const u8 *challenge, u8 *response)
+{
+ struct icm_fr_pkg_challenge_device request;
+ struct icm_fr_pkg_challenge_device_response reply;
+ int ret;
+
+ memset(&request, 0, sizeof(request));
+ memcpy(&request.ep_uuid, sw->uuid, sizeof(request.ep_uuid));
+ request.hdr.code = ICM_CHALLENGE_DEVICE;
+ request.connection_id = sw->connection_id;
+ request.connection_key = sw->connection_key;
+ memcpy(request.challenge, challenge, TB_SWITCH_KEY_SIZE);
+
+ memset(&reply, 0, sizeof(reply));
+ ret = icm_request(tb, &request, sizeof(request), &reply, sizeof(reply),
+ 1, ICM_TIMEOUT);
+ if (ret)
+ return ret;
+
+ if (reply.hdr.flags & ICM_FLAGS_ERROR)
+ return -EKEYREJECTED;
+ if (reply.hdr.flags & ICM_FLAGS_NO_KEY)
+ return -ENOKEY;
+
+ memcpy(response, reply.response, TB_SWITCH_KEY_SIZE);
+
+ return 0;
+}
+
+static void remove_switch(struct tb_switch *sw)
+{
+ struct tb_switch *parent_sw;
+
+ parent_sw = tb_to_switch(sw->dev.parent);
+ tb_port_at(tb_route(sw), parent_sw)->remote = NULL;
+ tb_switch_remove(sw);
+}
+
+static void
+icm_fr_device_connected(struct tb *tb, const struct icm_pkg_header *hdr)
+{
+ const struct icm_fr_event_device_connected *pkg =
+ (const struct icm_fr_event_device_connected *)hdr;
+ struct tb_switch *sw, *parent_sw;
+ struct icm *icm = tb_priv(tb);
+ bool authorized = false;
+ u8 link, depth;
+ u64 route;
+ int ret;
+
+ link = pkg->link_info & ICM_LINK_INFO_LINK_MASK;
+ depth = (pkg->link_info & ICM_LINK_INFO_DEPTH_MASK) >>
+ ICM_LINK_INFO_DEPTH_SHIFT;
+ authorized = pkg->link_info & ICM_LINK_INFO_APPROVED;
+
+ ret = icm->get_route(tb, link, depth, &route);
+ if (ret) {
+ tb_err(tb, "failed to find route string for switch at %u.%u\n",
+ link, depth);
+ return;
+ }
+
+ sw = tb_switch_find_by_uuid(tb, &pkg->ep_uuid);
+ if (sw) {
+ u8 phy_port, sw_phy_port;
+
+ parent_sw = tb_to_switch(sw->dev.parent);
+ sw_phy_port = phy_port_from_route(tb_route(sw), sw->depth);
+ phy_port = phy_port_from_route(route, depth);
+
+ /*
+ * On resume ICM will send us connected events for the
+ * devices that still are present. However, that
+ * information might have changed for example by the
+ * fact that a switch on a dual-link connection might
+ * have been enumerated using the other link now. Make
+ * sure our book keeping matches that.
+ */
+ if (sw->depth == depth && sw_phy_port == phy_port &&
+ !!sw->authorized == authorized) {
+ tb_port_at(tb_route(sw), parent_sw)->remote = NULL;
+ tb_port_at(route, parent_sw)->remote =
+ tb_upstream_port(sw);
+ sw->config.route_hi = upper_32_bits(route);
+ sw->config.route_lo = lower_32_bits(route);
+ sw->connection_id = pkg->connection_id;
+ sw->connection_key = pkg->connection_key;
+ sw->link = link;
+ sw->depth = depth;
+ sw->is_unplugged = false;
+ tb_switch_put(sw);
+ return;
+ }
+
+ /*
+ * User connected the same switch to another physical
+ * port or to another part of the topology. Remove the
+ * existing switch now before adding the new one.
+ */
+ remove_switch(sw);
+ tb_switch_put(sw);
+ }
+
+ /*
+ * If the switch was not found by UUID, look for a switch on
+ * same physical port (taking possible link aggregation into
+ * account) and depth. If we found one it is definitely a stale
+ * one so remove it first.
+ */
+ sw = tb_switch_find_by_link_depth(tb, link, depth);
+ if (!sw) {
+ u8 dual_link;
+
+ dual_link = dual_link_from_link(link);
+ if (dual_link)
+ sw = tb_switch_find_by_link_depth(tb, dual_link, depth);
+ }
+ if (sw) {
+ remove_switch(sw);
+ tb_switch_put(sw);
+ }
+
+ parent_sw = tb_switch_find_by_link_depth(tb, link, depth - 1);
+ if (!parent_sw) {
+ tb_err(tb, "failed to find parent switch for %u.%u\n",
+ link, depth);
+ return;
+ }
+
+ sw = tb_switch_alloc(tb, &parent_sw->dev, route);
+ if (!sw) {
+ tb_switch_put(parent_sw);
+ return;
+ }
+
+ sw->uuid = kmemdup(&pkg->ep_uuid, sizeof(pkg->ep_uuid), GFP_KERNEL);
+ sw->connection_id = pkg->connection_id;
+ sw->connection_key = pkg->connection_key;
+ sw->link = link;
+ sw->depth = depth;
+ sw->authorized = authorized;
+ sw->security_level = (pkg->hdr.flags & ICM_FLAGS_SLEVEL_MASK) >>
+ ICM_FLAGS_SLEVEL_SHIFT;
+
+ /* Link the two switches now */
+ tb_port_at(route, parent_sw)->remote = tb_upstream_port(sw);
+ tb_upstream_port(sw)->remote = tb_port_at(route, parent_sw);
+
+ ret = tb_switch_add(sw);
+ if (ret) {
+ tb_port_at(tb_route(sw), parent_sw)->remote = NULL;
+ tb_switch_put(sw);
+ }
+ tb_switch_put(parent_sw);
+}
+
+static void
+icm_fr_device_disconnected(struct tb *tb, const struct icm_pkg_header *hdr)
+{
+ const struct icm_fr_event_device_disconnected *pkg =
+ (const struct icm_fr_event_device_disconnected *)hdr;
+ struct tb_switch *sw;
+ u8 link, depth;
+
+ link = pkg->link_info & ICM_LINK_INFO_LINK_MASK;
+ depth = (pkg->link_info & ICM_LINK_INFO_DEPTH_MASK) >>
+ ICM_LINK_INFO_DEPTH_SHIFT;
+
+ if (link > ICM_MAX_LINK || depth > ICM_MAX_DEPTH) {
+ tb_warn(tb, "invalid topology %u.%u, ignoring\n", link, depth);
+ return;
+ }
+
+ sw = tb_switch_find_by_link_depth(tb, link, depth);
+ if (!sw) {
+ tb_warn(tb, "no switch exists at %u.%u, ignoring\n", link,
+ depth);
+ return;
+ }
+
+ remove_switch(sw);
+ tb_switch_put(sw);
+}
+
+static struct pci_dev *get_upstream_port(struct pci_dev *pdev)
+{
+ struct pci_dev *parent;
+
+ parent = pci_upstream_bridge(pdev);
+ while (parent) {
+ if (!pci_is_pcie(parent))
+ return NULL;
+ if (pci_pcie_type(parent) == PCI_EXP_TYPE_UPSTREAM)
+ break;
+ parent = pci_upstream_bridge(parent);
+ }
+
+ if (!parent)
+ return NULL;
+
+ switch (parent->device) {
+ case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_2C_BRIDGE:
+ case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_4C_BRIDGE:
+ case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_LP_BRIDGE:
+ case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_4C_BRIDGE:
+ case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_2C_BRIDGE:
+ return parent;
+ }
+
+ return NULL;
+}
+
+static bool icm_ar_is_supported(struct tb *tb)
+{
+ struct pci_dev *upstream_port;
+ struct icm *icm = tb_priv(tb);
+
+ /*
+ * Starting from Alpine Ridge we can use ICM on Apple machines
+ * as well. We just need to reset and re-enable it first.
+ */
+ if (!x86_apple_machine)
+ return true;
+
+ /*
+ * Find the upstream PCIe port in case we need to do reset
+ * through its vendor specific registers.
+ */
+ upstream_port = get_upstream_port(tb->nhi->pdev);
+ if (upstream_port) {
+ int cap;
+
+ cap = pci_find_ext_capability(upstream_port,
+ PCI_EXT_CAP_ID_VNDR);
+ if (cap > 0) {
+ icm->upstream_port = upstream_port;
+ icm->vnd_cap = cap;
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static int icm_ar_get_mode(struct tb *tb)
+{
+ struct tb_nhi *nhi = tb->nhi;
+ int retries = 60;
+ u32 val;
+
+ do {
+ val = ioread32(nhi->iobase + REG_FW_STS);
+ if (val & REG_FW_STS_NVM_AUTH_DONE)
+ break;
+ msleep(50);
+ } while (--retries);
+
+ if (!retries) {
+ dev_err(&nhi->pdev->dev, "ICM firmware not authenticated\n");
+ return -ENODEV;
+ }
+
+ return nhi_mailbox_mode(nhi);
+}
+
+static int icm_ar_get_route(struct tb *tb, u8 link, u8 depth, u64 *route)
+{
+ struct icm_ar_pkg_get_route_response reply;
+ struct icm_ar_pkg_get_route request = {
+ .hdr = { .code = ICM_GET_ROUTE },
+ .link_info = depth << ICM_LINK_INFO_DEPTH_SHIFT | link,
+ };
+ int ret;
+
+ memset(&reply, 0, sizeof(reply));
+ ret = icm_request(tb, &request, sizeof(request), &reply, sizeof(reply),
+ 1, ICM_TIMEOUT);
+ if (ret)
+ return ret;
+
+ if (reply.hdr.flags & ICM_FLAGS_ERROR)
+ return -EIO;
+
+ *route = get_route(reply.route_hi, reply.route_lo);
+ return 0;
+}
+
+static void icm_handle_notification(struct work_struct *work)
+{
+ struct icm_notification *n = container_of(work, typeof(*n), work);
+ struct tb *tb = n->tb;
+ struct icm *icm = tb_priv(tb);
+
+ mutex_lock(&tb->lock);
+
+ switch (n->pkg->code) {
+ case ICM_EVENT_DEVICE_CONNECTED:
+ icm->device_connected(tb, n->pkg);
+ break;
+ case ICM_EVENT_DEVICE_DISCONNECTED:
+ icm->device_disconnected(tb, n->pkg);
+ break;
+ }
+
+ mutex_unlock(&tb->lock);
+
+ kfree(n->pkg);
+ kfree(n);
+}
+
+static void icm_handle_event(struct tb *tb, enum tb_cfg_pkg_type type,
+ const void *buf, size_t size)
+{
+ struct icm_notification *n;
+
+ n = kmalloc(sizeof(*n), GFP_KERNEL);
+ if (!n)
+ return;
+
+ INIT_WORK(&n->work, icm_handle_notification);
+ n->pkg = kmemdup(buf, size, GFP_KERNEL);
+ n->tb = tb;
+
+ queue_work(tb->wq, &n->work);
+}
+
+static int
+__icm_driver_ready(struct tb *tb, enum tb_security_level *security_level)
+{
+ struct icm_pkg_driver_ready_response reply;
+ struct icm_pkg_driver_ready request = {
+ .hdr.code = ICM_DRIVER_READY,
+ };
+ unsigned int retries = 10;
+ int ret;
+
+ memset(&reply, 0, sizeof(reply));
+ ret = icm_request(tb, &request, sizeof(request), &reply, sizeof(reply),
+ 1, ICM_TIMEOUT);
+ if (ret)
+ return ret;
+
+ if (security_level)
+ *security_level = reply.security_level & 0xf;
+
+ /*
+ * Hold on here until the switch config space is accessible so
+ * that we can read root switch config successfully.
+ */
+ do {
+ struct tb_cfg_result res;
+ u32 tmp;
+
+ res = tb_cfg_read_raw(tb->ctl, &tmp, 0, 0, TB_CFG_SWITCH,
+ 0, 1, 100);
+ if (!res.err)
+ return 0;
+
+ msleep(50);
+ } while (--retries);
+
+ return -ETIMEDOUT;
+}
+
+static int pci2cio_wait_completion(struct icm *icm, unsigned long timeout_msec)
+{
+ unsigned long end = jiffies + msecs_to_jiffies(timeout_msec);
+ u32 cmd;
+
+ do {
+ pci_read_config_dword(icm->upstream_port,
+ icm->vnd_cap + PCIE2CIO_CMD, &cmd);
+ if (!(cmd & PCIE2CIO_CMD_START)) {
+ if (cmd & PCIE2CIO_CMD_TIMEOUT)
+ break;
+ return 0;
+ }
+
+ msleep(50);
+ } while (time_before(jiffies, end));
+
+ return -ETIMEDOUT;
+}
+
+static int pcie2cio_read(struct icm *icm, enum tb_cfg_space cs,
+ unsigned int port, unsigned int index, u32 *data)
+{
+ struct pci_dev *pdev = icm->upstream_port;
+ int ret, vnd_cap = icm->vnd_cap;
+ u32 cmd;
+
+ cmd = index;
+ cmd |= (port << PCIE2CIO_CMD_PORT_SHIFT) & PCIE2CIO_CMD_PORT_MASK;
+ cmd |= (cs << PCIE2CIO_CMD_CS_SHIFT) & PCIE2CIO_CMD_CS_MASK;
+ cmd |= PCIE2CIO_CMD_START;
+ pci_write_config_dword(pdev, vnd_cap + PCIE2CIO_CMD, cmd);
+
+ ret = pci2cio_wait_completion(icm, 5000);
+ if (ret)
+ return ret;
+
+ pci_read_config_dword(pdev, vnd_cap + PCIE2CIO_RDDATA, data);
+ return 0;
+}
+
+static int pcie2cio_write(struct icm *icm, enum tb_cfg_space cs,
+ unsigned int port, unsigned int index, u32 data)
+{
+ struct pci_dev *pdev = icm->upstream_port;
+ int vnd_cap = icm->vnd_cap;
+ u32 cmd;
+
+ pci_write_config_dword(pdev, vnd_cap + PCIE2CIO_WRDATA, data);
+
+ cmd = index;
+ cmd |= (port << PCIE2CIO_CMD_PORT_SHIFT) & PCIE2CIO_CMD_PORT_MASK;
+ cmd |= (cs << PCIE2CIO_CMD_CS_SHIFT) & PCIE2CIO_CMD_CS_MASK;
+ cmd |= PCIE2CIO_CMD_WRITE | PCIE2CIO_CMD_START;
+ pci_write_config_dword(pdev, vnd_cap + PCIE2CIO_CMD, cmd);
+
+ return pci2cio_wait_completion(icm, 5000);
+}
+
+static int icm_firmware_reset(struct tb *tb, struct tb_nhi *nhi)
+{
+ struct icm *icm = tb_priv(tb);
+ u32 val;
+
+ if (!icm->upstream_port)
+ return -ENODEV;
+
+ /* Put ARC to wait for CIO reset event to happen */
+ val = ioread32(nhi->iobase + REG_FW_STS);
+ val |= REG_FW_STS_CIO_RESET_REQ;
+ iowrite32(val, nhi->iobase + REG_FW_STS);
+
+ /* Re-start ARC */
+ val = ioread32(nhi->iobase + REG_FW_STS);
+ val |= REG_FW_STS_ICM_EN_INVERT;
+ val |= REG_FW_STS_ICM_EN_CPU;
+ iowrite32(val, nhi->iobase + REG_FW_STS);
+
+ /* Trigger CIO reset now */
+ return pcie2cio_write(icm, TB_CFG_SWITCH, 0, 0x50, BIT(9));
+}
+
+static int icm_firmware_start(struct tb *tb, struct tb_nhi *nhi)
+{
+ unsigned int retries = 10;
+ int ret;
+ u32 val;
+
+ /* Check if the ICM firmware is already running */
+ val = ioread32(nhi->iobase + REG_FW_STS);
+ if (val & REG_FW_STS_ICM_EN)
+ return 0;
+
+ dev_info(&nhi->pdev->dev, "starting ICM firmware\n");
+
+ ret = icm_firmware_reset(tb, nhi);
+ if (ret)
+ return ret;
+
+ /* Wait until the ICM firmware tells us it is up and running */
+ do {
+ /* Check that the ICM firmware is running */
+ val = ioread32(nhi->iobase + REG_FW_STS);
+ if (val & REG_FW_STS_NVM_AUTH_DONE)
+ return 0;
+
+ msleep(300);
+ } while (--retries);
+
+ return -ETIMEDOUT;
+}
+
+static int icm_reset_phy_port(struct tb *tb, int phy_port)
+{
+ struct icm *icm = tb_priv(tb);
+ u32 state0, state1;
+ int port0, port1;
+ u32 val0, val1;
+ int ret;
+
+ if (!icm->upstream_port)
+ return 0;
+
+ if (phy_port) {
+ port0 = 3;
+ port1 = 4;
+ } else {
+ port0 = 1;
+ port1 = 2;
+ }
+
+ /*
+ * Read link status of both null ports belonging to a single
+ * physical port.
+ */
+ ret = pcie2cio_read(icm, TB_CFG_PORT, port0, PHY_PORT_CS1, &val0);
+ if (ret)
+ return ret;
+ ret = pcie2cio_read(icm, TB_CFG_PORT, port1, PHY_PORT_CS1, &val1);
+ if (ret)
+ return ret;
+
+ state0 = val0 & PHY_PORT_CS1_LINK_STATE_MASK;
+ state0 >>= PHY_PORT_CS1_LINK_STATE_SHIFT;
+ state1 = val1 & PHY_PORT_CS1_LINK_STATE_MASK;
+ state1 >>= PHY_PORT_CS1_LINK_STATE_SHIFT;
+
+ /* If they are both up we need to reset them now */
+ if (state0 != TB_PORT_UP || state1 != TB_PORT_UP)
+ return 0;
+
+ val0 |= PHY_PORT_CS1_LINK_DISABLE;
+ ret = pcie2cio_write(icm, TB_CFG_PORT, port0, PHY_PORT_CS1, val0);
+ if (ret)
+ return ret;
+
+ val1 |= PHY_PORT_CS1_LINK_DISABLE;
+ ret = pcie2cio_write(icm, TB_CFG_PORT, port1, PHY_PORT_CS1, val1);
+ if (ret)
+ return ret;
+
+ /* Wait a bit and then re-enable both ports */
+ usleep_range(10, 100);
+
+ ret = pcie2cio_read(icm, TB_CFG_PORT, port0, PHY_PORT_CS1, &val0);
+ if (ret)
+ return ret;
+ ret = pcie2cio_read(icm, TB_CFG_PORT, port1, PHY_PORT_CS1, &val1);
+ if (ret)
+ return ret;
+
+ val0 &= ~PHY_PORT_CS1_LINK_DISABLE;
+ ret = pcie2cio_write(icm, TB_CFG_PORT, port0, PHY_PORT_CS1, val0);
+ if (ret)
+ return ret;
+
+ val1 &= ~PHY_PORT_CS1_LINK_DISABLE;
+ return pcie2cio_write(icm, TB_CFG_PORT, port1, PHY_PORT_CS1, val1);
+}
+
+static int icm_firmware_init(struct tb *tb)
+{
+ struct icm *icm = tb_priv(tb);
+ struct tb_nhi *nhi = tb->nhi;
+ int ret;
+
+ ret = icm_firmware_start(tb, nhi);
+ if (ret) {
+ dev_err(&nhi->pdev->dev, "could not start ICM firmware\n");
+ return ret;
+ }
+
+ if (icm->get_mode) {
+ ret = icm->get_mode(tb);
+
+ switch (ret) {
+ case NHI_FW_SAFE_MODE:
+ icm->safe_mode = true;
+ break;
+
+ case NHI_FW_CM_MODE:
+ /* Ask ICM to accept all Thunderbolt devices */
+ nhi_mailbox_cmd(nhi, NHI_MAILBOX_ALLOW_ALL_DEVS, 0);
+ break;
+
+ default:
+ if (ret < 0)
+ return ret;
+
+ tb_err(tb, "ICM firmware is in wrong mode: %u\n", ret);
+ return -ENODEV;
+ }
+ }
+
+ /*
+ * Reset both physical ports if there is anything connected to
+ * them already.
+ */
+ ret = icm_reset_phy_port(tb, 0);
+ if (ret)
+ dev_warn(&nhi->pdev->dev, "failed to reset links on port0\n");
+ ret = icm_reset_phy_port(tb, 1);
+ if (ret)
+ dev_warn(&nhi->pdev->dev, "failed to reset links on port1\n");
+
+ return 0;
+}
+
+static int icm_driver_ready(struct tb *tb)
+{
+ struct icm *icm = tb_priv(tb);
+ int ret;
+
+ ret = icm_firmware_init(tb);
+ if (ret)
+ return ret;
+
+ if (icm->safe_mode) {
+ tb_info(tb, "Thunderbolt host controller is in safe mode.\n");
+ tb_info(tb, "You need to update NVM firmware of the controller before it can be used.\n");
+ tb_info(tb, "For latest updates check https://thunderbolttechnology.net/updates.\n");
+ return 0;
+ }
+
+ return __icm_driver_ready(tb, &tb->security_level);
+}
+
+static int icm_suspend(struct tb *tb)
+{
+ int ret;
+
+ ret = nhi_mailbox_cmd(tb->nhi, NHI_MAILBOX_SAVE_DEVS, 0);
+ if (ret)
+ tb_info(tb, "Ignoring mailbox command error (%d) in %s\n",
+ ret, __func__);
+
+ return 0;
+}
+
+/*
+ * Mark all switches (except root switch) below this one unplugged. ICM
+ * firmware will send us an updated list of switches after we have send
+ * it driver ready command. If a switch is not in that list it will be
+ * removed when we perform rescan.
+ */
+static void icm_unplug_children(struct tb_switch *sw)
+{
+ unsigned int i;
+
+ if (tb_route(sw))
+ sw->is_unplugged = true;
+
+ 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;
+
+ icm_unplug_children(port->remote->sw);
+ }
+}
+
+static void icm_free_unplugged_children(struct tb_switch *sw)
+{
+ unsigned 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_remove(port->remote->sw);
+ port->remote = NULL;
+ } else {
+ icm_free_unplugged_children(port->remote->sw);
+ }
+ }
+}
+
+static void icm_rescan_work(struct work_struct *work)
+{
+ struct icm *icm = container_of(work, struct icm, rescan_work.work);
+ struct tb *tb = icm_to_tb(icm);
+
+ mutex_lock(&tb->lock);
+ if (tb->root_switch)
+ icm_free_unplugged_children(tb->root_switch);
+ mutex_unlock(&tb->lock);
+}
+
+static void icm_complete(struct tb *tb)
+{
+ struct icm *icm = tb_priv(tb);
+
+ if (tb->nhi->going_away)
+ return;
+
+ icm_unplug_children(tb->root_switch);
+
+ /*
+ * Now all existing children should be resumed, start events
+ * from ICM to get updated status.
+ */
+ __icm_driver_ready(tb, NULL);
+
+ /*
+ * We do not get notifications of devices that have been
+ * unplugged during suspend so schedule rescan to clean them up
+ * if any.
+ */
+ queue_delayed_work(tb->wq, &icm->rescan_work, msecs_to_jiffies(500));
+}
+
+static int icm_start(struct tb *tb)
+{
+ struct icm *icm = tb_priv(tb);
+ int ret;
+
+ if (icm->safe_mode)
+ tb->root_switch = tb_switch_alloc_safe_mode(tb, &tb->dev, 0);
+ else
+ tb->root_switch = tb_switch_alloc(tb, &tb->dev, 0);
+ if (!tb->root_switch)
+ return -ENODEV;
+
+ /*
+ * NVM upgrade has not been tested on Apple systems and they
+ * don't provide images publicly either. To be on the safe side
+ * prevent root switch NVM upgrade on Macs for now.
+ */
+ tb->root_switch->no_nvm_upgrade = x86_apple_machine;
+
+ ret = tb_switch_add(tb->root_switch);
+ if (ret)
+ tb_switch_put(tb->root_switch);
+
+ return ret;
+}
+
+static void icm_stop(struct tb *tb)
+{
+ struct icm *icm = tb_priv(tb);
+
+ cancel_delayed_work(&icm->rescan_work);
+ tb_switch_remove(tb->root_switch);
+ tb->root_switch = NULL;
+ nhi_mailbox_cmd(tb->nhi, NHI_MAILBOX_DRV_UNLOADS, 0);
+}
+
+static int icm_disconnect_pcie_paths(struct tb *tb)
+{
+ return nhi_mailbox_cmd(tb->nhi, NHI_MAILBOX_DISCONNECT_PCIE_PATHS, 0);
+}
+
+/* Falcon Ridge and Alpine Ridge */
+static const struct tb_cm_ops icm_fr_ops = {
+ .driver_ready = icm_driver_ready,
+ .start = icm_start,
+ .stop = icm_stop,
+ .suspend = icm_suspend,
+ .complete = icm_complete,
+ .handle_event = icm_handle_event,
+ .approve_switch = icm_fr_approve_switch,
+ .add_switch_key = icm_fr_add_switch_key,
+ .challenge_switch_key = icm_fr_challenge_switch_key,
+ .disconnect_pcie_paths = icm_disconnect_pcie_paths,
+};
+
+struct tb *icm_probe(struct tb_nhi *nhi)
+{
+ struct icm *icm;
+ struct tb *tb;
+
+ tb = tb_domain_alloc(nhi, sizeof(struct icm));
+ if (!tb)
+ return NULL;
+
+ icm = tb_priv(tb);
+ INIT_DELAYED_WORK(&icm->rescan_work, icm_rescan_work);
+ mutex_init(&icm->request_lock);
+
+ switch (nhi->pdev->device) {
+ case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_NHI:
+ case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_NHI:
+ icm->is_supported = icm_fr_is_supported;
+ icm->get_route = icm_fr_get_route;
+ icm->device_connected = icm_fr_device_connected;
+ icm->device_disconnected = icm_fr_device_disconnected;
+ tb->cm_ops = &icm_fr_ops;
+ break;
+
+ case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_2C_NHI:
+ case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_4C_NHI:
+ case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_LP_NHI:
+ case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_4C_NHI:
+ case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_2C_NHI:
+ icm->is_supported = icm_ar_is_supported;
+ icm->get_mode = icm_ar_get_mode;
+ icm->get_route = icm_ar_get_route;
+ icm->device_connected = icm_fr_device_connected;
+ icm->device_disconnected = icm_fr_device_disconnected;
+ tb->cm_ops = &icm_fr_ops;
+ break;
+ }
+
+ if (!icm->is_supported || !icm->is_supported(tb)) {
+ dev_dbg(&nhi->pdev->dev, "ICM not supported on this controller\n");
+ tb_domain_put(tb);
+ return NULL;
+ }
+
+ return tb;
+}
diff --git a/src/kernel/linux/v4.14/drivers/thunderbolt/nhi.c b/src/kernel/linux/v4.14/drivers/thunderbolt/nhi.c
new file mode 100644
index 0000000..af44e6e
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/thunderbolt/nhi.c
@@ -0,0 +1,945 @@
+/*
+ * 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/pm_runtime.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/pci.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+
+#include "nhi.h"
+#include "nhi_regs.h"
+#include "tb.h"
+
+#define RING_TYPE(ring) ((ring)->is_tx ? "TX ring" : "RX ring")
+
+/*
+ * Minimal number of vectors when we use MSI-X. Two for control channel
+ * Rx/Tx and the rest four are for cross domain DMA paths.
+ */
+#define MSIX_MIN_VECS 6
+#define MSIX_MAX_VECS 16
+
+#define NHI_MAILBOX_TIMEOUT 500 /* ms */
+
+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 * 4;
+ int bit = ring_interrupt_index(ring) & 31;
+ int mask = 1 << bit;
+ u32 old, new;
+
+ if (ring->irq > 0) {
+ u32 step, shift, ivr, misc;
+ void __iomem *ivr_base;
+ int index;
+
+ if (ring->is_tx)
+ index = ring->hop;
+ else
+ index = ring->hop + ring->nhi->hop_count;
+
+ /*
+ * Ask the hardware to clear interrupt status bits automatically
+ * since we already know which interrupt was triggered.
+ */
+ misc = ioread32(ring->nhi->iobase + REG_DMA_MISC);
+ if (!(misc & REG_DMA_MISC_INT_AUTO_CLEAR)) {
+ misc |= REG_DMA_MISC_INT_AUTO_CLEAR;
+ iowrite32(misc, ring->nhi->iobase + REG_DMA_MISC);
+ }
+
+ ivr_base = ring->nhi->iobase + REG_INT_VEC_ALLOC_BASE;
+ step = index / REG_INT_VEC_ALLOC_REGS * REG_INT_VEC_ALLOC_BITS;
+ shift = index % REG_INT_VEC_ALLOC_REGS * REG_INT_VEC_ALLOC_BITS;
+ ivr = ioread32(ivr_base + step);
+ ivr &= ~(REG_INT_VEC_ALLOC_MASK << shift);
+ if (active)
+ ivr |= ring->vector << shift;
+ iowrite32(ivr, ivr_base + step);
+ }
+
+ 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_iowrite_cons(struct tb_ring *ring, u16 cons)
+{
+ /*
+ * The other 16-bits in the register is read-only and writes to it
+ * are ignored by the hardware so we can save one ioread32() by
+ * filling the read-only bits with zeroes.
+ */
+ iowrite32(cons, ring_desc_base(ring) + 8);
+}
+
+static void ring_iowrite_prod(struct tb_ring *ring, u16 prod)
+{
+ /* See ring_iowrite_cons() above for explanation */
+ iowrite32(prod << 16, ring_desc_base(ring) + 8);
+}
+
+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;
+ if (ring->is_tx)
+ ring_iowrite_prod(ring, ring->head);
+ else
+ ring_iowrite_cons(ring, ring->head);
+ }
+}
+
+/**
+ * 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 irqreturn_t ring_msix(int irq, void *data)
+{
+ struct tb_ring *ring = data;
+
+ schedule_work(&ring->work);
+ return IRQ_HANDLED;
+}
+
+static int ring_request_msix(struct tb_ring *ring, bool no_suspend)
+{
+ struct tb_nhi *nhi = ring->nhi;
+ unsigned long irqflags;
+ int ret;
+
+ if (!nhi->pdev->msix_enabled)
+ return 0;
+
+ ret = ida_simple_get(&nhi->msix_ida, 0, MSIX_MAX_VECS, GFP_KERNEL);
+ if (ret < 0)
+ return ret;
+
+ ring->vector = ret;
+
+ ring->irq = pci_irq_vector(ring->nhi->pdev, ring->vector);
+ if (ring->irq < 0)
+ return ring->irq;
+
+ irqflags = no_suspend ? IRQF_NO_SUSPEND : 0;
+ return request_irq(ring->irq, ring_msix, irqflags, "thunderbolt", ring);
+}
+
+static void ring_release_msix(struct tb_ring *ring)
+{
+ if (ring->irq <= 0)
+ return;
+
+ free_irq(ring->irq, ring);
+ ida_simple_remove(&ring->nhi->msix_ida, ring->vector);
+ ring->vector = 0;
+ ring->irq = 0;
+}
+
+static struct tb_ring *ring_alloc(struct tb_nhi *nhi, u32 hop, int size,
+ bool transmit, unsigned int flags)
+{
+ 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->flags = flags;
+ ring->head = 0;
+ ring->tail = 0;
+ ring->running = false;
+
+ if (ring_request_msix(ring, flags & RING_FLAG_NO_SUSPEND))
+ goto err;
+
+ 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,
+ unsigned int flags)
+{
+ return ring_alloc(nhi, hop, size, true, flags);
+}
+
+struct tb_ring *ring_alloc_rx(struct tb_nhi *nhi, int hop, int size,
+ unsigned int flags)
+{
+ return ring_alloc(nhi, hop, size, false, flags);
+}
+
+/**
+ * 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->nhi->going_away)
+ goto err;
+ 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->nhi->going_away)
+ goto err;
+ 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_iowrite32desc(ring, 0, 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);
+ }
+
+ ring_release_msix(ring);
+
+ dma_free_coherent(&ring->nhi->pdev->dev,
+ ring->size * sizeof(*ring->descriptors),
+ ring->descriptors, ring->descriptors_dma);
+
+ ring->descriptors = NULL;
+ 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, ring_stop and ring_msix). Wait for it
+ * to finish before freeing the ring.
+ */
+ flush_work(&ring->work);
+ mutex_destroy(&ring->lock);
+ kfree(ring);
+}
+
+/**
+ * nhi_mailbox_cmd() - Send a command through NHI mailbox
+ * @nhi: Pointer to the NHI structure
+ * @cmd: Command to send
+ * @data: Data to be send with the command
+ *
+ * Sends mailbox command to the firmware running on NHI. Returns %0 in
+ * case of success and negative errno in case of failure.
+ */
+int nhi_mailbox_cmd(struct tb_nhi *nhi, enum nhi_mailbox_cmd cmd, u32 data)
+{
+ ktime_t timeout;
+ u32 val;
+
+ iowrite32(data, nhi->iobase + REG_INMAIL_DATA);
+
+ val = ioread32(nhi->iobase + REG_INMAIL_CMD);
+ val &= ~(REG_INMAIL_CMD_MASK | REG_INMAIL_ERROR);
+ val |= REG_INMAIL_OP_REQUEST | cmd;
+ iowrite32(val, nhi->iobase + REG_INMAIL_CMD);
+
+ timeout = ktime_add_ms(ktime_get(), NHI_MAILBOX_TIMEOUT);
+ do {
+ val = ioread32(nhi->iobase + REG_INMAIL_CMD);
+ if (!(val & REG_INMAIL_OP_REQUEST))
+ break;
+ usleep_range(10, 20);
+ } while (ktime_before(ktime_get(), timeout));
+
+ if (val & REG_INMAIL_OP_REQUEST)
+ return -ETIMEDOUT;
+ if (val & REG_INMAIL_ERROR)
+ return -EIO;
+
+ return 0;
+}
+
+/**
+ * nhi_mailbox_mode() - Return current firmware operation mode
+ * @nhi: Pointer to the NHI structure
+ *
+ * The function reads current firmware operation mode using NHI mailbox
+ * registers and returns it to the caller.
+ */
+enum nhi_fw_mode nhi_mailbox_mode(struct tb_nhi *nhi)
+{
+ u32 val;
+
+ val = ioread32(nhi->iobase + REG_OUTMAIL_CMD);
+ val &= REG_OUTMAIL_CMD_OPMODE_MASK;
+ val >>= REG_OUTMAIL_CMD_OPMODE_SHIFT;
+
+ return (enum nhi_fw_mode)val;
+}
+
+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 int nhi_suspend_noirq(struct device *dev)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+ struct tb *tb = pci_get_drvdata(pdev);
+
+ return tb_domain_suspend_noirq(tb);
+}
+
+static int nhi_resume_noirq(struct device *dev)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+ struct tb *tb = pci_get_drvdata(pdev);
+
+ /*
+ * Check that the device is still there. It may be that the user
+ * unplugged last device which causes the host controller to go
+ * away on PCs.
+ */
+ if (!pci_device_is_present(pdev))
+ tb->nhi->going_away = true;
+
+ return tb_domain_resume_noirq(tb);
+}
+
+static int nhi_suspend(struct device *dev)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+ struct tb *tb = pci_get_drvdata(pdev);
+
+ return tb_domain_suspend(tb);
+}
+
+static void nhi_complete(struct device *dev)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+ struct tb *tb = pci_get_drvdata(pdev);
+
+ tb_domain_complete(tb);
+}
+
+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.
+ */
+ if (!nhi->pdev->msix_enabled) {
+ devm_free_irq(&nhi->pdev->dev, nhi->pdev->irq, nhi);
+ flush_work(&nhi->interrupt_work);
+ }
+ mutex_destroy(&nhi->lock);
+ ida_destroy(&nhi->msix_ida);
+}
+
+static int nhi_init_msi(struct tb_nhi *nhi)
+{
+ struct pci_dev *pdev = nhi->pdev;
+ int res, irq, nvec;
+
+ /* In case someone left them on. */
+ nhi_disable_interrupts(nhi);
+
+ ida_init(&nhi->msix_ida);
+
+ /*
+ * The NHI has 16 MSI-X vectors or a single MSI. We first try to
+ * get all MSI-X vectors and if we succeed, each ring will have
+ * one MSI-X. If for some reason that does not work out, we
+ * fallback to a single MSI.
+ */
+ nvec = pci_alloc_irq_vectors(pdev, MSIX_MIN_VECS, MSIX_MAX_VECS,
+ PCI_IRQ_MSIX);
+ if (nvec < 0) {
+ nvec = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSI);
+ if (nvec < 0)
+ return nvec;
+
+ INIT_WORK(&nhi->interrupt_work, nhi_interrupt_work);
+
+ irq = pci_irq_vector(nhi->pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ res = devm_request_irq(&pdev->dev, irq, nhi_msi,
+ IRQF_NO_SUSPEND, "thunderbolt", nhi);
+ if (res) {
+ dev_err(&pdev->dev, "request_irq failed, aborting\n");
+ return res;
+ }
+ }
+
+ return 0;
+}
+
+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);
+ if (res) {
+ dev_err(&pdev->dev, "cannot enable PCI device, 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 && nhi->hop_count != 32)
+ dev_warn(&pdev->dev, "unexpected hop count: %d\n",
+ nhi->hop_count);
+
+ 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;
+
+ res = nhi_init_msi(nhi);
+ if (res) {
+ dev_err(&pdev->dev, "cannot enable MSI, aborting\n");
+ return res;
+ }
+
+ mutex_init(&nhi->lock);
+
+ pci_set_master(pdev);
+
+ /* magic value - clock related? */
+ iowrite32(3906250 / 10000, nhi->iobase + 0x38c00);
+
+ tb = icm_probe(nhi);
+ if (!tb)
+ tb = tb_probe(nhi);
+ if (!tb) {
+ dev_err(&nhi->pdev->dev,
+ "failed to determine connection manager, aborting\n");
+ return -ENODEV;
+ }
+
+ dev_info(&nhi->pdev->dev, "NHI initialized, starting thunderbolt\n");
+
+ res = tb_domain_add(tb);
+ if (res) {
+ /*
+ * At this point the RX/TX rings might already have been
+ * activated. Do a proper shutdown.
+ */
+ tb_domain_put(tb);
+ nhi_shutdown(nhi);
+ return -EIO;
+ }
+ pci_set_drvdata(pdev, tb);
+
+ return 0;
+}
+
+static void nhi_remove(struct pci_dev *pdev)
+{
+ struct tb *tb = pci_get_drvdata(pdev);
+ struct tb_nhi *nhi = tb->nhi;
+
+ tb_domain_remove(tb);
+ 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.
+ */
+ .thaw_noirq = nhi_resume_noirq,
+ .restore_noirq = nhi_resume_noirq,
+ .suspend = nhi_suspend,
+ .freeze = nhi_suspend,
+ .poweroff = nhi_suspend,
+ .complete = nhi_complete,
+};
+
+static struct pci_device_id nhi_ids[] = {
+ /*
+ * We have to specify class, the TB bridges use the same device and
+ * vendor (sub)id on gen 1 and gen 2 controllers.
+ */
+ {
+ .class = PCI_CLASS_SYSTEM_OTHER << 8, .class_mask = ~0,
+ .vendor = PCI_VENDOR_ID_INTEL,
+ .device = PCI_DEVICE_ID_INTEL_LIGHT_RIDGE,
+ .subvendor = 0x2222, .subdevice = 0x1111,
+ },
+ {
+ .class = PCI_CLASS_SYSTEM_OTHER << 8, .class_mask = ~0,
+ .vendor = PCI_VENDOR_ID_INTEL,
+ .device = PCI_DEVICE_ID_INTEL_CACTUS_RIDGE_4C,
+ .subvendor = 0x2222, .subdevice = 0x1111,
+ },
+ {
+ .class = PCI_CLASS_SYSTEM_OTHER << 8, .class_mask = ~0,
+ .vendor = PCI_VENDOR_ID_INTEL,
+ .device = PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_NHI,
+ .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID,
+ },
+ {
+ .class = PCI_CLASS_SYSTEM_OTHER << 8, .class_mask = ~0,
+ .vendor = PCI_VENDOR_ID_INTEL,
+ .device = PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_NHI,
+ .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID,
+ },
+
+ /* Thunderbolt 3 */
+ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_2C_NHI) },
+ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_4C_NHI) },
+ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_USBONLY_NHI) },
+ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_LP_NHI) },
+ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_LP_USBONLY_NHI) },
+ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_2C_NHI) },
+ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_4C_NHI) },
+ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_USBONLY_NHI) },
+
+ { 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,
+ .driver.pm = &nhi_pm_ops,
+};
+
+static int __init nhi_init(void)
+{
+ int ret;
+
+ ret = tb_domain_init();
+ if (ret)
+ return ret;
+ ret = pci_register_driver(&nhi_driver);
+ if (ret)
+ tb_domain_exit();
+ return ret;
+}
+
+static void __exit nhi_unload(void)
+{
+ pci_unregister_driver(&nhi_driver);
+ tb_domain_exit();
+}
+
+module_init(nhi_init);
+module_exit(nhi_unload);
diff --git a/src/kernel/linux/v4.14/drivers/thunderbolt/nhi.h b/src/kernel/linux/v4.14/drivers/thunderbolt/nhi.h
new file mode 100644
index 0000000..1c7e6b9
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/thunderbolt/nhi.h
@@ -0,0 +1,184 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Thunderbolt Cactus Ridge driver - NHI driver
+ *
+ * Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com>
+ */
+
+#ifndef DSL3510_H_
+#define DSL3510_H_
+
+#include <linux/idr.h>
+#include <linux/mutex.h>
+#include <linux/workqueue.h>
+
+/**
+ * struct tb_nhi - thunderbolt native host interface
+ * @lock: Must be held during ring creation/destruction. Is acquired by
+ * interrupt_work when dispatching interrupts to individual rings.
+ * @pdev: Pointer to the PCI device
+ * @iobase: MMIO space of the NHI
+ * @tx_rings: All Tx rings available on this host controller
+ * @rx_rings: All Rx rings available on this host controller
+ * @msix_ida: Used to allocate MSI-X vectors for rings
+ * @going_away: The host controller device is about to disappear so when
+ * this flag is set, avoid touching the hardware anymore.
+ * @interrupt_work: Work scheduled to handle ring interrupt when no
+ * MSI-X is used.
+ * @hop_count: Number of rings (end point hops) supported by NHI.
+ */
+struct tb_nhi {
+ struct mutex lock;
+ struct pci_dev *pdev;
+ void __iomem *iobase;
+ struct tb_ring **tx_rings;
+ struct tb_ring **rx_rings;
+ struct ida msix_ida;
+ bool going_away;
+ struct work_struct interrupt_work;
+ u32 hop_count;
+};
+
+/**
+ * struct tb_ring - thunderbolt TX or RX ring associated with a NHI
+ * @lock: Lock serializing actions to this ring. Must be acquired after
+ * nhi->lock.
+ * @nhi: Pointer to the native host controller interface
+ * @size: Size of the ring
+ * @hop: Hop (DMA channel) associated with this ring
+ * @head: Head of the ring (write next descriptor here)
+ * @tail: Tail of the ring (complete next descriptor here)
+ * @descriptors: Allocated descriptors for this ring
+ * @queue: Queue holding frames to be transferred over this ring
+ * @in_flight: Queue holding frames that are currently in flight
+ * @work: Interrupt work structure
+ * @is_tx: Is the ring Tx or Rx
+ * @running: Is the ring running
+ * @irq: MSI-X irq number if the ring uses MSI-X. %0 otherwise.
+ * @vector: MSI-X vector number the ring uses (only set if @irq is > 0)
+ * @flags: Ring specific flags
+ */
+struct tb_ring {
+ struct mutex lock;
+ struct tb_nhi *nhi;
+ int size;
+ int hop;
+ int head;
+ int tail;
+ 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;
+ bool running:1;
+ int irq;
+ u8 vector;
+ unsigned int flags;
+};
+
+/* Leave ring interrupt enabled on suspend */
+#define RING_FLAG_NO_SUSPEND BIT(0)
+
+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,
+ unsigned int flags);
+struct tb_ring *ring_alloc_rx(struct tb_nhi *nhi, int hop, int size,
+ unsigned int flags);
+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);
+}
+
+enum nhi_fw_mode {
+ NHI_FW_SAFE_MODE,
+ NHI_FW_AUTH_MODE,
+ NHI_FW_EP_MODE,
+ NHI_FW_CM_MODE,
+};
+
+enum nhi_mailbox_cmd {
+ NHI_MAILBOX_SAVE_DEVS = 0x05,
+ NHI_MAILBOX_DISCONNECT_PCIE_PATHS = 0x06,
+ NHI_MAILBOX_DRV_UNLOADS = 0x07,
+ NHI_MAILBOX_ALLOW_ALL_DEVS = 0x23,
+};
+
+int nhi_mailbox_cmd(struct tb_nhi *nhi, enum nhi_mailbox_cmd cmd, u32 data);
+enum nhi_fw_mode nhi_mailbox_mode(struct tb_nhi *nhi);
+
+/*
+ * PCI IDs used in this driver from Win Ridge forward. There is no
+ * need for the PCI quirk anymore as we will use ICM also on Apple
+ * hardware.
+ */
+#define PCI_DEVICE_ID_INTEL_WIN_RIDGE_2C_NHI 0x157d
+#define PCI_DEVICE_ID_INTEL_WIN_RIDGE_2C_BRIDGE 0x157e
+#define PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_LP_NHI 0x15bf
+#define PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_LP_BRIDGE 0x15c0
+#define PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_4C_NHI 0x15d2
+#define PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_4C_BRIDGE 0x15d3
+#define PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_2C_NHI 0x15d9
+#define PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_2C_BRIDGE 0x15da
+#define PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_LP_USBONLY_NHI 0x15dc
+#define PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_USBONLY_NHI 0x15dd
+#define PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_USBONLY_NHI 0x15de
+
+#endif
diff --git a/src/kernel/linux/v4.14/drivers/thunderbolt/nhi_regs.h b/src/kernel/linux/v4.14/drivers/thunderbolt/nhi_regs.h
new file mode 100644
index 0000000..752ec04
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/thunderbolt/nhi_regs.h
@@ -0,0 +1,129 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Thunderbolt driver - NHI registers
+ *
+ * Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com>
+ */
+
+#ifndef NHI_REGS_H_
+#define NHI_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)
+
+/* Interrupt Vector Allocation */
+#define REG_INT_VEC_ALLOC_BASE 0x38c40
+#define REG_INT_VEC_ALLOC_BITS 4
+#define REG_INT_VEC_ALLOC_MASK GENMASK(3, 0)
+#define REG_INT_VEC_ALLOC_REGS (32 / REG_INT_VEC_ALLOC_BITS)
+
+/* The last 11 bits contain the number of hops supported by the NHI port. */
+#define REG_HOP_COUNT 0x39640
+
+#define REG_DMA_MISC 0x39864
+#define REG_DMA_MISC_INT_AUTO_CLEAR BIT(2)
+
+#define REG_INMAIL_DATA 0x39900
+
+#define REG_INMAIL_CMD 0x39904
+#define REG_INMAIL_CMD_MASK GENMASK(7, 0)
+#define REG_INMAIL_ERROR BIT(30)
+#define REG_INMAIL_OP_REQUEST BIT(31)
+
+#define REG_OUTMAIL_CMD 0x3990c
+#define REG_OUTMAIL_CMD_OPMODE_SHIFT 8
+#define REG_OUTMAIL_CMD_OPMODE_MASK GENMASK(11, 8)
+
+#define REG_FW_STS 0x39944
+#define REG_FW_STS_NVM_AUTH_DONE BIT(31)
+#define REG_FW_STS_CIO_RESET_REQ BIT(30)
+#define REG_FW_STS_ICM_EN_CPU BIT(2)
+#define REG_FW_STS_ICM_EN_INVERT BIT(1)
+#define REG_FW_STS_ICM_EN BIT(0)
+
+#endif
diff --git a/src/kernel/linux/v4.14/drivers/thunderbolt/path.c b/src/kernel/linux/v4.14/drivers/thunderbolt/path.c
new file mode 100644
index 0000000..ff49ad8
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/thunderbolt/path.c
@@ -0,0 +1,235 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * 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 = { 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;
+ 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/src/kernel/linux/v4.14/drivers/thunderbolt/switch.c b/src/kernel/linux/v4.14/drivers/thunderbolt/switch.c
new file mode 100644
index 0000000..9cfc65c
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/thunderbolt/switch.c
@@ -0,0 +1,1571 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Thunderbolt Cactus Ridge driver - switch/port utility functions
+ *
+ * Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com>
+ */
+
+#include <linux/delay.h>
+#include <linux/idr.h>
+#include <linux/nvmem-provider.h>
+#include <linux/sizes.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+
+#include "tb.h"
+
+/* Switch authorization from userspace is serialized by this lock */
+static DEFINE_MUTEX(switch_lock);
+
+/* Switch NVM support */
+
+#define NVM_DEVID 0x05
+#define NVM_VERSION 0x08
+#define NVM_CSS 0x10
+#define NVM_FLASH_SIZE 0x45
+
+#define NVM_MIN_SIZE SZ_32K
+#define NVM_MAX_SIZE SZ_512K
+
+static DEFINE_IDA(nvm_ida);
+
+struct nvm_auth_status {
+ struct list_head list;
+ uuid_t uuid;
+ u32 status;
+};
+
+/*
+ * Hold NVM authentication failure status per switch This information
+ * needs to stay around even when the switch gets power cycled so we
+ * keep it separately.
+ */
+static LIST_HEAD(nvm_auth_status_cache);
+static DEFINE_MUTEX(nvm_auth_status_lock);
+
+static struct nvm_auth_status *__nvm_get_auth_status(const struct tb_switch *sw)
+{
+ struct nvm_auth_status *st;
+
+ list_for_each_entry(st, &nvm_auth_status_cache, list) {
+ if (uuid_equal(&st->uuid, sw->uuid))
+ return st;
+ }
+
+ return NULL;
+}
+
+static void nvm_get_auth_status(const struct tb_switch *sw, u32 *status)
+{
+ struct nvm_auth_status *st;
+
+ mutex_lock(&nvm_auth_status_lock);
+ st = __nvm_get_auth_status(sw);
+ mutex_unlock(&nvm_auth_status_lock);
+
+ *status = st ? st->status : 0;
+}
+
+static void nvm_set_auth_status(const struct tb_switch *sw, u32 status)
+{
+ struct nvm_auth_status *st;
+
+ if (WARN_ON(!sw->uuid))
+ return;
+
+ mutex_lock(&nvm_auth_status_lock);
+ st = __nvm_get_auth_status(sw);
+
+ if (!st) {
+ st = kzalloc(sizeof(*st), GFP_KERNEL);
+ if (!st)
+ goto unlock;
+
+ memcpy(&st->uuid, sw->uuid, sizeof(st->uuid));
+ INIT_LIST_HEAD(&st->list);
+ list_add_tail(&st->list, &nvm_auth_status_cache);
+ }
+
+ st->status = status;
+unlock:
+ mutex_unlock(&nvm_auth_status_lock);
+}
+
+static void nvm_clear_auth_status(const struct tb_switch *sw)
+{
+ struct nvm_auth_status *st;
+
+ mutex_lock(&nvm_auth_status_lock);
+ st = __nvm_get_auth_status(sw);
+ if (st) {
+ list_del(&st->list);
+ kfree(st);
+ }
+ mutex_unlock(&nvm_auth_status_lock);
+}
+
+static int nvm_validate_and_write(struct tb_switch *sw)
+{
+ unsigned int image_size, hdr_size;
+ const u8 *buf = sw->nvm->buf;
+ u16 ds_size;
+ int ret;
+
+ if (!buf)
+ return -EINVAL;
+
+ image_size = sw->nvm->buf_data_size;
+ if (image_size < NVM_MIN_SIZE || image_size > NVM_MAX_SIZE)
+ return -EINVAL;
+
+ /*
+ * FARB pointer must point inside the image and must at least
+ * contain parts of the digital section we will be reading here.
+ */
+ hdr_size = (*(u32 *)buf) & 0xffffff;
+ if (hdr_size + NVM_DEVID + 2 >= image_size)
+ return -EINVAL;
+
+ /* Digital section start should be aligned to 4k page */
+ if (!IS_ALIGNED(hdr_size, SZ_4K))
+ return -EINVAL;
+
+ /*
+ * Read digital section size and check that it also fits inside
+ * the image.
+ */
+ ds_size = *(u16 *)(buf + hdr_size);
+ if (ds_size >= image_size)
+ return -EINVAL;
+
+ if (!sw->safe_mode) {
+ u16 device_id;
+
+ /*
+ * Make sure the device ID in the image matches the one
+ * we read from the switch config space.
+ */
+ device_id = *(u16 *)(buf + hdr_size + NVM_DEVID);
+ if (device_id != sw->config.device_id)
+ return -EINVAL;
+
+ if (sw->generation < 3) {
+ /* Write CSS headers first */
+ ret = dma_port_flash_write(sw->dma_port,
+ DMA_PORT_CSS_ADDRESS, buf + NVM_CSS,
+ DMA_PORT_CSS_MAX_SIZE);
+ if (ret)
+ return ret;
+ }
+
+ /* Skip headers in the image */
+ buf += hdr_size;
+ image_size -= hdr_size;
+ }
+
+ return dma_port_flash_write(sw->dma_port, 0, buf, image_size);
+}
+
+static int nvm_authenticate_host(struct tb_switch *sw)
+{
+ int ret;
+
+ /*
+ * Root switch NVM upgrade requires that we disconnect the
+ * existing PCIe paths first (in case it is not in safe mode
+ * already).
+ */
+ if (!sw->safe_mode) {
+ ret = tb_domain_disconnect_pcie_paths(sw->tb);
+ if (ret)
+ return ret;
+ /*
+ * The host controller goes away pretty soon after this if
+ * everything goes well so getting timeout is expected.
+ */
+ ret = dma_port_flash_update_auth(sw->dma_port);
+ return ret == -ETIMEDOUT ? 0 : ret;
+ }
+
+ /*
+ * From safe mode we can get out by just power cycling the
+ * switch.
+ */
+ dma_port_power_cycle(sw->dma_port);
+ return 0;
+}
+
+static int nvm_authenticate_device(struct tb_switch *sw)
+{
+ int ret, retries = 10;
+
+ ret = dma_port_flash_update_auth(sw->dma_port);
+ if (ret && ret != -ETIMEDOUT)
+ return ret;
+
+ /*
+ * Poll here for the authentication status. It takes some time
+ * for the device to respond (we get timeout for a while). Once
+ * we get response the device needs to be power cycled in order
+ * to the new NVM to be taken into use.
+ */
+ do {
+ u32 status;
+
+ ret = dma_port_flash_update_auth_status(sw->dma_port, &status);
+ if (ret < 0 && ret != -ETIMEDOUT)
+ return ret;
+ if (ret > 0) {
+ if (status) {
+ tb_sw_warn(sw, "failed to authenticate NVM\n");
+ nvm_set_auth_status(sw, status);
+ }
+
+ tb_sw_info(sw, "power cycling the switch now\n");
+ dma_port_power_cycle(sw->dma_port);
+ return 0;
+ }
+
+ msleep(500);
+ } while (--retries);
+
+ return -ETIMEDOUT;
+}
+
+static int tb_switch_nvm_read(void *priv, unsigned int offset, void *val,
+ size_t bytes)
+{
+ struct tb_switch *sw = priv;
+
+ return dma_port_flash_read(sw->dma_port, offset, val, bytes);
+}
+
+static int tb_switch_nvm_no_read(void *priv, unsigned int offset, void *val,
+ size_t bytes)
+{
+ return -EPERM;
+}
+
+static int tb_switch_nvm_write(void *priv, unsigned int offset, void *val,
+ size_t bytes)
+{
+ struct tb_switch *sw = priv;
+ int ret = 0;
+
+ if (mutex_lock_interruptible(&switch_lock))
+ return -ERESTARTSYS;
+
+ /*
+ * Since writing the NVM image might require some special steps,
+ * for example when CSS headers are written, we cache the image
+ * locally here and handle the special cases when the user asks
+ * us to authenticate the image.
+ */
+ if (!sw->nvm->buf) {
+ sw->nvm->buf = vmalloc(NVM_MAX_SIZE);
+ if (!sw->nvm->buf) {
+ ret = -ENOMEM;
+ goto unlock;
+ }
+ }
+
+ sw->nvm->buf_data_size = offset + bytes;
+ memcpy(sw->nvm->buf + offset, val, bytes);
+
+unlock:
+ mutex_unlock(&switch_lock);
+
+ return ret;
+}
+
+static struct nvmem_device *register_nvmem(struct tb_switch *sw, int id,
+ size_t size, bool active)
+{
+ struct nvmem_config config;
+
+ memset(&config, 0, sizeof(config));
+
+ if (active) {
+ config.name = "nvm_active";
+ config.reg_read = tb_switch_nvm_read;
+ config.read_only = true;
+ } else {
+ config.name = "nvm_non_active";
+ config.reg_read = tb_switch_nvm_no_read;
+ config.reg_write = tb_switch_nvm_write;
+ config.root_only = true;
+ }
+
+ config.id = id;
+ config.stride = 4;
+ config.word_size = 4;
+ config.size = size;
+ config.dev = &sw->dev;
+ config.owner = THIS_MODULE;
+ config.priv = sw;
+
+ return nvmem_register(&config);
+}
+
+static int tb_switch_nvm_add(struct tb_switch *sw)
+{
+ struct nvmem_device *nvm_dev;
+ struct tb_switch_nvm *nvm;
+ u32 val;
+ int ret;
+
+ if (!sw->dma_port)
+ return 0;
+
+ nvm = kzalloc(sizeof(*nvm), GFP_KERNEL);
+ if (!nvm)
+ return -ENOMEM;
+
+ nvm->id = ida_simple_get(&nvm_ida, 0, 0, GFP_KERNEL);
+
+ /*
+ * If the switch is in safe-mode the only accessible portion of
+ * the NVM is the non-active one where userspace is expected to
+ * write new functional NVM.
+ */
+ if (!sw->safe_mode) {
+ u32 nvm_size, hdr_size;
+
+ ret = dma_port_flash_read(sw->dma_port, NVM_FLASH_SIZE, &val,
+ sizeof(val));
+ if (ret)
+ goto err_ida;
+
+ hdr_size = sw->generation < 3 ? SZ_8K : SZ_16K;
+ nvm_size = (SZ_1M << (val & 7)) / 8;
+ nvm_size = (nvm_size - hdr_size) / 2;
+
+ ret = dma_port_flash_read(sw->dma_port, NVM_VERSION, &val,
+ sizeof(val));
+ if (ret)
+ goto err_ida;
+
+ nvm->major = val >> 16;
+ nvm->minor = val >> 8;
+
+ nvm_dev = register_nvmem(sw, nvm->id, nvm_size, true);
+ if (IS_ERR(nvm_dev)) {
+ ret = PTR_ERR(nvm_dev);
+ goto err_ida;
+ }
+ nvm->active = nvm_dev;
+ }
+
+ nvm_dev = register_nvmem(sw, nvm->id, NVM_MAX_SIZE, false);
+ if (IS_ERR(nvm_dev)) {
+ ret = PTR_ERR(nvm_dev);
+ goto err_nvm_active;
+ }
+ nvm->non_active = nvm_dev;
+
+ mutex_lock(&switch_lock);
+ sw->nvm = nvm;
+ mutex_unlock(&switch_lock);
+
+ return 0;
+
+err_nvm_active:
+ if (nvm->active)
+ nvmem_unregister(nvm->active);
+err_ida:
+ ida_simple_remove(&nvm_ida, nvm->id);
+ kfree(nvm);
+
+ return ret;
+}
+
+static void tb_switch_nvm_remove(struct tb_switch *sw)
+{
+ struct tb_switch_nvm *nvm;
+
+ mutex_lock(&switch_lock);
+ nvm = sw->nvm;
+ sw->nvm = NULL;
+ mutex_unlock(&switch_lock);
+
+ if (!nvm)
+ return;
+
+ /* Remove authentication status in case the switch is unplugged */
+ if (!nvm->authenticating)
+ nvm_clear_auth_status(sw);
+
+ nvmem_unregister(nvm->non_active);
+ if (nvm->active)
+ nvmem_unregister(nvm->active);
+ ida_simple_remove(&nvm_ida, nvm->id);
+ vfree(nvm->buf);
+ kfree(nvm);
+}
+
+/* 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_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_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
+ * any downstream switches.
+ *
+ * Return: Returns 0 on success or an error code on failure.
+ */
+static int tb_init_port(struct tb_port *port)
+{
+ int res;
+ int cap;
+
+ 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->port != 0) {
+ cap = tb_port_find_cap(port, TB_PORT_CAP_PHY);
+
+ if (cap > 0)
+ port->cap_phy = cap;
+ else
+ tb_port_WARN(port, "non switch port without a PHY\n");
+ }
+
+ tb_dump_port(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);
+}
+
+/**
+ * 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; /*
+ * 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 NULL;
+ if (tb_is_upstream_port(&sw->ports[next_port]))
+ return NULL;
+ if (!sw->ports[next_port].remote)
+ return NULL;
+ 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
+ *
+ * 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;
+
+ if (!sw->config.enabled)
+ return 0;
+
+ 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 PCI_DEVICE_ID_INTEL_LIGHT_RIDGE:
+ case PCI_DEVICE_ID_INTEL_EAGLE_RIDGE:
+ case PCI_DEVICE_ID_INTEL_PORT_RIDGE:
+ break;
+ default:
+ data |= 4;
+ }
+ } else {
+ data = data | 0x7c;
+ }
+ return tb_sw_write(sw, &data, TB_CFG_SWITCH,
+ sw->cap_plug_events + 1, 1);
+}
+
+static ssize_t authorized_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct tb_switch *sw = tb_to_switch(dev);
+
+ return sprintf(buf, "%u\n", sw->authorized);
+}
+
+static int tb_switch_set_authorized(struct tb_switch *sw, unsigned int val)
+{
+ int ret = -EINVAL;
+
+ if (mutex_lock_interruptible(&switch_lock))
+ return -ERESTARTSYS;
+
+ if (sw->authorized)
+ goto unlock;
+
+ /*
+ * Make sure there is no PCIe rescan ongoing when a new PCIe
+ * tunnel is created. Otherwise the PCIe rescan code might find
+ * the new tunnel too early.
+ */
+ pci_lock_rescan_remove();
+
+ switch (val) {
+ /* Approve switch */
+ case 1:
+ if (sw->key)
+ ret = tb_domain_approve_switch_key(sw->tb, sw);
+ else
+ ret = tb_domain_approve_switch(sw->tb, sw);
+ break;
+
+ /* Challenge switch */
+ case 2:
+ if (sw->key)
+ ret = tb_domain_challenge_switch_key(sw->tb, sw);
+ break;
+
+ default:
+ break;
+ }
+
+ pci_unlock_rescan_remove();
+
+ if (!ret) {
+ sw->authorized = val;
+ /* Notify status change to the userspace */
+ kobject_uevent(&sw->dev.kobj, KOBJ_CHANGE);
+ }
+
+unlock:
+ mutex_unlock(&switch_lock);
+ return ret;
+}
+
+static ssize_t authorized_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct tb_switch *sw = tb_to_switch(dev);
+ unsigned int val;
+ ssize_t ret;
+
+ ret = kstrtouint(buf, 0, &val);
+ if (ret)
+ return ret;
+ if (val > 2)
+ return -EINVAL;
+
+ ret = tb_switch_set_authorized(sw, val);
+
+ return ret ? ret : count;
+}
+static DEVICE_ATTR_RW(authorized);
+
+static ssize_t device_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct tb_switch *sw = tb_to_switch(dev);
+
+ return sprintf(buf, "%#x\n", sw->device);
+}
+static DEVICE_ATTR_RO(device);
+
+static ssize_t
+device_name_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct tb_switch *sw = tb_to_switch(dev);
+
+ return sprintf(buf, "%s\n", sw->device_name ? sw->device_name : "");
+}
+static DEVICE_ATTR_RO(device_name);
+
+static ssize_t key_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct tb_switch *sw = tb_to_switch(dev);
+ ssize_t ret;
+
+ if (mutex_lock_interruptible(&switch_lock))
+ return -ERESTARTSYS;
+
+ if (sw->key)
+ ret = sprintf(buf, "%*phN\n", TB_SWITCH_KEY_SIZE, sw->key);
+ else
+ ret = sprintf(buf, "\n");
+
+ mutex_unlock(&switch_lock);
+ return ret;
+}
+
+static ssize_t key_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct tb_switch *sw = tb_to_switch(dev);
+ u8 key[TB_SWITCH_KEY_SIZE];
+ ssize_t ret = count;
+ bool clear = false;
+
+ if (!strcmp(buf, "\n"))
+ clear = true;
+ else if (hex2bin(key, buf, sizeof(key)))
+ return -EINVAL;
+
+ if (mutex_lock_interruptible(&switch_lock))
+ return -ERESTARTSYS;
+
+ if (sw->authorized) {
+ ret = -EBUSY;
+ } else {
+ kfree(sw->key);
+ if (clear) {
+ sw->key = NULL;
+ } else {
+ sw->key = kmemdup(key, sizeof(key), GFP_KERNEL);
+ if (!sw->key)
+ ret = -ENOMEM;
+ }
+ }
+
+ mutex_unlock(&switch_lock);
+ return ret;
+}
+static DEVICE_ATTR(key, 0600, key_show, key_store);
+
+static ssize_t nvm_authenticate_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct tb_switch *sw = tb_to_switch(dev);
+ u32 status;
+
+ nvm_get_auth_status(sw, &status);
+ return sprintf(buf, "%#x\n", status);
+}
+
+static ssize_t nvm_authenticate_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct tb_switch *sw = tb_to_switch(dev);
+ bool val;
+ int ret;
+
+ if (mutex_lock_interruptible(&switch_lock))
+ return -ERESTARTSYS;
+
+ /* If NVMem devices are not yet added */
+ if (!sw->nvm) {
+ ret = -EAGAIN;
+ goto exit_unlock;
+ }
+
+ ret = kstrtobool(buf, &val);
+ if (ret)
+ goto exit_unlock;
+
+ /* Always clear the authentication status */
+ nvm_clear_auth_status(sw);
+
+ if (val) {
+ ret = nvm_validate_and_write(sw);
+ if (ret)
+ goto exit_unlock;
+
+ sw->nvm->authenticating = true;
+
+ if (!tb_route(sw))
+ ret = nvm_authenticate_host(sw);
+ else
+ ret = nvm_authenticate_device(sw);
+ }
+
+exit_unlock:
+ mutex_unlock(&switch_lock);
+
+ if (ret)
+ return ret;
+ return count;
+}
+static DEVICE_ATTR_RW(nvm_authenticate);
+
+static ssize_t nvm_version_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct tb_switch *sw = tb_to_switch(dev);
+ int ret;
+
+ if (mutex_lock_interruptible(&switch_lock))
+ return -ERESTARTSYS;
+
+ if (sw->safe_mode)
+ ret = -ENODATA;
+ else if (!sw->nvm)
+ ret = -EAGAIN;
+ else
+ ret = sprintf(buf, "%x.%x\n", sw->nvm->major, sw->nvm->minor);
+
+ mutex_unlock(&switch_lock);
+
+ return ret;
+}
+static DEVICE_ATTR_RO(nvm_version);
+
+static ssize_t vendor_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct tb_switch *sw = tb_to_switch(dev);
+
+ return sprintf(buf, "%#x\n", sw->vendor);
+}
+static DEVICE_ATTR_RO(vendor);
+
+static ssize_t
+vendor_name_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct tb_switch *sw = tb_to_switch(dev);
+
+ return sprintf(buf, "%s\n", sw->vendor_name ? sw->vendor_name : "");
+}
+static DEVICE_ATTR_RO(vendor_name);
+
+static ssize_t unique_id_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct tb_switch *sw = tb_to_switch(dev);
+
+ return sprintf(buf, "%pUb\n", sw->uuid);
+}
+static DEVICE_ATTR_RO(unique_id);
+
+static struct attribute *switch_attrs[] = {
+ &dev_attr_authorized.attr,
+ &dev_attr_device.attr,
+ &dev_attr_device_name.attr,
+ &dev_attr_key.attr,
+ &dev_attr_nvm_authenticate.attr,
+ &dev_attr_nvm_version.attr,
+ &dev_attr_vendor.attr,
+ &dev_attr_vendor_name.attr,
+ &dev_attr_unique_id.attr,
+ NULL,
+};
+
+static umode_t switch_attr_is_visible(struct kobject *kobj,
+ struct attribute *attr, int n)
+{
+ struct device *dev = container_of(kobj, struct device, kobj);
+ struct tb_switch *sw = tb_to_switch(dev);
+
+ if (attr == &dev_attr_key.attr) {
+ if (tb_route(sw) &&
+ sw->tb->security_level == TB_SECURITY_SECURE &&
+ sw->security_level == TB_SECURITY_SECURE)
+ return attr->mode;
+ return 0;
+ } else if (attr == &dev_attr_nvm_authenticate.attr ||
+ attr == &dev_attr_nvm_version.attr) {
+ if (sw->dma_port)
+ return attr->mode;
+ return 0;
+ }
+
+ return sw->safe_mode ? 0 : attr->mode;
+}
+
+static struct attribute_group switch_group = {
+ .is_visible = switch_attr_is_visible,
+ .attrs = switch_attrs,
+};
+
+static const struct attribute_group *switch_groups[] = {
+ &switch_group,
+ NULL,
+};
+
+static void tb_switch_release(struct device *dev)
+{
+ struct tb_switch *sw = tb_to_switch(dev);
+
+ dma_port_free(sw->dma_port);
+
+ kfree(sw->uuid);
+ kfree(sw->device_name);
+ kfree(sw->vendor_name);
+ kfree(sw->ports);
+ kfree(sw->drom);
+ kfree(sw->key);
+ kfree(sw);
+}
+
+struct device_type tb_switch_type = {
+ .name = "thunderbolt_device",
+ .release = tb_switch_release,
+};
+
+static int tb_switch_get_generation(struct tb_switch *sw)
+{
+ switch (sw->config.device_id) {
+ case PCI_DEVICE_ID_INTEL_LIGHT_RIDGE:
+ case PCI_DEVICE_ID_INTEL_EAGLE_RIDGE:
+ case PCI_DEVICE_ID_INTEL_LIGHT_PEAK:
+ case PCI_DEVICE_ID_INTEL_CACTUS_RIDGE_2C:
+ case PCI_DEVICE_ID_INTEL_CACTUS_RIDGE_4C:
+ case PCI_DEVICE_ID_INTEL_PORT_RIDGE:
+ case PCI_DEVICE_ID_INTEL_REDWOOD_RIDGE_2C_BRIDGE:
+ case PCI_DEVICE_ID_INTEL_REDWOOD_RIDGE_4C_BRIDGE:
+ return 1;
+
+ case PCI_DEVICE_ID_INTEL_WIN_RIDGE_2C_BRIDGE:
+ case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_BRIDGE:
+ case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_BRIDGE:
+ return 2;
+
+ case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_LP_BRIDGE:
+ case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_2C_BRIDGE:
+ case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_4C_BRIDGE:
+ case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_2C_BRIDGE:
+ case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_4C_BRIDGE:
+ return 3;
+
+ default:
+ /*
+ * For unknown switches assume generation to be 1 to be
+ * on the safe side.
+ */
+ tb_sw_warn(sw, "unsupported switch device id %#x\n",
+ sw->config.device_id);
+ return 1;
+ }
+}
+
+/**
+ * tb_switch_alloc() - allocate a switch
+ * @tb: Pointer to the owning domain
+ * @parent: Parent device for this switch
+ * @route: Route string for this switch
+ *
+ * Allocates and initializes a switch. Will not upload configuration to
+ * the switch. For that you need to call tb_switch_configure()
+ * separately. The returned switch should be released by calling
+ * tb_switch_put().
+ *
+ * Return: Pointer to the allocated switch or %NULL in case of failure
+ */
+struct tb_switch *tb_switch_alloc(struct tb *tb, struct device *parent,
+ 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)
+ 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, TB_CFG_SWITCH, 0, 5))
+ goto err_free_sw_ports;
+
+ tb_info(tb, "current 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 = 0;
+
+ /* initialize ports */
+ sw->ports = kcalloc(sw->config.max_port_number + 1, sizeof(*sw->ports),
+ GFP_KERNEL);
+ if (!sw->ports)
+ goto err_free_sw_ports;
+
+ for (i = 0; i <= sw->config.max_port_number; i++) {
+ /* minimum setup for tb_find_cap and tb_drom_read to work */
+ sw->ports[i].sw = sw;
+ sw->ports[i].port = i;
+ }
+
+ sw->generation = tb_switch_get_generation(sw);
+
+ cap = tb_switch_find_vse_cap(sw, TB_VSE_CAP_PLUG_EVENTS);
+ if (cap < 0) {
+ tb_sw_warn(sw, "cannot find TB_VSE_CAP_PLUG_EVENTS aborting\n");
+ goto err_free_sw_ports;
+ }
+ sw->cap_plug_events = cap;
+
+ /* Root switch is always authorized */
+ if (!route)
+ sw->authorized = true;
+
+ device_initialize(&sw->dev);
+ sw->dev.parent = parent;
+ sw->dev.bus = &tb_bus_type;
+ sw->dev.type = &tb_switch_type;
+ sw->dev.groups = switch_groups;
+ dev_set_name(&sw->dev, "%u-%llx", tb->index, tb_route(sw));
+
+ return sw;
+
+err_free_sw_ports:
+ kfree(sw->ports);
+ kfree(sw);
+
+ return NULL;
+}
+
+/**
+ * tb_switch_alloc_safe_mode() - allocate a switch that is in safe mode
+ * @tb: Pointer to the owning domain
+ * @parent: Parent device for this switch
+ * @route: Route string for this switch
+ *
+ * This creates a switch in safe mode. This means the switch pretty much
+ * lacks all capabilities except DMA configuration port before it is
+ * flashed with a valid NVM firmware.
+ *
+ * The returned switch must be released by calling tb_switch_put().
+ *
+ * Return: Pointer to the allocated switch or %NULL in case of failure
+ */
+struct tb_switch *
+tb_switch_alloc_safe_mode(struct tb *tb, struct device *parent, u64 route)
+{
+ struct tb_switch *sw;
+
+ sw = kzalloc(sizeof(*sw), GFP_KERNEL);
+ if (!sw)
+ return NULL;
+
+ sw->tb = tb;
+ sw->config.depth = tb_route_length(route);
+ sw->config.route_hi = upper_32_bits(route);
+ sw->config.route_lo = lower_32_bits(route);
+ sw->safe_mode = true;
+
+ device_initialize(&sw->dev);
+ sw->dev.parent = parent;
+ sw->dev.bus = &tb_bus_type;
+ sw->dev.type = &tb_switch_type;
+ sw->dev.groups = switch_groups;
+ dev_set_name(&sw->dev, "%u-%llx", tb->index, tb_route(sw));
+
+ return sw;
+}
+
+/**
+ * tb_switch_configure() - Uploads configuration to the switch
+ * @sw: Switch to configure
+ *
+ * Call this function before the switch is added to the system. It will
+ * upload configuration to the switch and makes it available for the
+ * connection manager to use.
+ *
+ * Return: %0 in case of success and negative errno in case of failure
+ */
+int tb_switch_configure(struct tb_switch *sw)
+{
+ struct tb *tb = sw->tb;
+ u64 route;
+ int ret;
+
+ route = tb_route(sw);
+ tb_info(tb,
+ "initializing Switch at %#llx (depth: %d, up port: %d)\n",
+ route, tb_route_length(route), sw->config.upstream_port_number);
+
+ if (sw->config.vendor_id != PCI_VENDOR_ID_INTEL)
+ tb_sw_warn(sw, "unknown switch vendor id %#x\n",
+ sw->config.vendor_id);
+
+ sw->config.enabled = 1;
+
+ /* upload configuration */
+ ret = tb_sw_write(sw, 1 + (u32 *)&sw->config, TB_CFG_SWITCH, 1, 3);
+ if (ret)
+ return ret;
+
+ return tb_plug_events_active(sw, true);
+}
+
+static int tb_switch_set_uuid(struct tb_switch *sw)
+{
+ u32 uuid[4];
+ int cap, ret;
+
+ ret = 0;
+ if (sw->uuid)
+ return ret;
+
+ /*
+ * The newer controllers include fused UUID as part of link
+ * controller specific registers
+ */
+ cap = tb_switch_find_vse_cap(sw, TB_VSE_CAP_LINK_CONTROLLER);
+ if (cap > 0) {
+ ret = tb_sw_read(sw, uuid, TB_CFG_SWITCH, cap + 3, 4);
+ if (ret)
+ return ret;
+ } else {
+ /*
+ * ICM generates UUID based on UID and fills the upper
+ * two words with ones. This is not strictly following
+ * UUID format but we want to be compatible with it so
+ * we do the same here.
+ */
+ uuid[0] = sw->uid & 0xffffffff;
+ uuid[1] = (sw->uid >> 32) & 0xffffffff;
+ uuid[2] = 0xffffffff;
+ uuid[3] = 0xffffffff;
+ }
+
+ sw->uuid = kmemdup(uuid, sizeof(uuid), GFP_KERNEL);
+ if (!sw->uuid)
+ ret = -ENOMEM;
+ return ret;
+}
+
+static int tb_switch_add_dma_port(struct tb_switch *sw)
+{
+ u32 status;
+ int ret;
+
+ switch (sw->generation) {
+ case 3:
+ break;
+
+ case 2:
+ /* Only root switch can be upgraded */
+ if (tb_route(sw))
+ return 0;
+ break;
+
+ default:
+ /*
+ * DMA port is the only thing available when the switch
+ * is in safe mode.
+ */
+ if (!sw->safe_mode)
+ return 0;
+ break;
+ }
+
+ if (sw->no_nvm_upgrade)
+ return 0;
+
+ sw->dma_port = dma_port_alloc(sw);
+ if (!sw->dma_port)
+ return 0;
+
+ /*
+ * Check status of the previous flash authentication. If there
+ * is one we need to power cycle the switch in any case to make
+ * it functional again.
+ */
+ ret = dma_port_flash_update_auth_status(sw->dma_port, &status);
+ if (ret <= 0)
+ return ret;
+
+ if (status) {
+ tb_sw_info(sw, "switch flash authentication failed\n");
+ ret = tb_switch_set_uuid(sw);
+ if (ret)
+ return ret;
+ nvm_set_auth_status(sw, status);
+ }
+
+ tb_sw_info(sw, "power cycling the switch now\n");
+ dma_port_power_cycle(sw->dma_port);
+
+ /*
+ * We return error here which causes the switch adding failure.
+ * It should appear back after power cycle is complete.
+ */
+ return -ESHUTDOWN;
+}
+
+/**
+ * tb_switch_add() - Add a switch to the domain
+ * @sw: Switch to add
+ *
+ * This is the last step in adding switch to the domain. It will read
+ * identification information from DROM and initializes ports so that
+ * they can be used to connect other switches. The switch will be
+ * exposed to the userspace when this function successfully returns. To
+ * remove and release the switch, call tb_switch_remove().
+ *
+ * Return: %0 in case of success and negative errno in case of failure
+ */
+int tb_switch_add(struct tb_switch *sw)
+{
+ int i, ret;
+
+ /*
+ * Initialize DMA control port now before we read DROM. Recent
+ * host controllers have more complete DROM on NVM that includes
+ * vendor and model identification strings which we then expose
+ * to the userspace. NVM can be accessed through DMA
+ * configuration based mailbox.
+ */
+ ret = tb_switch_add_dma_port(sw);
+ if (ret)
+ return ret;
+
+ if (!sw->safe_mode) {
+ /* read drom */
+ ret = tb_drom_read(sw);
+ if (ret) {
+ tb_sw_warn(sw, "tb_eeprom_read_rom failed\n");
+ return ret;
+ }
+ tb_sw_info(sw, "uid: %#llx\n", sw->uid);
+
+ ret = tb_switch_set_uuid(sw);
+ if (ret)
+ return ret;
+
+ 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;
+ }
+ ret = tb_init_port(&sw->ports[i]);
+ if (ret)
+ return ret;
+ }
+ }
+
+ ret = device_add(&sw->dev);
+ if (ret)
+ return ret;
+
+ ret = tb_switch_nvm_add(sw);
+ if (ret)
+ device_del(&sw->dev);
+
+ return ret;
+}
+
+/**
+ * tb_switch_remove() - Remove and release a switch
+ * @sw: Switch to remove
+ *
+ * This will remove the switch from the domain and release it after last
+ * reference count drops to zero. If there are switches connected below
+ * this switch, they will be removed as well.
+ */
+void tb_switch_remove(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_remove(sw->ports[i].remote->sw);
+ sw->ports[i].remote = NULL;
+ }
+
+ if (!sw->is_unplugged)
+ tb_plug_events_active(sw, false);
+
+ tb_switch_nvm_remove(sw);
+ device_unregister(&sw->dev);
+}
+
+/**
+ * tb_sw_set_unplugged() - set is_unplugged on switch and downstream switches
+ */
+void tb_sw_set_unplugged(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_unplugged(sw->ports[i].remote->sw);
+ }
+}
+
+int tb_switch_resume(struct tb_switch *sw)
+{
+ int i, err;
+ tb_sw_info(sw, "resuming switch\n");
+
+ /*
+ * Check for UID of the connected switches except for root
+ * switch which we assume cannot be removed.
+ */
+ if (tb_route(sw)) {
+ u64 uid;
+
+ err = tb_drom_read_uid_only(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_unplugged(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?
+ */
+}
+
+struct tb_sw_lookup {
+ struct tb *tb;
+ u8 link;
+ u8 depth;
+ const uuid_t *uuid;
+};
+
+static int tb_switch_match(struct device *dev, void *data)
+{
+ struct tb_switch *sw = tb_to_switch(dev);
+ struct tb_sw_lookup *lookup = data;
+
+ if (!sw)
+ return 0;
+ if (sw->tb != lookup->tb)
+ return 0;
+
+ if (lookup->uuid)
+ return !memcmp(sw->uuid, lookup->uuid, sizeof(*lookup->uuid));
+
+ /* Root switch is matched only by depth */
+ if (!lookup->depth)
+ return !sw->depth;
+
+ return sw->link == lookup->link && sw->depth == lookup->depth;
+}
+
+/**
+ * tb_switch_find_by_link_depth() - Find switch by link and depth
+ * @tb: Domain the switch belongs
+ * @link: Link number the switch is connected
+ * @depth: Depth of the switch in link
+ *
+ * Returned switch has reference count increased so the caller needs to
+ * call tb_switch_put() when done with the switch.
+ */
+struct tb_switch *tb_switch_find_by_link_depth(struct tb *tb, u8 link, u8 depth)
+{
+ struct tb_sw_lookup lookup;
+ struct device *dev;
+
+ memset(&lookup, 0, sizeof(lookup));
+ lookup.tb = tb;
+ lookup.link = link;
+ lookup.depth = depth;
+
+ dev = bus_find_device(&tb_bus_type, NULL, &lookup, tb_switch_match);
+ if (dev)
+ return tb_to_switch(dev);
+
+ return NULL;
+}
+
+/**
+ * tb_switch_find_by_link_depth() - Find switch by UUID
+ * @tb: Domain the switch belongs
+ * @uuid: UUID to look for
+ *
+ * Returned switch has reference count increased so the caller needs to
+ * call tb_switch_put() when done with the switch.
+ */
+struct tb_switch *tb_switch_find_by_uuid(struct tb *tb, const uuid_t *uuid)
+{
+ struct tb_sw_lookup lookup;
+ struct device *dev;
+
+ memset(&lookup, 0, sizeof(lookup));
+ lookup.tb = tb;
+ lookup.uuid = uuid;
+
+ dev = bus_find_device(&tb_bus_type, NULL, &lookup, tb_switch_match);
+ if (dev)
+ return tb_to_switch(dev);
+
+ return NULL;
+}
+
+void tb_switch_exit(void)
+{
+ ida_destroy(&nvm_ida);
+}
diff --git a/src/kernel/linux/v4.14/drivers/thunderbolt/tb.c b/src/kernel/linux/v4.14/drivers/thunderbolt/tb.c
new file mode 100644
index 0000000..1424581
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/thunderbolt/tb.c
@@ -0,0 +1,472 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * 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 <linux/platform_data/x86/apple.h>
+
+#include "tb.h"
+#include "tb_regs.h"
+#include "tunnel_pci.h"
+
+/**
+ * struct tb_cm - Simple Thunderbolt connection manager
+ * @tunnel_list: List of active tunnels
+ * @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_cm {
+ struct list_head tunnel_list;
+ bool hotplug_active;
+};
+
+/* 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 (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) {
+ tb_port_WARN(port, "port already has a remote!\n");
+ return;
+ }
+ sw = tb_switch_alloc(port->sw->tb, &port->sw->dev,
+ tb_downstream_route(port));
+ if (!sw)
+ return;
+
+ if (tb_switch_configure(sw)) {
+ tb_switch_put(sw);
+ return;
+ }
+
+ sw->authorized = true;
+
+ if (tb_switch_add(sw)) {
+ tb_switch_put(sw);
+ return;
+ }
+
+ port->remote = tb_upstream_port(sw);
+ tb_upstream_port(sw)->remote = 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_cm *tcm = tb_priv(tb);
+ struct tb_pci_tunnel *tunnel;
+ struct tb_pci_tunnel *n;
+
+ list_for_each_entry_safe(tunnel, n, &tcm->tunnel_list, list) {
+ if (tb_pci_is_invalid(tunnel)) {
+ tb_pci_deactivate(tunnel);
+ list_del(&tunnel->list);
+ tb_pci_free(tunnel);
+ }
+ }
+}
+
+/**
+ * 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_remove(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)
+{
+ 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_port_find_cap(&sw->ports[i], TB_PORT_CAP_ADAP);
+ 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;
+ struct tb_cm *tcm = tb_priv(tb);
+
+ /* 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_port_find_cap(up_port, TB_PORT_CAP_ADAP);
+ 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);
+ continue;
+ }
+
+ list_add(&tunnel->list, &tcm->tunnel_list);
+ }
+}
+
+/* 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;
+ struct tb_cm *tcm = tb_priv(tb);
+ struct tb_switch *sw;
+ struct tb_port *port;
+ mutex_lock(&tb->lock);
+ if (!tcm->hotplug_active)
+ goto out; /* during init, suspend or shutdown */
+
+ 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_unplugged(port->remote->sw);
+ tb_free_invalid_tunnels(tb);
+ tb_switch_remove(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");
+ } else {
+ tb_sw_info(port->remote->sw,
+ "hotplug: activating pcie devices\n");
+ tb_activate_pcie_devices(tb);
+ }
+ }
+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_handle_event(struct tb *tb, enum tb_cfg_pkg_type type,
+ const void *buf, size_t size)
+{
+ const struct cfg_event_pkg *pkg = buf;
+ struct tb_hotplug_event *ev;
+ u64 route;
+
+ if (type != TB_CFG_PKG_EVENT) {
+ tb_warn(tb, "unexpected event %#x, ignoring\n", type);
+ return;
+ }
+
+ route = tb_cfg_get_route(&pkg->header);
+
+ if (tb_cfg_error(tb->ctl, route, pkg->port,
+ TB_CFG_ERROR_ACK_PLUG_EVENT)) {
+ tb_warn(tb, "could not ack plug event on %llx:%x\n", route,
+ pkg->port);
+ }
+
+ ev = kmalloc(sizeof(*ev), GFP_KERNEL);
+ if (!ev)
+ return;
+ INIT_WORK(&ev->work, tb_handle_hotplug);
+ ev->tb = tb;
+ ev->route = route;
+ ev->port = pkg->port;
+ ev->unplug = pkg->unplug;
+ queue_work(tb->wq, &ev->work);
+}
+
+static void tb_stop(struct tb *tb)
+{
+ struct tb_cm *tcm = tb_priv(tb);
+ struct tb_pci_tunnel *tunnel;
+ struct tb_pci_tunnel *n;
+
+ /* tunnels are only present after everything has been initialized */
+ list_for_each_entry_safe(tunnel, n, &tcm->tunnel_list, list) {
+ tb_pci_deactivate(tunnel);
+ tb_pci_free(tunnel);
+ }
+ tb_switch_remove(tb->root_switch);
+ tcm->hotplug_active = false; /* signal tb_handle_hotplug to quit */
+}
+
+static int tb_start(struct tb *tb)
+{
+ struct tb_cm *tcm = tb_priv(tb);
+ int ret;
+
+ tb->root_switch = tb_switch_alloc(tb, &tb->dev, 0);
+ if (!tb->root_switch)
+ return -ENOMEM;
+
+ /*
+ * ICM firmware upgrade needs running firmware and in native
+ * mode that is not available so disable firmware upgrade of the
+ * root switch.
+ */
+ tb->root_switch->no_nvm_upgrade = true;
+
+ ret = tb_switch_configure(tb->root_switch);
+ if (ret) {
+ tb_switch_put(tb->root_switch);
+ return ret;
+ }
+
+ /* Announce the switch to the world */
+ ret = tb_switch_add(tb->root_switch);
+ if (ret) {
+ tb_switch_put(tb->root_switch);
+ return ret;
+ }
+
+ /* 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 */
+ tcm->hotplug_active = true;
+ return 0;
+}
+
+static int tb_suspend_noirq(struct tb *tb)
+{
+ struct tb_cm *tcm = tb_priv(tb);
+
+ tb_info(tb, "suspending...\n");
+ tb_switch_suspend(tb->root_switch);
+ tcm->hotplug_active = false; /* signal tb_handle_hotplug to quit */
+ tb_info(tb, "suspend finished\n");
+
+ return 0;
+}
+
+static int tb_resume_noirq(struct tb *tb)
+{
+ struct tb_cm *tcm = tb_priv(tb);
+ struct tb_pci_tunnel *tunnel, *n;
+
+ tb_info(tb, "resuming...\n");
+
+ /* 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, &tcm->tunnel_list, list)
+ tb_pci_restart(tunnel);
+ if (!list_empty(&tcm->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 */
+ tcm->hotplug_active = true;
+ tb_info(tb, "resume finished\n");
+
+ return 0;
+}
+
+static const struct tb_cm_ops tb_cm_ops = {
+ .start = tb_start,
+ .stop = tb_stop,
+ .suspend_noirq = tb_suspend_noirq,
+ .resume_noirq = tb_resume_noirq,
+ .handle_event = tb_handle_event,
+};
+
+struct tb *tb_probe(struct tb_nhi *nhi)
+{
+ struct tb_cm *tcm;
+ struct tb *tb;
+
+ if (!x86_apple_machine)
+ return NULL;
+
+ tb = tb_domain_alloc(nhi, sizeof(*tcm));
+ if (!tb)
+ return NULL;
+
+ tb->security_level = TB_SECURITY_NONE;
+ tb->cm_ops = &tb_cm_ops;
+
+ tcm = tb_priv(tb);
+ INIT_LIST_HEAD(&tcm->tunnel_list);
+
+ return tb;
+}
diff --git a/src/kernel/linux/v4.14/drivers/thunderbolt/tb.h b/src/kernel/linux/v4.14/drivers/thunderbolt/tb.h
new file mode 100644
index 0000000..d7ffc15
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/thunderbolt/tb.h
@@ -0,0 +1,475 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Thunderbolt Cactus Ridge driver - bus logic (NHI independent)
+ *
+ * Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com>
+ */
+
+#ifndef TB_H_
+#define TB_H_
+
+#include <linux/nvmem-provider.h>
+#include <linux/pci.h>
+#include <linux/uuid.h>
+
+#include "tb_regs.h"
+#include "ctl.h"
+#include "dma_port.h"
+
+/**
+ * struct tb_switch_nvm - Structure holding switch NVM information
+ * @major: Major version number of the active NVM portion
+ * @minor: Minor version number of the active NVM portion
+ * @id: Identifier used with both NVM portions
+ * @active: Active portion NVMem device
+ * @non_active: Non-active portion NVMem device
+ * @buf: Buffer where the NVM image is stored before it is written to
+ * the actual NVM flash device
+ * @buf_data_size: Number of bytes actually consumed by the new NVM
+ * image
+ * @authenticating: The switch is authenticating the new NVM
+ */
+struct tb_switch_nvm {
+ u8 major;
+ u8 minor;
+ int id;
+ struct nvmem_device *active;
+ struct nvmem_device *non_active;
+ void *buf;
+ size_t buf_data_size;
+ bool authenticating;
+};
+
+/**
+ * enum tb_security_level - Thunderbolt security level
+ * @TB_SECURITY_NONE: No security, legacy mode
+ * @TB_SECURITY_USER: User approval required at minimum
+ * @TB_SECURITY_SECURE: One time saved key required at minimum
+ * @TB_SECURITY_DPONLY: Only tunnel Display port (and USB)
+ */
+enum tb_security_level {
+ TB_SECURITY_NONE,
+ TB_SECURITY_USER,
+ TB_SECURITY_SECURE,
+ TB_SECURITY_DPONLY,
+};
+
+#define TB_SWITCH_KEY_SIZE 32
+/* Each physical port contains 2 links on modern controllers */
+#define TB_SWITCH_LINKS_PER_PHY_PORT 2
+
+/**
+ * struct tb_switch - a thunderbolt switch
+ * @dev: Device for the switch
+ * @config: Switch configuration
+ * @ports: Ports in this switch
+ * @dma_port: If the switch has port supporting DMA configuration based
+ * mailbox this will hold the pointer to that (%NULL
+ * otherwise). If set it also means the switch has
+ * upgradeable NVM.
+ * @tb: Pointer to the domain the switch belongs to
+ * @uid: Unique ID of the switch
+ * @uuid: UUID of the switch (or %NULL if not supported)
+ * @vendor: Vendor ID of the switch
+ * @device: Device ID of the switch
+ * @vendor_name: Name of the vendor (or %NULL if not known)
+ * @device_name: Name of the device (or %NULL if not known)
+ * @generation: Switch Thunderbolt generation
+ * @cap_plug_events: Offset to the plug events capability (%0 if not found)
+ * @is_unplugged: The switch is going away
+ * @drom: DROM of the switch (%NULL if not found)
+ * @nvm: Pointer to the NVM if the switch has one (%NULL otherwise)
+ * @no_nvm_upgrade: Prevent NVM upgrade of this switch
+ * @safe_mode: The switch is in safe-mode
+ * @authorized: Whether the switch is authorized by user or policy
+ * @work: Work used to automatically authorize a switch
+ * @security_level: Switch supported security level
+ * @key: Contains the key used to challenge the device or %NULL if not
+ * supported. Size of the key is %TB_SWITCH_KEY_SIZE.
+ * @connection_id: Connection ID used with ICM messaging
+ * @connection_key: Connection key used with ICM messaging
+ * @link: Root switch link this switch is connected (ICM only)
+ * @depth: Depth in the chain this switch is connected (ICM only)
+ *
+ * When the switch is being added or removed to the domain (other
+ * switches) you need to have domain lock held. For switch authorization
+ * internal switch_lock is enough.
+ */
+struct tb_switch {
+ struct device dev;
+ struct tb_regs_switch_header config;
+ struct tb_port *ports;
+ struct tb_dma_port *dma_port;
+ struct tb *tb;
+ u64 uid;
+ uuid_t *uuid;
+ u16 vendor;
+ u16 device;
+ const char *vendor_name;
+ const char *device_name;
+ unsigned int generation;
+ int cap_plug_events;
+ bool is_unplugged;
+ u8 *drom;
+ struct tb_switch_nvm *nvm;
+ bool no_nvm_upgrade;
+ bool safe_mode;
+ unsigned int authorized;
+ struct work_struct work;
+ enum tb_security_level security_level;
+ u8 *key;
+ u8 connection_id;
+ u8 connection_key;
+ u8 link;
+ u8 depth;
+};
+
+/**
+ * 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 */
+ 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;
+};
+
+/**
+ * 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_cm_ops - Connection manager specific operations vector
+ * @driver_ready: Called right after control channel is started. Used by
+ * ICM to send driver ready message to the firmware.
+ * @start: Starts the domain
+ * @stop: Stops the domain
+ * @suspend_noirq: Connection manager specific suspend_noirq
+ * @resume_noirq: Connection manager specific resume_noirq
+ * @suspend: Connection manager specific suspend
+ * @complete: Connection manager specific complete
+ * @handle_event: Handle thunderbolt event
+ * @approve_switch: Approve switch
+ * @add_switch_key: Add key to switch
+ * @challenge_switch_key: Challenge switch using key
+ * @disconnect_pcie_paths: Disconnects PCIe paths before NVM update
+ */
+struct tb_cm_ops {
+ int (*driver_ready)(struct tb *tb);
+ int (*start)(struct tb *tb);
+ void (*stop)(struct tb *tb);
+ int (*suspend_noirq)(struct tb *tb);
+ int (*resume_noirq)(struct tb *tb);
+ int (*suspend)(struct tb *tb);
+ void (*complete)(struct tb *tb);
+ void (*handle_event)(struct tb *tb, enum tb_cfg_pkg_type,
+ const void *buf, size_t size);
+ int (*approve_switch)(struct tb *tb, struct tb_switch *sw);
+ int (*add_switch_key)(struct tb *tb, struct tb_switch *sw);
+ int (*challenge_switch_key)(struct tb *tb, struct tb_switch *sw,
+ const u8 *challenge, u8 *response);
+ int (*disconnect_pcie_paths)(struct tb *tb);
+};
+
+/**
+ * struct tb - main thunderbolt bus structure
+ * @dev: Domain device
+ * @lock: Big lock. Must be held when accessing any struct
+ * tb_switch / struct tb_port.
+ * @nhi: Pointer to the NHI structure
+ * @ctl: Control channel for this domain
+ * @wq: Ordered workqueue for all domain specific work
+ * @root_switch: Root switch of this domain
+ * @cm_ops: Connection manager specific operations vector
+ * @index: Linux assigned domain number
+ * @security_level: Current security level
+ * @privdata: Private connection manager specific data
+ */
+struct tb {
+ struct device dev;
+ struct mutex lock;
+ struct tb_nhi *nhi;
+ struct tb_ctl *ctl;
+ struct workqueue_struct *wq;
+ struct tb_switch *root_switch;
+ const struct tb_cm_ops *cm_ops;
+ int index;
+ enum tb_security_level security_level;
+ unsigned long privdata[0];
+};
+
+static inline void *tb_priv(struct tb *tb)
+{
+ return (void *)tb->privdata;
+}
+
+/* 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 struct tb_port *tb_port_at(u64 route, struct tb_switch *sw)
+{
+ u8 port;
+
+ port = route >> (sw->config.depth * 8);
+ if (WARN_ON(port > sw->config.max_port_number))
+ return NULL;
+ return &sw->ports[port];
+}
+
+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, const 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 *icm_probe(struct tb_nhi *nhi);
+struct tb *tb_probe(struct tb_nhi *nhi);
+
+extern struct bus_type tb_bus_type;
+extern struct device_type tb_domain_type;
+extern struct device_type tb_switch_type;
+
+int tb_domain_init(void);
+void tb_domain_exit(void);
+void tb_switch_exit(void);
+
+struct tb *tb_domain_alloc(struct tb_nhi *nhi, size_t privsize);
+int tb_domain_add(struct tb *tb);
+void tb_domain_remove(struct tb *tb);
+int tb_domain_suspend_noirq(struct tb *tb);
+int tb_domain_resume_noirq(struct tb *tb);
+int tb_domain_suspend(struct tb *tb);
+void tb_domain_complete(struct tb *tb);
+int tb_domain_approve_switch(struct tb *tb, struct tb_switch *sw);
+int tb_domain_approve_switch_key(struct tb *tb, struct tb_switch *sw);
+int tb_domain_challenge_switch_key(struct tb *tb, struct tb_switch *sw);
+int tb_domain_disconnect_pcie_paths(struct tb *tb);
+
+static inline void tb_domain_put(struct tb *tb)
+{
+ put_device(&tb->dev);
+}
+
+struct tb_switch *tb_switch_alloc(struct tb *tb, struct device *parent,
+ u64 route);
+struct tb_switch *tb_switch_alloc_safe_mode(struct tb *tb,
+ struct device *parent, u64 route);
+int tb_switch_configure(struct tb_switch *sw);
+int tb_switch_add(struct tb_switch *sw);
+void tb_switch_remove(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_unplugged(struct tb_switch *sw);
+struct tb_switch *get_switch_at_route(struct tb_switch *sw, u64 route);
+struct tb_switch *tb_switch_find_by_link_depth(struct tb *tb, u8 link,
+ u8 depth);
+struct tb_switch *tb_switch_find_by_uuid(struct tb *tb, const uuid_t *uuid);
+
+static inline unsigned int tb_switch_phy_port_from_link(unsigned int link)
+{
+ return (link - 1) / TB_SWITCH_LINKS_PER_PHY_PORT;
+}
+
+static inline void tb_switch_put(struct tb_switch *sw)
+{
+ put_device(&sw->dev);
+}
+
+static inline bool tb_is_switch(const struct device *dev)
+{
+ return dev->type == &tb_switch_type;
+}
+
+static inline struct tb_switch *tb_to_switch(struct device *dev)
+{
+ if (tb_is_switch(dev))
+ return container_of(dev, struct tb_switch, dev);
+ return NULL;
+}
+
+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_switch_find_vse_cap(struct tb_switch *sw, enum tb_switch_vse_cap vsec);
+int tb_port_find_cap(struct tb_port *port, enum tb_port_cap cap);
+
+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);
+
+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)
+{
+ 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);
+}
+
+/**
+ * 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
diff --git a/src/kernel/linux/v4.14/drivers/thunderbolt/tb_msgs.h b/src/kernel/linux/v4.14/drivers/thunderbolt/tb_msgs.h
new file mode 100644
index 0000000..de6441e
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/thunderbolt/tb_msgs.h
@@ -0,0 +1,260 @@
+/*
+ * Thunderbolt control channel messages
+ *
+ * Copyright (C) 2014 Andreas Noever <andreas.noever@gmail.com>
+ * Copyright (C) 2017, Intel Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _TB_MSGS
+#define _TB_MSGS
+
+#include <linux/types.h>
+#include <linux/uuid.h>
+
+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_ICM_EVENT = 10,
+ TB_CFG_PKG_ICM_CMD = 11,
+ TB_CFG_PKG_ICM_RESP = 12,
+ TB_CFG_PKG_PREPARE_TO_SLEEP = 0xd,
+
+};
+
+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_LINK_ERROR = 1,
+ 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,
+ TB_CFG_ERROR_HEC_ERROR_DETECTED = 12,
+ TB_CFG_ERROR_FLOW_CONTROL_ERROR = 13,
+};
+
+/* 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;
+
+/* ICM messages */
+
+enum icm_pkg_code {
+ ICM_GET_TOPOLOGY = 0x1,
+ ICM_DRIVER_READY = 0x3,
+ ICM_APPROVE_DEVICE = 0x4,
+ ICM_CHALLENGE_DEVICE = 0x5,
+ ICM_ADD_DEVICE_KEY = 0x6,
+ ICM_GET_ROUTE = 0xa,
+};
+
+enum icm_event_code {
+ ICM_EVENT_DEVICE_CONNECTED = 3,
+ ICM_EVENT_DEVICE_DISCONNECTED = 4,
+};
+
+struct icm_pkg_header {
+ u8 code;
+ u8 flags;
+ u8 packet_id;
+ u8 total_packets;
+} __packed;
+
+#define ICM_FLAGS_ERROR BIT(0)
+#define ICM_FLAGS_NO_KEY BIT(1)
+#define ICM_FLAGS_SLEVEL_SHIFT 3
+#define ICM_FLAGS_SLEVEL_MASK GENMASK(4, 3)
+
+struct icm_pkg_driver_ready {
+ struct icm_pkg_header hdr;
+} __packed;
+
+struct icm_pkg_driver_ready_response {
+ struct icm_pkg_header hdr;
+ u8 romver;
+ u8 ramver;
+ u16 security_level;
+} __packed;
+
+/* Falcon Ridge & Alpine Ridge common messages */
+
+struct icm_fr_pkg_get_topology {
+ struct icm_pkg_header hdr;
+} __packed;
+
+#define ICM_GET_TOPOLOGY_PACKETS 14
+
+struct icm_fr_pkg_get_topology_response {
+ struct icm_pkg_header hdr;
+ u32 route_lo;
+ u32 route_hi;
+ u8 first_data;
+ u8 second_data;
+ u8 drom_i2c_address_index;
+ u8 switch_index;
+ u32 reserved[2];
+ u32 ports[16];
+ u32 port_hop_info[16];
+} __packed;
+
+#define ICM_SWITCH_USED BIT(0)
+#define ICM_SWITCH_UPSTREAM_PORT_MASK GENMASK(7, 1)
+#define ICM_SWITCH_UPSTREAM_PORT_SHIFT 1
+
+#define ICM_PORT_TYPE_MASK GENMASK(23, 0)
+#define ICM_PORT_INDEX_SHIFT 24
+#define ICM_PORT_INDEX_MASK GENMASK(31, 24)
+
+struct icm_fr_event_device_connected {
+ struct icm_pkg_header hdr;
+ uuid_t ep_uuid;
+ u8 connection_key;
+ u8 connection_id;
+ u16 link_info;
+ u32 ep_name[55];
+} __packed;
+
+#define ICM_LINK_INFO_LINK_MASK 0x7
+#define ICM_LINK_INFO_DEPTH_SHIFT 4
+#define ICM_LINK_INFO_DEPTH_MASK GENMASK(7, 4)
+#define ICM_LINK_INFO_APPROVED BIT(8)
+
+struct icm_fr_pkg_approve_device {
+ struct icm_pkg_header hdr;
+ uuid_t ep_uuid;
+ u8 connection_key;
+ u8 connection_id;
+ u16 reserved;
+} __packed;
+
+struct icm_fr_event_device_disconnected {
+ struct icm_pkg_header hdr;
+ u16 reserved;
+ u16 link_info;
+} __packed;
+
+struct icm_fr_pkg_add_device_key {
+ struct icm_pkg_header hdr;
+ uuid_t ep_uuid;
+ u8 connection_key;
+ u8 connection_id;
+ u16 reserved;
+ u32 key[8];
+} __packed;
+
+struct icm_fr_pkg_add_device_key_response {
+ struct icm_pkg_header hdr;
+ uuid_t ep_uuid;
+ u8 connection_key;
+ u8 connection_id;
+ u16 reserved;
+} __packed;
+
+struct icm_fr_pkg_challenge_device {
+ struct icm_pkg_header hdr;
+ uuid_t ep_uuid;
+ u8 connection_key;
+ u8 connection_id;
+ u16 reserved;
+ u32 challenge[8];
+} __packed;
+
+struct icm_fr_pkg_challenge_device_response {
+ struct icm_pkg_header hdr;
+ uuid_t ep_uuid;
+ u8 connection_key;
+ u8 connection_id;
+ u16 reserved;
+ u32 challenge[8];
+ u32 response[8];
+} __packed;
+
+/* Alpine Ridge only messages */
+
+struct icm_ar_pkg_get_route {
+ struct icm_pkg_header hdr;
+ u16 reserved;
+ u16 link_info;
+} __packed;
+
+struct icm_ar_pkg_get_route_response {
+ struct icm_pkg_header hdr;
+ u16 reserved;
+ u16 link_info;
+ u32 route_hi;
+ u32 route_lo;
+} __packed;
+
+#endif
diff --git a/src/kernel/linux/v4.14/drivers/thunderbolt/tb_regs.h b/src/kernel/linux/v4.14/drivers/thunderbolt/tb_regs.h
new file mode 100644
index 0000000..5d94142
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/thunderbolt/tb_regs.h
@@ -0,0 +1,240 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * 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_switch_cap {
+ TB_SWITCH_CAP_VSE = 0x05,
+};
+
+enum tb_switch_vse_cap {
+ TB_VSE_CAP_PLUG_EVENTS = 0x01, /* also EEPROM */
+ TB_VSE_CAP_TIME2 = 0x03,
+ TB_VSE_CAP_IECS = 0x04,
+ TB_VSE_CAP_LINK_CONTROLLER = 0x06, /* also IECS */
+};
+
+enum tb_port_cap {
+ TB_PORT_CAP_PHY = 0x01,
+ TB_PORT_CAP_TIME1 = 0x03,
+ TB_PORT_CAP_ADAP = 0x04,
+ TB_PORT_CAP_VSE = 0x05,
+};
+
+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 - Switch extended short capability
+ * @next: Pointer to the next capability. If @next and @length are zero
+ * then we have a long cap.
+ * @cap: Base capability ID (see &enum tb_switch_cap)
+ * @vsec_id: Vendor specific capability ID (see &enum switch_vse_cap)
+ * @length: Length of this capability
+ */
+struct tb_cap_extended_short {
+ u8 next;
+ u8 cap;
+ u8 vsec_id;
+ u8 length;
+} __packed;
+
+/**
+ * struct tb_cap_extended_long - Switch extended long capability
+ * @zero1: This field should be zero
+ * @cap: Base capability ID (see &enum tb_switch_cap)
+ * @vsec_id: Vendor specific capability ID (see &enum switch_vse_cap)
+ * @zero2: This field should be zero
+ * @next: Pointer to the next capability
+ * @length: Length of this capability
+ */
+struct tb_cap_extended_long {
+ u8 zero1;
+ u8 cap;
+ u8 vsec_id;
+ 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
diff --git a/src/kernel/linux/v4.14/drivers/thunderbolt/tunnel_pci.c b/src/kernel/linux/v4.14/drivers/thunderbolt/tunnel_pci.c
new file mode 100644
index 0000000..0637537
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/thunderbolt/tunnel_pci.c
@@ -0,0 +1,226 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * 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_port_find_cap(port, TB_PORT_CAP_ADAP);
+ if (cap < 0) {
+ tb_port_warn(port, "TB_PORT_CAP_ADAP not found: %d\n", cap);
+ return cap;
+ }
+ 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)
+{
+ 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;
+ }
+
+ return tb_pci_restart(tunnel);
+}
+
+
+
+/**
+ * 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);
+}
+
diff --git a/src/kernel/linux/v4.14/drivers/thunderbolt/tunnel_pci.h b/src/kernel/linux/v4.14/drivers/thunderbolt/tunnel_pci.h
new file mode 100644
index 0000000..f9b65fa
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/thunderbolt/tunnel_pci.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * 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
+