Commit c4b3630a authored by David S. Miller's avatar David S. Miller

Merge branch 'Thunderbolt-networking'

Mika Westerberg says:

====================
Thunderbolt networking

In addition of tunneling PCIe, Display Port and USB traffic, Thunderbolt
allows connecting two hosts (domains) over a Thunderbolt cable. It is
possible to tunnel arbitrary data packets over such connection using
high-speed DMA rings available in the Thunderbolt host controller.

In order to discover Thunderbolt services the other host supports, there is
a software protocol running on top of the automatically configured control
channel (ring 0). This protocol is called XDomain discovery protocol and it
uses XDomain properties to describe the host (domain) and the services it
supports.

Once both sides have agreed what services are supported they can enable
high-speed DMA rings to transfer data over the cable.

This series adds support for the XDomain protocol so that we expose each
remote connection as Thunderbolt XDomain device and each service as
Thunderbolt service device. On top of that we create an API that allows
writing drivers for these services and finally we provide an example
Thunderbolt service driver that creates virtual ethernet inferface that
allows tunneling networking packets over Thunderbolt cable. The API could
be used for creating other future Thunderbolt services, such as tunneling
SCSI over Thunderbolt, for example.

The XDomain protocol and networking support is also available in macOS and
Windows so this makes it possible to connect Linux to macOS and Windows as
well.

The patches are based on previous Thunderbolt networking patch series by
Amir Levy and Michael Jamet, that can be found here:

  https://lwn.net/Articles/705998/

The main difference to that patch series is that we have the XDomain
protocol running in the kernel now so there is no need for a separate
userspace daemon.

Note this does not affect the existing functionality, so security levels
and NVM firmware upgrade continue to work as before (with the small
exception that now sysfs also shows the XDomain connections and services in
addition to normal Thunderbolt devices). It is also possible to connect up
to 5 Thunderbolt devices and then another host, and the network driver
works exactly the same.

This is third version of the patch series. The previous versions can be
be found here:

  v2: https://lkml.org/lkml/2017/9/25/225
  v1: https://lwn.net/Articles/734019/

Changes from the v2:

  * Add comment regarding calculation of interrupt throttling value
  * Add UUIDs as strings in comments on top of each declaration
  * Add a patch removing __packed from existing ICM messages. They are all
    32-bit aligned and should pack fine without the __packed.
  * Move adding MAINTAINERS entries to a separate patches
  * Added Michael and Yehezkel to be maintainers of the network driver
  * Remove __packed from the new ICM messages. They should pack fine as
    well without it.
  * Call register_netdev() after all other initialization is done in the
    network driver.
  * Use build_skb() instead of copying. We allocate order 1 page here to
    leave room for SKB shared info required by build_skb(). However, we do
    not leave room for full NET_SKB_PAD because the NHI hardware does not
    cope well if a frame crosses 4kB boundary. According comments in
    __build_skb() that should still be fine.
  * Added Reviewed-by tag from Andy.

Changes from the v1:

  * Add include/linux/thunderbolt.h to MAINTAINERS
  * Correct Linux version and date of new sysfs entries in
    Documentation/ABI/testing/sysfs-bus-thunderbolt
  * Move network driver from drivers/thunderbolt/net.c to
    drivers/net/thunderbolt.c and update it to follow coding style in
    drivers/net/*.
  * Add MAINTAINERS entry for the network driver
  * Minor cleanups

In case someone wants to try this out, the last patch adds documentation
how the networking driver can be used. In short, if you connect Linux to a
macOS or Windows, everything is done automatically (as those systems have
the networking service enabled by default). For Linux to Linux connection
one host needs to load the networking driver first (so that the other side
can locate the networking service and load the corresponding driver).
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 1dd236fd c024297e
...@@ -110,3 +110,51 @@ Description: When new NVM image is written to the non-active NVM ...@@ -110,3 +110,51 @@ Description: When new NVM image is written to the non-active NVM
is directly the status value from the DMA configuration is directly the status value from the DMA configuration
based mailbox before the device is power cycled. Writing based mailbox before the device is power cycled. Writing
0 here clears the status. 0 here clears the status.
What: /sys/bus/thunderbolt/devices/<xdomain>.<service>/key
Date: Jan 2018
KernelVersion: 4.15
Contact: thunderbolt-software@lists.01.org
Description: This contains name of the property directory the XDomain
service exposes. This entry describes the protocol in
question. Following directories are already reserved by
the Apple XDomain specification:
network: IP/ethernet over Thunderbolt
targetdm: Target disk mode protocol over Thunderbolt
extdisp: External display mode protocol over Thunderbolt
What: /sys/bus/thunderbolt/devices/<xdomain>.<service>/modalias
Date: Jan 2018
KernelVersion: 4.15
Contact: thunderbolt-software@lists.01.org
Description: Stores the same MODALIAS value emitted by uevent for
the XDomain service. Format: tbtsvc:kSpNvNrN
What: /sys/bus/thunderbolt/devices/<xdomain>.<service>/prtcid
Date: Jan 2018
KernelVersion: 4.15
Contact: thunderbolt-software@lists.01.org
Description: This contains XDomain protocol identifier the XDomain
service supports.
What: /sys/bus/thunderbolt/devices/<xdomain>.<service>/prtcvers
Date: Jan 2018
KernelVersion: 4.15
Contact: thunderbolt-software@lists.01.org
Description: This contains XDomain protocol version the XDomain
service supports.
What: /sys/bus/thunderbolt/devices/<xdomain>.<service>/prtcrevs
Date: Jan 2018
KernelVersion: 4.15
Contact: thunderbolt-software@lists.01.org
Description: This contains XDomain software version the XDomain
service supports.
What: /sys/bus/thunderbolt/devices/<xdomain>.<service>/prtcstns
Date: Jan 2018
KernelVersion: 4.15
Contact: thunderbolt-software@lists.01.org
Description: This contains XDomain service specific settings as
bitmask. Format: %x
...@@ -197,3 +197,27 @@ information is missing. ...@@ -197,3 +197,27 @@ information is missing.
To recover from this mode, one needs to flash a valid NVM image to the To recover from this mode, one needs to flash a valid NVM image to the
host host controller in the same way it is done in the previous chapter. host host controller in the same way it is done in the previous chapter.
Networking over Thunderbolt cable
---------------------------------
Thunderbolt technology allows software communication across two hosts
connected by a Thunderbolt cable.
It is possible to tunnel any kind of traffic over Thunderbolt link but
currently we only support Apple ThunderboltIP protocol.
If the other host is running Windows or macOS only thing you need to
do is to connect Thunderbolt cable between the two hosts, the
``thunderbolt-net`` is loaded automatically. If the other host is also
Linux you should load ``thunderbolt-net`` manually on one host (it does
not matter which one)::
# modprobe thunderbolt-net
This triggers module load on the other host automatically. If the driver
is built-in to the kernel image, there is no need to do anything.
The driver will create one virtual ethernet interface per Thunderbolt
port which are named like ``thunderbolt0`` and so on. From this point
you can either use standard userspace tools like ``ifconfig`` to
configure the interface or let your GUI to handle it automatically.
...@@ -13278,6 +13278,15 @@ M: Mika Westerberg <mika.westerberg@linux.intel.com> ...@@ -13278,6 +13278,15 @@ M: Mika Westerberg <mika.westerberg@linux.intel.com>
M: Yehezkel Bernat <yehezkel.bernat@intel.com> M: Yehezkel Bernat <yehezkel.bernat@intel.com>
S: Maintained S: Maintained
F: drivers/thunderbolt/ F: drivers/thunderbolt/
F: include/linux/thunderbolt.h
THUNDERBOLT NETWORK DRIVER
M: Michael Jamet <michael.jamet@intel.com>
M: Mika Westerberg <mika.westerberg@linux.intel.com>
M: Yehezkel Bernat <yehezkel.bernat@intel.com>
L: netdev@vger.kernel.org
S: Maintained
F: drivers/net/thunderbolt.c
THUNDERX GPIO DRIVER THUNDERX GPIO DRIVER
M: David Daney <david.daney@cavium.com> M: David Daney <david.daney@cavium.com>
......
...@@ -483,6 +483,18 @@ config FUJITSU_ES ...@@ -483,6 +483,18 @@ config FUJITSU_ES
This driver provides support for Extended Socket network device This driver provides support for Extended Socket network device
on Extended Partitioning of FUJITSU PRIMEQUEST 2000 E2 series. on Extended Partitioning of FUJITSU PRIMEQUEST 2000 E2 series.
config THUNDERBOLT_NET
tristate "Networking over Thunderbolt cable"
depends on THUNDERBOLT && INET
help
Select this if you want to create network between two
computers over a Thunderbolt cable. The driver supports Apple
ThunderboltIP protocol and allows communication with any host
supporting the same protocol including Windows and macOS.
To compile this driver a module, choose M here. The module will be
called thunderbolt-net.
source "drivers/net/hyperv/Kconfig" source "drivers/net/hyperv/Kconfig"
endif # NETDEVICES endif # NETDEVICES
...@@ -74,3 +74,6 @@ obj-$(CONFIG_HYPERV_NET) += hyperv/ ...@@ -74,3 +74,6 @@ obj-$(CONFIG_HYPERV_NET) += hyperv/
obj-$(CONFIG_NTB_NETDEV) += ntb_netdev.o obj-$(CONFIG_NTB_NETDEV) += ntb_netdev.o
obj-$(CONFIG_FUJITSU_ES) += fjes/ obj-$(CONFIG_FUJITSU_ES) += fjes/
thunderbolt-net-y += thunderbolt.o
obj-$(CONFIG_THUNDERBOLT_NET) += thunderbolt-net.o
This diff is collapsed.
obj-${CONFIG_THUNDERBOLT} := thunderbolt.o 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 := 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 thunderbolt-objs += domain.o dma_port.o icm.o property.o xdomain.o
...@@ -289,20 +289,6 @@ static void tb_cfg_print_error(struct tb_ctl *ctl, ...@@ -289,20 +289,6 @@ static void tb_cfg_print_error(struct tb_ctl *ctl,
} }
} }
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) static __be32 tb_crc(const void *data, size_t len)
{ {
return cpu_to_be32(~__crc32c_le(~0, data, len)); return cpu_to_be32(~__crc32c_le(~0, data, len));
...@@ -373,7 +359,7 @@ static int tb_ctl_tx(struct tb_ctl *ctl, const void *data, size_t len, ...@@ -373,7 +359,7 @@ static int tb_ctl_tx(struct tb_ctl *ctl, const void *data, size_t len,
cpu_to_be32_array(pkg->buffer, data, len / 4); cpu_to_be32_array(pkg->buffer, data, len / 4);
*(__be32 *) (pkg->buffer + len) = tb_crc(pkg->buffer, len); *(__be32 *) (pkg->buffer + len) = tb_crc(pkg->buffer, len);
res = ring_tx(ctl->tx, &pkg->frame); res = tb_ring_tx(ctl->tx, &pkg->frame);
if (res) /* ring is stopped */ if (res) /* ring is stopped */
tb_ctl_pkg_free(pkg); tb_ctl_pkg_free(pkg);
return res; return res;
...@@ -382,15 +368,15 @@ static int tb_ctl_tx(struct tb_ctl *ctl, const void *data, size_t len, ...@@ -382,15 +368,15 @@ static int tb_ctl_tx(struct tb_ctl *ctl, const void *data, size_t len,
/** /**
* tb_ctl_handle_event() - acknowledge a plug event, invoke ctl->callback * 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, static bool tb_ctl_handle_event(struct tb_ctl *ctl, enum tb_cfg_pkg_type type,
struct ctl_pkg *pkg, size_t size) struct ctl_pkg *pkg, size_t size)
{ {
ctl->callback(ctl->callback_data, type, pkg->buffer, size); return ctl->callback(ctl->callback_data, type, pkg->buffer, size);
} }
static void tb_ctl_rx_submit(struct ctl_pkg *pkg) static void tb_ctl_rx_submit(struct ctl_pkg *pkg)
{ {
ring_rx(pkg->ctl->rx, &pkg->frame); /* tb_ring_rx(pkg->ctl->rx, &pkg->frame); /*
* We ignore failures during stop. * We ignore failures during stop.
* All rx packets are referenced * All rx packets are referenced
* from ctl->rx_packets, so we do * from ctl->rx_packets, so we do
...@@ -458,6 +444,8 @@ static void tb_ctl_rx_callback(struct tb_ring *ring, struct ring_frame *frame, ...@@ -458,6 +444,8 @@ static void tb_ctl_rx_callback(struct tb_ring *ring, struct ring_frame *frame,
break; break;
case TB_CFG_PKG_EVENT: case TB_CFG_PKG_EVENT:
case TB_CFG_PKG_XDOMAIN_RESP:
case TB_CFG_PKG_XDOMAIN_REQ:
if (*(__be32 *)(pkg->buffer + frame->size) != crc32) { if (*(__be32 *)(pkg->buffer + frame->size) != crc32) {
tb_ctl_err(pkg->ctl, tb_ctl_err(pkg->ctl,
"RX: checksum mismatch, dropping packet\n"); "RX: checksum mismatch, dropping packet\n");
...@@ -465,8 +453,9 @@ static void tb_ctl_rx_callback(struct tb_ring *ring, struct ring_frame *frame, ...@@ -465,8 +453,9 @@ static void tb_ctl_rx_callback(struct tb_ring *ring, struct ring_frame *frame,
} }
/* Fall through */ /* Fall through */
case TB_CFG_PKG_ICM_EVENT: case TB_CFG_PKG_ICM_EVENT:
tb_ctl_handle_event(pkg->ctl, frame->eof, pkg, frame->size); if (tb_ctl_handle_event(pkg->ctl, frame->eof, pkg, frame->size))
goto rx; goto rx;
break;
default: default:
break; break;
...@@ -625,11 +614,12 @@ struct tb_ctl *tb_ctl_alloc(struct tb_nhi *nhi, event_cb cb, void *cb_data) ...@@ -625,11 +614,12 @@ struct tb_ctl *tb_ctl_alloc(struct tb_nhi *nhi, event_cb cb, void *cb_data)
if (!ctl->frame_pool) if (!ctl->frame_pool)
goto err; goto err;
ctl->tx = ring_alloc_tx(nhi, 0, 10, RING_FLAG_NO_SUSPEND); ctl->tx = tb_ring_alloc_tx(nhi, 0, 10, RING_FLAG_NO_SUSPEND);
if (!ctl->tx) if (!ctl->tx)
goto err; goto err;
ctl->rx = ring_alloc_rx(nhi, 0, 10, RING_FLAG_NO_SUSPEND); ctl->rx = tb_ring_alloc_rx(nhi, 0, 10, RING_FLAG_NO_SUSPEND, 0xffff,
0xffff, NULL, NULL);
if (!ctl->rx) if (!ctl->rx)
goto err; goto err;
...@@ -662,9 +652,9 @@ void tb_ctl_free(struct tb_ctl *ctl) ...@@ -662,9 +652,9 @@ void tb_ctl_free(struct tb_ctl *ctl)
return; return;
if (ctl->rx) if (ctl->rx)
ring_free(ctl->rx); tb_ring_free(ctl->rx);
if (ctl->tx) if (ctl->tx)
ring_free(ctl->tx); tb_ring_free(ctl->tx);
/* free RX packets */ /* free RX packets */
for (i = 0; i < TB_CTL_RX_PKG_COUNT; i++) for (i = 0; i < TB_CTL_RX_PKG_COUNT; i++)
...@@ -683,8 +673,8 @@ void tb_ctl_start(struct tb_ctl *ctl) ...@@ -683,8 +673,8 @@ void tb_ctl_start(struct tb_ctl *ctl)
{ {
int i; int i;
tb_ctl_info(ctl, "control channel starting...\n"); tb_ctl_info(ctl, "control channel starting...\n");
ring_start(ctl->tx); /* is used to ack hotplug packets, start first */ tb_ring_start(ctl->tx); /* is used to ack hotplug packets, start first */
ring_start(ctl->rx); tb_ring_start(ctl->rx);
for (i = 0; i < TB_CTL_RX_PKG_COUNT; i++) for (i = 0; i < TB_CTL_RX_PKG_COUNT; i++)
tb_ctl_rx_submit(ctl->rx_packets[i]); tb_ctl_rx_submit(ctl->rx_packets[i]);
...@@ -705,8 +695,8 @@ void tb_ctl_stop(struct tb_ctl *ctl) ...@@ -705,8 +695,8 @@ void tb_ctl_stop(struct tb_ctl *ctl)
ctl->running = false; ctl->running = false;
mutex_unlock(&ctl->request_queue_lock); mutex_unlock(&ctl->request_queue_lock);
ring_stop(ctl->rx); tb_ring_stop(ctl->rx);
ring_stop(ctl->tx); tb_ring_stop(ctl->tx);
if (!list_empty(&ctl->request_queue)) if (!list_empty(&ctl->request_queue))
tb_ctl_WARN(ctl, "dangling request in request_queue\n"); tb_ctl_WARN(ctl, "dangling request in request_queue\n");
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#define _TB_CFG #define _TB_CFG
#include <linux/kref.h> #include <linux/kref.h>
#include <linux/thunderbolt.h>
#include "nhi.h" #include "nhi.h"
#include "tb_msgs.h" #include "tb_msgs.h"
...@@ -15,7 +16,7 @@ ...@@ -15,7 +16,7 @@
/* control channel */ /* control channel */
struct tb_ctl; struct tb_ctl;
typedef void (*event_cb)(void *data, enum tb_cfg_pkg_type type, typedef bool (*event_cb)(void *data, enum tb_cfg_pkg_type type,
const void *buf, size_t size); const void *buf, size_t size);
struct tb_ctl *tb_ctl_alloc(struct tb_nhi *nhi, event_cb cb, void *cb_data); struct tb_ctl *tb_ctl_alloc(struct tb_nhi *nhi, event_cb cb, void *cb_data);
......
...@@ -20,6 +20,98 @@ ...@@ -20,6 +20,98 @@
static DEFINE_IDA(tb_domain_ida); static DEFINE_IDA(tb_domain_ida);
static bool match_service_id(const struct tb_service_id *id,
const struct tb_service *svc)
{
if (id->match_flags & TBSVC_MATCH_PROTOCOL_KEY) {
if (strcmp(id->protocol_key, svc->key))
return false;
}
if (id->match_flags & TBSVC_MATCH_PROTOCOL_ID) {
if (id->protocol_id != svc->prtcid)
return false;
}
if (id->match_flags & TBSVC_MATCH_PROTOCOL_VERSION) {
if (id->protocol_version != svc->prtcvers)
return false;
}
if (id->match_flags & TBSVC_MATCH_PROTOCOL_VERSION) {
if (id->protocol_revision != svc->prtcrevs)
return false;
}
return true;
}
static const struct tb_service_id *__tb_service_match(struct device *dev,
struct device_driver *drv)
{
struct tb_service_driver *driver;
const struct tb_service_id *ids;
struct tb_service *svc;
svc = tb_to_service(dev);
if (!svc)
return NULL;
driver = container_of(drv, struct tb_service_driver, driver);
if (!driver->id_table)
return NULL;
for (ids = driver->id_table; ids->match_flags != 0; ids++) {
if (match_service_id(ids, svc))
return ids;
}
return NULL;
}
static int tb_service_match(struct device *dev, struct device_driver *drv)
{
return !!__tb_service_match(dev, drv);
}
static int tb_service_probe(struct device *dev)
{
struct tb_service *svc = tb_to_service(dev);
struct tb_service_driver *driver;
const struct tb_service_id *id;
driver = container_of(dev->driver, struct tb_service_driver, driver);
id = __tb_service_match(dev, &driver->driver);
return driver->probe(svc, id);
}
static int tb_service_remove(struct device *dev)
{
struct tb_service *svc = tb_to_service(dev);
struct tb_service_driver *driver;
driver = container_of(dev->driver, struct tb_service_driver, driver);
if (driver->remove)
driver->remove(svc);
return 0;
}
static void tb_service_shutdown(struct device *dev)
{
struct tb_service_driver *driver;
struct tb_service *svc;
svc = tb_to_service(dev);
if (!svc || !dev->driver)
return;
driver = container_of(dev->driver, struct tb_service_driver, driver);
if (driver->shutdown)
driver->shutdown(svc);
}
static const char * const tb_security_names[] = { static const char * const tb_security_names[] = {
[TB_SECURITY_NONE] = "none", [TB_SECURITY_NONE] = "none",
[TB_SECURITY_USER] = "user", [TB_SECURITY_USER] = "user",
...@@ -52,6 +144,10 @@ static const struct attribute_group *domain_attr_groups[] = { ...@@ -52,6 +144,10 @@ static const struct attribute_group *domain_attr_groups[] = {
struct bus_type tb_bus_type = { struct bus_type tb_bus_type = {
.name = "thunderbolt", .name = "thunderbolt",
.match = tb_service_match,
.probe = tb_service_probe,
.remove = tb_service_remove,
.shutdown = tb_service_shutdown,
}; };
static void tb_domain_release(struct device *dev) static void tb_domain_release(struct device *dev)
...@@ -128,17 +224,26 @@ struct tb *tb_domain_alloc(struct tb_nhi *nhi, size_t privsize) ...@@ -128,17 +224,26 @@ struct tb *tb_domain_alloc(struct tb_nhi *nhi, size_t privsize)
return NULL; return NULL;
} }
static void tb_domain_event_cb(void *data, enum tb_cfg_pkg_type type, static bool tb_domain_event_cb(void *data, enum tb_cfg_pkg_type type,
const void *buf, size_t size) const void *buf, size_t size)
{ {
struct tb *tb = data; struct tb *tb = data;
if (!tb->cm_ops->handle_event) { if (!tb->cm_ops->handle_event) {
tb_warn(tb, "domain does not have event handler\n"); tb_warn(tb, "domain does not have event handler\n");
return; return true;
} }
tb->cm_ops->handle_event(tb, type, buf, size); switch (type) {
case TB_CFG_PKG_XDOMAIN_REQ:
case TB_CFG_PKG_XDOMAIN_RESP:
return tb_xdomain_handle_request(tb, type, buf, size);
default:
tb->cm_ops->handle_event(tb, type, buf, size);
}
return true;
} }
/** /**
...@@ -443,9 +548,92 @@ int tb_domain_disconnect_pcie_paths(struct tb *tb) ...@@ -443,9 +548,92 @@ int tb_domain_disconnect_pcie_paths(struct tb *tb)
return tb->cm_ops->disconnect_pcie_paths(tb); return tb->cm_ops->disconnect_pcie_paths(tb);
} }
/**
* tb_domain_approve_xdomain_paths() - Enable DMA paths for XDomain
* @tb: Domain enabling the DMA paths
* @xd: XDomain DMA paths are created to
*
* Calls connection manager specific method to enable DMA paths to the
* XDomain in question.
*
* Return: 0% in case of success and negative errno otherwise. In
* particular returns %-ENOTSUPP if the connection manager
* implementation does not support XDomains.
*/
int tb_domain_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd)
{
if (!tb->cm_ops->approve_xdomain_paths)
return -ENOTSUPP;
return tb->cm_ops->approve_xdomain_paths(tb, xd);
}
/**
* tb_domain_disconnect_xdomain_paths() - Disable DMA paths for XDomain
* @tb: Domain disabling the DMA paths
* @xd: XDomain whose DMA paths are disconnected
*
* Calls connection manager specific method to disconnect DMA paths to
* the XDomain in question.
*
* Return: 0% in case of success and negative errno otherwise. In
* particular returns %-ENOTSUPP if the connection manager
* implementation does not support XDomains.
*/
int tb_domain_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd)
{
if (!tb->cm_ops->disconnect_xdomain_paths)
return -ENOTSUPP;
return tb->cm_ops->disconnect_xdomain_paths(tb, xd);
}
static int disconnect_xdomain(struct device *dev, void *data)
{
struct tb_xdomain *xd;
struct tb *tb = data;
int ret = 0;
xd = tb_to_xdomain(dev);
if (xd && xd->tb == tb)
ret = tb_xdomain_disable_paths(xd);
return ret;
}
/**
* tb_domain_disconnect_all_paths() - Disconnect all paths for the domain
* @tb: Domain whose paths are disconnected
*
* This function can be used to disconnect all paths (PCIe, XDomain) for
* example in preparation for host NVM firmware upgrade. After this is
* called the paths cannot be established without resetting the switch.
*
* Return: %0 in case of success and negative errno otherwise.
*/
int tb_domain_disconnect_all_paths(struct tb *tb)
{
int ret;
ret = tb_domain_disconnect_pcie_paths(tb);
if (ret)
return ret;
return bus_for_each_dev(&tb_bus_type, NULL, tb, disconnect_xdomain);
}
int tb_domain_init(void) int tb_domain_init(void)
{ {
return bus_register(&tb_bus_type); int ret;
ret = tb_xdomain_init();
if (ret)
return ret;
ret = bus_register(&tb_bus_type);
if (ret)
tb_xdomain_exit();
return ret;
} }
void tb_domain_exit(void) void tb_domain_exit(void)
...@@ -453,4 +641,5 @@ void tb_domain_exit(void) ...@@ -453,4 +641,5 @@ void tb_domain_exit(void)
bus_unregister(&tb_bus_type); bus_unregister(&tb_bus_type);
ida_destroy(&tb_domain_ida); ida_destroy(&tb_domain_ida);
tb_switch_exit(); tb_switch_exit();
tb_xdomain_exit();
} }
...@@ -60,6 +60,8 @@ ...@@ -60,6 +60,8 @@
* @get_route: Find a route string for given switch * @get_route: Find a route string for given switch
* @device_connected: Handle device connected ICM message * @device_connected: Handle device connected ICM message
* @device_disconnected: Handle device disconnected ICM message * @device_disconnected: Handle device disconnected ICM message
* @xdomain_connected - Handle XDomain connected ICM message
* @xdomain_disconnected - Handle XDomain disconnected ICM message
*/ */
struct icm { struct icm {
struct mutex request_lock; struct mutex request_lock;
...@@ -74,6 +76,10 @@ struct icm { ...@@ -74,6 +76,10 @@ struct icm {
const struct icm_pkg_header *hdr); const struct icm_pkg_header *hdr);
void (*device_disconnected)(struct tb *tb, void (*device_disconnected)(struct tb *tb,
const struct icm_pkg_header *hdr); const struct icm_pkg_header *hdr);
void (*xdomain_connected)(struct tb *tb,
const struct icm_pkg_header *hdr);
void (*xdomain_disconnected)(struct tb *tb,
const struct icm_pkg_header *hdr);
}; };
struct icm_notification { struct icm_notification {
...@@ -89,7 +95,10 @@ static inline struct tb *icm_to_tb(struct icm *icm) ...@@ -89,7 +95,10 @@ static inline struct tb *icm_to_tb(struct icm *icm)
static inline u8 phy_port_from_route(u64 route, u8 depth) static inline u8 phy_port_from_route(u64 route, u8 depth)
{ {
return tb_switch_phy_port_from_link(route >> ((depth - 1) * 8)); u8 link;
link = depth ? route >> ((depth - 1) * 8) : route;
return tb_phy_port_from_link(link);
} }
static inline u8 dual_link_from_link(u8 link) static inline u8 dual_link_from_link(u8 link)
...@@ -320,6 +329,51 @@ static int icm_fr_challenge_switch_key(struct tb *tb, struct tb_switch *sw, ...@@ -320,6 +329,51 @@ static int icm_fr_challenge_switch_key(struct tb *tb, struct tb_switch *sw,
return 0; return 0;
} }
static int icm_fr_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd)
{
struct icm_fr_pkg_approve_xdomain_response reply;
struct icm_fr_pkg_approve_xdomain request;
int ret;
memset(&request, 0, sizeof(request));
request.hdr.code = ICM_APPROVE_XDOMAIN;
request.link_info = xd->depth << ICM_LINK_INFO_DEPTH_SHIFT | xd->link;
memcpy(&request.remote_uuid, xd->remote_uuid, sizeof(*xd->remote_uuid));
request.transmit_path = xd->transmit_path;
request.transmit_ring = xd->transmit_ring;
request.receive_path = xd->receive_path;
request.receive_ring = xd->receive_ring;
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;
return 0;
}
static int icm_fr_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd)
{
u8 phy_port;
u8 cmd;
phy_port = tb_phy_port_from_link(xd->link);
if (phy_port == 0)
cmd = NHI_MAILBOX_DISCONNECT_PA;
else
cmd = NHI_MAILBOX_DISCONNECT_PB;
nhi_mailbox_cmd(tb->nhi, cmd, 1);
usleep_range(10, 50);
nhi_mailbox_cmd(tb->nhi, cmd, 2);
return 0;
}
static void remove_switch(struct tb_switch *sw) static void remove_switch(struct tb_switch *sw)
{ {
struct tb_switch *parent_sw; struct tb_switch *parent_sw;
...@@ -475,6 +529,141 @@ icm_fr_device_disconnected(struct tb *tb, const struct icm_pkg_header *hdr) ...@@ -475,6 +529,141 @@ icm_fr_device_disconnected(struct tb *tb, const struct icm_pkg_header *hdr)
tb_switch_put(sw); tb_switch_put(sw);
} }
static void remove_xdomain(struct tb_xdomain *xd)
{
struct tb_switch *sw;
sw = tb_to_switch(xd->dev.parent);
tb_port_at(xd->route, sw)->xdomain = NULL;
tb_xdomain_remove(xd);
}
static void
icm_fr_xdomain_connected(struct tb *tb, const struct icm_pkg_header *hdr)
{
const struct icm_fr_event_xdomain_connected *pkg =
(const struct icm_fr_event_xdomain_connected *)hdr;
struct tb_xdomain *xd;
struct tb_switch *sw;
u8 link, depth;
bool approved;
u64 route;
/*
* After NVM upgrade adding root switch device fails because we
* initiated reset. During that time ICM might still send
* XDomain connected message which we ignore here.
*/
if (!tb->root_switch)
return;
link = pkg->link_info & ICM_LINK_INFO_LINK_MASK;
depth = (pkg->link_info & ICM_LINK_INFO_DEPTH_MASK) >>
ICM_LINK_INFO_DEPTH_SHIFT;
approved = pkg->link_info & ICM_LINK_INFO_APPROVED;
if (link > ICM_MAX_LINK || depth > ICM_MAX_DEPTH) {
tb_warn(tb, "invalid topology %u.%u, ignoring\n", link, depth);
return;
}
route = get_route(pkg->local_route_hi, pkg->local_route_lo);
xd = tb_xdomain_find_by_uuid(tb, &pkg->remote_uuid);
if (xd) {
u8 xd_phy_port, phy_port;
xd_phy_port = phy_port_from_route(xd->route, xd->depth);
phy_port = phy_port_from_route(route, depth);
if (xd->depth == depth && xd_phy_port == phy_port) {
xd->link = link;
xd->route = route;
xd->is_unplugged = false;
tb_xdomain_put(xd);
return;
}
/*
* If we find an existing XDomain connection remove it
* now. We need to go through login handshake and
* everything anyway to be able to re-establish the
* connection.
*/
remove_xdomain(xd);
tb_xdomain_put(xd);
}
/*
* Look if there already exists an XDomain in the same place
* than the new one and in that case remove it because it is
* most likely another host that got disconnected.
*/
xd = tb_xdomain_find_by_link_depth(tb, link, depth);
if (!xd) {
u8 dual_link;
dual_link = dual_link_from_link(link);
if (dual_link)
xd = tb_xdomain_find_by_link_depth(tb, dual_link,
depth);
}
if (xd) {
remove_xdomain(xd);
tb_xdomain_put(xd);
}
/*
* If the user disconnected a switch during suspend and
* connected another host to the same port, remove the switch
* first.
*/
sw = get_switch_at_route(tb->root_switch, route);
if (sw)
remove_switch(sw);
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;
}
xd = tb_xdomain_alloc(sw->tb, &sw->dev, route,
&pkg->local_uuid, &pkg->remote_uuid);
if (!xd) {
tb_switch_put(sw);
return;
}
xd->link = link;
xd->depth = depth;
tb_port_at(route, sw)->xdomain = xd;
tb_xdomain_add(xd);
tb_switch_put(sw);
}
static void
icm_fr_xdomain_disconnected(struct tb *tb, const struct icm_pkg_header *hdr)
{
const struct icm_fr_event_xdomain_disconnected *pkg =
(const struct icm_fr_event_xdomain_disconnected *)hdr;
struct tb_xdomain *xd;
/*
* If the connection is through one or multiple devices, the
* XDomain device is removed along with them so it is fine if we
* cannot find it here.
*/
xd = tb_xdomain_find_by_uuid(tb, &pkg->remote_uuid);
if (xd) {
remove_xdomain(xd);
tb_xdomain_put(xd);
}
}
static struct pci_dev *get_upstream_port(struct pci_dev *pdev) static struct pci_dev *get_upstream_port(struct pci_dev *pdev)
{ {
struct pci_dev *parent; struct pci_dev *parent;
...@@ -594,6 +783,12 @@ static void icm_handle_notification(struct work_struct *work) ...@@ -594,6 +783,12 @@ static void icm_handle_notification(struct work_struct *work)
case ICM_EVENT_DEVICE_DISCONNECTED: case ICM_EVENT_DEVICE_DISCONNECTED:
icm->device_disconnected(tb, n->pkg); icm->device_disconnected(tb, n->pkg);
break; break;
case ICM_EVENT_XDOMAIN_CONNECTED:
icm->xdomain_connected(tb, n->pkg);
break;
case ICM_EVENT_XDOMAIN_DISCONNECTED:
icm->xdomain_disconnected(tb, n->pkg);
break;
} }
mutex_unlock(&tb->lock); mutex_unlock(&tb->lock);
...@@ -927,6 +1122,10 @@ static void icm_unplug_children(struct tb_switch *sw) ...@@ -927,6 +1122,10 @@ static void icm_unplug_children(struct tb_switch *sw)
if (tb_is_upstream_port(port)) if (tb_is_upstream_port(port))
continue; continue;
if (port->xdomain) {
port->xdomain->is_unplugged = true;
continue;
}
if (!port->remote) if (!port->remote)
continue; continue;
...@@ -943,6 +1142,13 @@ static void icm_free_unplugged_children(struct tb_switch *sw) ...@@ -943,6 +1142,13 @@ static void icm_free_unplugged_children(struct tb_switch *sw)
if (tb_is_upstream_port(port)) if (tb_is_upstream_port(port))
continue; continue;
if (port->xdomain && port->xdomain->is_unplugged) {
tb_xdomain_remove(port->xdomain);
port->xdomain = NULL;
continue;
}
if (!port->remote) if (!port->remote)
continue; continue;
...@@ -1009,8 +1215,10 @@ static int icm_start(struct tb *tb) ...@@ -1009,8 +1215,10 @@ static int icm_start(struct tb *tb)
tb->root_switch->no_nvm_upgrade = x86_apple_machine; tb->root_switch->no_nvm_upgrade = x86_apple_machine;
ret = tb_switch_add(tb->root_switch); ret = tb_switch_add(tb->root_switch);
if (ret) if (ret) {
tb_switch_put(tb->root_switch); tb_switch_put(tb->root_switch);
tb->root_switch = NULL;
}
return ret; return ret;
} }
...@@ -1042,6 +1250,8 @@ static const struct tb_cm_ops icm_fr_ops = { ...@@ -1042,6 +1250,8 @@ static const struct tb_cm_ops icm_fr_ops = {
.add_switch_key = icm_fr_add_switch_key, .add_switch_key = icm_fr_add_switch_key,
.challenge_switch_key = icm_fr_challenge_switch_key, .challenge_switch_key = icm_fr_challenge_switch_key,
.disconnect_pcie_paths = icm_disconnect_pcie_paths, .disconnect_pcie_paths = icm_disconnect_pcie_paths,
.approve_xdomain_paths = icm_fr_approve_xdomain_paths,
.disconnect_xdomain_paths = icm_fr_disconnect_xdomain_paths,
}; };
struct tb *icm_probe(struct tb_nhi *nhi) struct tb *icm_probe(struct tb_nhi *nhi)
...@@ -1064,6 +1274,8 @@ struct tb *icm_probe(struct tb_nhi *nhi) ...@@ -1064,6 +1274,8 @@ struct tb *icm_probe(struct tb_nhi *nhi)
icm->get_route = icm_fr_get_route; icm->get_route = icm_fr_get_route;
icm->device_connected = icm_fr_device_connected; icm->device_connected = icm_fr_device_connected;
icm->device_disconnected = icm_fr_device_disconnected; icm->device_disconnected = icm_fr_device_disconnected;
icm->xdomain_connected = icm_fr_xdomain_connected;
icm->xdomain_disconnected = icm_fr_xdomain_disconnected;
tb->cm_ops = &icm_fr_ops; tb->cm_ops = &icm_fr_ops;
break; break;
...@@ -1077,6 +1289,8 @@ struct tb *icm_probe(struct tb_nhi *nhi) ...@@ -1077,6 +1289,8 @@ struct tb *icm_probe(struct tb_nhi *nhi)
icm->get_route = icm_ar_get_route; icm->get_route = icm_ar_get_route;
icm->device_connected = icm_fr_device_connected; icm->device_connected = icm_fr_device_connected;
icm->device_disconnected = icm_fr_device_disconnected; icm->device_disconnected = icm_fr_device_disconnected;
icm->xdomain_connected = icm_fr_xdomain_connected;
icm->xdomain_disconnected = icm_fr_xdomain_disconnected;
tb->cm_ops = &icm_fr_ops; tb->cm_ops = &icm_fr_ops;
break; break;
} }
......
This diff is collapsed.
...@@ -7,144 +7,7 @@ ...@@ -7,144 +7,7 @@
#ifndef DSL3510_H_ #ifndef DSL3510_H_
#define DSL3510_H_ #define DSL3510_H_
#include <linux/idr.h> #include <linux/thunderbolt.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 { enum nhi_fw_mode {
NHI_FW_SAFE_MODE, NHI_FW_SAFE_MODE,
...@@ -157,6 +20,8 @@ enum nhi_mailbox_cmd { ...@@ -157,6 +20,8 @@ enum nhi_mailbox_cmd {
NHI_MAILBOX_SAVE_DEVS = 0x05, NHI_MAILBOX_SAVE_DEVS = 0x05,
NHI_MAILBOX_DISCONNECT_PCIE_PATHS = 0x06, NHI_MAILBOX_DISCONNECT_PCIE_PATHS = 0x06,
NHI_MAILBOX_DRV_UNLOADS = 0x07, NHI_MAILBOX_DRV_UNLOADS = 0x07,
NHI_MAILBOX_DISCONNECT_PA = 0x10,
NHI_MAILBOX_DISCONNECT_PB = 0x11,
NHI_MAILBOX_ALLOW_ALL_DEVS = 0x23, NHI_MAILBOX_ALLOW_ALL_DEVS = 0x23,
}; };
......
...@@ -17,13 +17,6 @@ enum ring_flags { ...@@ -17,13 +17,6 @@ enum ring_flags {
RING_FLAG_ENABLE = 1 << 31, 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 * struct ring_desc - TX/RX ring entry
* *
...@@ -77,6 +70,8 @@ struct ring_desc { ...@@ -77,6 +70,8 @@ struct ring_desc {
* ..: unknown * ..: unknown
*/ */
#define REG_RX_OPTIONS_BASE 0x29800 #define REG_RX_OPTIONS_BASE 0x29800
#define REG_RX_OPTIONS_E2E_HOP_MASK GENMASK(22, 12)
#define REG_RX_OPTIONS_E2E_HOP_SHIFT 12
/* /*
* three bitfields: tx, rx, rx overflow * three bitfields: tx, rx, rx overflow
...@@ -95,6 +90,8 @@ struct ring_desc { ...@@ -95,6 +90,8 @@ struct ring_desc {
#define REG_RING_INTERRUPT_BASE 0x38200 #define REG_RING_INTERRUPT_BASE 0x38200
#define RING_INTERRUPT_REG_COUNT(nhi) ((31 + 2 * nhi->hop_count) / 32) #define RING_INTERRUPT_REG_COUNT(nhi) ((31 + 2 * nhi->hop_count) / 32)
#define REG_INT_THROTTLING_RATE 0x38c00
/* Interrupt Vector Allocation */ /* Interrupt Vector Allocation */
#define REG_INT_VEC_ALLOC_BASE 0x38c40 #define REG_INT_VEC_ALLOC_BASE 0x38c40
#define REG_INT_VEC_ALLOC_BITS 4 #define REG_INT_VEC_ALLOC_BITS 4
......
This diff is collapsed.
...@@ -171,11 +171,11 @@ static int nvm_authenticate_host(struct tb_switch *sw) ...@@ -171,11 +171,11 @@ static int nvm_authenticate_host(struct tb_switch *sw)
/* /*
* Root switch NVM upgrade requires that we disconnect the * Root switch NVM upgrade requires that we disconnect the
* existing PCIe paths first (in case it is not in safe mode * existing paths first (in case it is not in safe mode
* already). * already).
*/ */
if (!sw->safe_mode) { if (!sw->safe_mode) {
ret = tb_domain_disconnect_pcie_paths(sw->tb); ret = tb_domain_disconnect_all_paths(sw->tb);
if (ret) if (ret)
return ret; return ret;
/* /*
...@@ -1363,6 +1363,9 @@ void tb_switch_remove(struct tb_switch *sw) ...@@ -1363,6 +1363,9 @@ void tb_switch_remove(struct tb_switch *sw)
if (sw->ports[i].remote) if (sw->ports[i].remote)
tb_switch_remove(sw->ports[i].remote->sw); tb_switch_remove(sw->ports[i].remote->sw);
sw->ports[i].remote = NULL; sw->ports[i].remote = NULL;
if (sw->ports[i].xdomain)
tb_xdomain_remove(sw->ports[i].xdomain);
sw->ports[i].xdomain = NULL;
} }
if (!sw->is_unplugged) if (!sw->is_unplugged)
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#include <linux/nvmem-provider.h> #include <linux/nvmem-provider.h>
#include <linux/pci.h> #include <linux/pci.h>
#include <linux/thunderbolt.h>
#include <linux/uuid.h> #include <linux/uuid.h>
#include "tb_regs.h" #include "tb_regs.h"
...@@ -39,23 +40,7 @@ struct tb_switch_nvm { ...@@ -39,23 +40,7 @@ struct tb_switch_nvm {
bool authenticating; 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 #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 * struct tb_switch - a thunderbolt switch
...@@ -125,14 +110,25 @@ struct tb_switch { ...@@ -125,14 +110,25 @@ struct tb_switch {
/** /**
* struct tb_port - a thunderbolt port, part of a tb_switch * struct tb_port - a thunderbolt port, part of a tb_switch
* @config: Cached port configuration read from registers
* @sw: Switch the port belongs to
* @remote: Remote port (%NULL if not connected)
* @xdomain: Remote host (%NULL if not connected)
* @cap_phy: Offset, zero if not found
* @port: Port number on switch
* @disabled: Disabled by eeprom
* @dual_link_port: If the switch is connected using two ports, points
* to the other port.
* @link_nr: Is this primary or secondary port on the dual_link.
*/ */
struct tb_port { struct tb_port {
struct tb_regs_port_header config; struct tb_regs_port_header config;
struct tb_switch *sw; struct tb_switch *sw;
struct tb_port *remote; /* remote port, NULL if not connected */ struct tb_port *remote;
int cap_phy; /* offset, zero if not found */ struct tb_xdomain *xdomain;
u8 port; /* port number on switch */ int cap_phy;
bool disabled; /* disabled by eeprom */ u8 port;
bool disabled;
struct tb_port *dual_link_port; struct tb_port *dual_link_port;
u8 link_nr:1; u8 link_nr:1;
}; };
...@@ -205,6 +201,8 @@ struct tb_path { ...@@ -205,6 +201,8 @@ struct tb_path {
* @add_switch_key: Add key to switch * @add_switch_key: Add key to switch
* @challenge_switch_key: Challenge switch using key * @challenge_switch_key: Challenge switch using key
* @disconnect_pcie_paths: Disconnects PCIe paths before NVM update * @disconnect_pcie_paths: Disconnects PCIe paths before NVM update
* @approve_xdomain_paths: Approve (establish) XDomain DMA paths
* @disconnect_xdomain_paths: Disconnect XDomain DMA paths
*/ */
struct tb_cm_ops { struct tb_cm_ops {
int (*driver_ready)(struct tb *tb); int (*driver_ready)(struct tb *tb);
...@@ -221,33 +219,8 @@ struct tb_cm_ops { ...@@ -221,33 +219,8 @@ struct tb_cm_ops {
int (*challenge_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); const u8 *challenge, u8 *response);
int (*disconnect_pcie_paths)(struct tb *tb); int (*disconnect_pcie_paths)(struct tb *tb);
}; int (*approve_xdomain_paths)(struct tb *tb, struct tb_xdomain *xd);
int (*disconnect_xdomain_paths)(struct tb *tb, struct tb_xdomain *xd);
/**
* 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) static inline void *tb_priv(struct tb *tb)
...@@ -368,13 +341,14 @@ static inline int tb_port_write(struct tb_port *port, const void *buffer, ...@@ -368,13 +341,14 @@ static inline int tb_port_write(struct tb_port *port, const void *buffer,
struct tb *icm_probe(struct tb_nhi *nhi); struct tb *icm_probe(struct tb_nhi *nhi);
struct tb *tb_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_domain_type;
extern struct device_type tb_switch_type; extern struct device_type tb_switch_type;
int tb_domain_init(void); int tb_domain_init(void);
void tb_domain_exit(void); void tb_domain_exit(void);
void tb_switch_exit(void); void tb_switch_exit(void);
int tb_xdomain_init(void);
void tb_xdomain_exit(void);
struct tb *tb_domain_alloc(struct tb_nhi *nhi, size_t privsize); struct tb *tb_domain_alloc(struct tb_nhi *nhi, size_t privsize);
int tb_domain_add(struct tb *tb); int tb_domain_add(struct tb *tb);
...@@ -387,6 +361,9 @@ int tb_domain_approve_switch(struct tb *tb, struct tb_switch *sw); ...@@ -387,6 +361,9 @@ 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_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_challenge_switch_key(struct tb *tb, struct tb_switch *sw);
int tb_domain_disconnect_pcie_paths(struct tb *tb); int tb_domain_disconnect_pcie_paths(struct tb *tb);
int tb_domain_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd);
int tb_domain_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd);
int tb_domain_disconnect_all_paths(struct tb *tb);
static inline void tb_domain_put(struct tb *tb) static inline void tb_domain_put(struct tb *tb)
{ {
...@@ -409,11 +386,6 @@ struct tb_switch *tb_switch_find_by_link_depth(struct tb *tb, u8 link, ...@@ -409,11 +386,6 @@ struct tb_switch *tb_switch_find_by_link_depth(struct tb *tb, u8 link,
u8 depth); u8 depth);
struct tb_switch *tb_switch_find_by_uuid(struct tb *tb, const uuid_t *uuid); 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) static inline void tb_switch_put(struct tb_switch *sw)
{ {
put_device(&sw->dev); put_device(&sw->dev);
...@@ -471,4 +443,14 @@ static inline u64 tb_downstream_route(struct tb_port *port) ...@@ -471,4 +443,14 @@ static inline u64 tb_downstream_route(struct tb_port *port)
| ((u64) port->port << (port->sw->config.depth * 8)); | ((u64) port->port << (port->sw->config.depth * 8));
} }
bool tb_xdomain_handle_request(struct tb *tb, enum tb_cfg_pkg_type type,
const void *buf, size_t size);
struct tb_xdomain *tb_xdomain_alloc(struct tb *tb, struct device *parent,
u64 route, const uuid_t *local_uuid,
const uuid_t *remote_uuid);
void tb_xdomain_add(struct tb_xdomain *xd);
void tb_xdomain_remove(struct tb_xdomain *xd);
struct tb_xdomain *tb_xdomain_find_by_link_depth(struct tb *tb, u8 link,
u8 depth);
#endif #endif
...@@ -15,23 +15,6 @@ ...@@ -15,23 +15,6 @@
#include <linux/types.h> #include <linux/types.h>
#include <linux/uuid.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 { enum tb_cfg_space {
TB_CFG_HOPS = 0, TB_CFG_HOPS = 0,
TB_CFG_PORT = 1, TB_CFG_PORT = 1,
...@@ -118,11 +101,14 @@ enum icm_pkg_code { ...@@ -118,11 +101,14 @@ enum icm_pkg_code {
ICM_CHALLENGE_DEVICE = 0x5, ICM_CHALLENGE_DEVICE = 0x5,
ICM_ADD_DEVICE_KEY = 0x6, ICM_ADD_DEVICE_KEY = 0x6,
ICM_GET_ROUTE = 0xa, ICM_GET_ROUTE = 0xa,
ICM_APPROVE_XDOMAIN = 0x10,
}; };
enum icm_event_code { enum icm_event_code {
ICM_EVENT_DEVICE_CONNECTED = 3, ICM_EVENT_DEVICE_CONNECTED = 3,
ICM_EVENT_DEVICE_DISCONNECTED = 4, ICM_EVENT_DEVICE_DISCONNECTED = 4,
ICM_EVENT_XDOMAIN_CONNECTED = 6,
ICM_EVENT_XDOMAIN_DISCONNECTED = 7,
}; };
struct icm_pkg_header { struct icm_pkg_header {
...@@ -130,7 +116,7 @@ struct icm_pkg_header { ...@@ -130,7 +116,7 @@ struct icm_pkg_header {
u8 flags; u8 flags;
u8 packet_id; u8 packet_id;
u8 total_packets; u8 total_packets;
} __packed; };
#define ICM_FLAGS_ERROR BIT(0) #define ICM_FLAGS_ERROR BIT(0)
#define ICM_FLAGS_NO_KEY BIT(1) #define ICM_FLAGS_NO_KEY BIT(1)
...@@ -139,20 +125,20 @@ struct icm_pkg_header { ...@@ -139,20 +125,20 @@ struct icm_pkg_header {
struct icm_pkg_driver_ready { struct icm_pkg_driver_ready {
struct icm_pkg_header hdr; struct icm_pkg_header hdr;
} __packed; };
struct icm_pkg_driver_ready_response { struct icm_pkg_driver_ready_response {
struct icm_pkg_header hdr; struct icm_pkg_header hdr;
u8 romver; u8 romver;
u8 ramver; u8 ramver;
u16 security_level; u16 security_level;
} __packed; };
/* Falcon Ridge & Alpine Ridge common messages */ /* Falcon Ridge & Alpine Ridge common messages */
struct icm_fr_pkg_get_topology { struct icm_fr_pkg_get_topology {
struct icm_pkg_header hdr; struct icm_pkg_header hdr;
} __packed; };
#define ICM_GET_TOPOLOGY_PACKETS 14 #define ICM_GET_TOPOLOGY_PACKETS 14
...@@ -167,7 +153,7 @@ struct icm_fr_pkg_get_topology_response { ...@@ -167,7 +153,7 @@ struct icm_fr_pkg_get_topology_response {
u32 reserved[2]; u32 reserved[2];
u32 ports[16]; u32 ports[16];
u32 port_hop_info[16]; u32 port_hop_info[16];
} __packed; };
#define ICM_SWITCH_USED BIT(0) #define ICM_SWITCH_USED BIT(0)
#define ICM_SWITCH_UPSTREAM_PORT_MASK GENMASK(7, 1) #define ICM_SWITCH_UPSTREAM_PORT_MASK GENMASK(7, 1)
...@@ -184,7 +170,7 @@ struct icm_fr_event_device_connected { ...@@ -184,7 +170,7 @@ struct icm_fr_event_device_connected {
u8 connection_id; u8 connection_id;
u16 link_info; u16 link_info;
u32 ep_name[55]; u32 ep_name[55];
} __packed; };
#define ICM_LINK_INFO_LINK_MASK 0x7 #define ICM_LINK_INFO_LINK_MASK 0x7
#define ICM_LINK_INFO_DEPTH_SHIFT 4 #define ICM_LINK_INFO_DEPTH_SHIFT 4
...@@ -197,13 +183,32 @@ struct icm_fr_pkg_approve_device { ...@@ -197,13 +183,32 @@ struct icm_fr_pkg_approve_device {
u8 connection_key; u8 connection_key;
u8 connection_id; u8 connection_id;
u16 reserved; u16 reserved;
} __packed; };
struct icm_fr_event_device_disconnected { struct icm_fr_event_device_disconnected {
struct icm_pkg_header hdr; struct icm_pkg_header hdr;
u16 reserved; u16 reserved;
u16 link_info; u16 link_info;
} __packed; };
struct icm_fr_event_xdomain_connected {
struct icm_pkg_header hdr;
u16 reserved;
u16 link_info;
uuid_t remote_uuid;
uuid_t local_uuid;
u32 local_route_hi;
u32 local_route_lo;
u32 remote_route_hi;
u32 remote_route_lo;
};
struct icm_fr_event_xdomain_disconnected {
struct icm_pkg_header hdr;
u16 reserved;
u16 link_info;
uuid_t remote_uuid;
};
struct icm_fr_pkg_add_device_key { struct icm_fr_pkg_add_device_key {
struct icm_pkg_header hdr; struct icm_pkg_header hdr;
...@@ -212,7 +217,7 @@ struct icm_fr_pkg_add_device_key { ...@@ -212,7 +217,7 @@ struct icm_fr_pkg_add_device_key {
u8 connection_id; u8 connection_id;
u16 reserved; u16 reserved;
u32 key[8]; u32 key[8];
} __packed; };
struct icm_fr_pkg_add_device_key_response { struct icm_fr_pkg_add_device_key_response {
struct icm_pkg_header hdr; struct icm_pkg_header hdr;
...@@ -220,7 +225,7 @@ struct icm_fr_pkg_add_device_key_response { ...@@ -220,7 +225,7 @@ struct icm_fr_pkg_add_device_key_response {
u8 connection_key; u8 connection_key;
u8 connection_id; u8 connection_id;
u16 reserved; u16 reserved;
} __packed; };
struct icm_fr_pkg_challenge_device { struct icm_fr_pkg_challenge_device {
struct icm_pkg_header hdr; struct icm_pkg_header hdr;
...@@ -229,7 +234,7 @@ struct icm_fr_pkg_challenge_device { ...@@ -229,7 +234,7 @@ struct icm_fr_pkg_challenge_device {
u8 connection_id; u8 connection_id;
u16 reserved; u16 reserved;
u32 challenge[8]; u32 challenge[8];
} __packed; };
struct icm_fr_pkg_challenge_device_response { struct icm_fr_pkg_challenge_device_response {
struct icm_pkg_header hdr; struct icm_pkg_header hdr;
...@@ -239,7 +244,29 @@ struct icm_fr_pkg_challenge_device_response { ...@@ -239,7 +244,29 @@ struct icm_fr_pkg_challenge_device_response {
u16 reserved; u16 reserved;
u32 challenge[8]; u32 challenge[8];
u32 response[8]; u32 response[8];
} __packed; };
struct icm_fr_pkg_approve_xdomain {
struct icm_pkg_header hdr;
u16 reserved;
u16 link_info;
uuid_t remote_uuid;
u16 transmit_path;
u16 transmit_ring;
u16 receive_path;
u16 receive_ring;
};
struct icm_fr_pkg_approve_xdomain_response {
struct icm_pkg_header hdr;
u16 reserved;
u16 link_info;
uuid_t remote_uuid;
u16 transmit_path;
u16 transmit_ring;
u16 receive_path;
u16 receive_ring;
};
/* Alpine Ridge only messages */ /* Alpine Ridge only messages */
...@@ -247,7 +274,7 @@ struct icm_ar_pkg_get_route { ...@@ -247,7 +274,7 @@ struct icm_ar_pkg_get_route {
struct icm_pkg_header hdr; struct icm_pkg_header hdr;
u16 reserved; u16 reserved;
u16 link_info; u16 link_info;
} __packed; };
struct icm_ar_pkg_get_route_response { struct icm_ar_pkg_get_route_response {
struct icm_pkg_header hdr; struct icm_pkg_header hdr;
...@@ -255,6 +282,85 @@ struct icm_ar_pkg_get_route_response { ...@@ -255,6 +282,85 @@ struct icm_ar_pkg_get_route_response {
u16 link_info; u16 link_info;
u32 route_hi; u32 route_hi;
u32 route_lo; u32 route_lo;
} __packed; };
/* XDomain messages */
struct tb_xdomain_header {
u32 route_hi;
u32 route_lo;
u32 length_sn;
};
#define TB_XDOMAIN_LENGTH_MASK GENMASK(5, 0)
#define TB_XDOMAIN_SN_MASK GENMASK(28, 27)
#define TB_XDOMAIN_SN_SHIFT 27
enum tb_xdp_type {
UUID_REQUEST_OLD = 1,
UUID_RESPONSE = 2,
PROPERTIES_REQUEST,
PROPERTIES_RESPONSE,
PROPERTIES_CHANGED_REQUEST,
PROPERTIES_CHANGED_RESPONSE,
ERROR_RESPONSE,
UUID_REQUEST = 12,
};
struct tb_xdp_header {
struct tb_xdomain_header xd_hdr;
uuid_t uuid;
u32 type;
};
struct tb_xdp_properties {
struct tb_xdp_header hdr;
uuid_t src_uuid;
uuid_t dst_uuid;
u16 offset;
u16 reserved;
};
struct tb_xdp_properties_response {
struct tb_xdp_header hdr;
uuid_t src_uuid;
uuid_t dst_uuid;
u16 offset;
u16 data_length;
u32 generation;
u32 data[0];
};
/*
* Max length of data array single XDomain property response is allowed
* to carry.
*/
#define TB_XDP_PROPERTIES_MAX_DATA_LENGTH \
(((256 - 4 - sizeof(struct tb_xdp_properties_response))) / 4)
/* Maximum size of the total property block in dwords we allow */
#define TB_XDP_PROPERTIES_MAX_LENGTH 500
struct tb_xdp_properties_changed {
struct tb_xdp_header hdr;
uuid_t src_uuid;
};
struct tb_xdp_properties_changed_response {
struct tb_xdp_header hdr;
};
enum tb_xdp_error {
ERROR_SUCCESS,
ERROR_UNKNOWN_PACKET,
ERROR_UNKNOWN_DOMAIN,
ERROR_NOT_SUPPORTED,
ERROR_NOT_READY,
};
struct tb_xdp_error_response {
struct tb_xdp_header hdr;
u32 error;
};
#endif #endif
This diff is collapsed.
...@@ -170,4 +170,20 @@ static inline void be64_add_cpu(__be64 *var, u64 val) ...@@ -170,4 +170,20 @@ static inline void be64_add_cpu(__be64 *var, u64 val)
*var = cpu_to_be64(be64_to_cpu(*var) + val); *var = cpu_to_be64(be64_to_cpu(*var) + val);
} }
static inline 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 inline void be32_to_cpu_array(u32 *dst, const __be32 *src, size_t len)
{
int i;
for (i = 0; i < len; i++)
dst[i] = be32_to_cpu(src[i]);
}
#endif /* _LINUX_BYTEORDER_GENERIC_H */ #endif /* _LINUX_BYTEORDER_GENERIC_H */
...@@ -683,5 +683,31 @@ struct fsl_mc_device_id { ...@@ -683,5 +683,31 @@ struct fsl_mc_device_id {
const char obj_type[16]; const char obj_type[16];
}; };
/**
* struct tb_service_id - Thunderbolt service identifiers
* @match_flags: Flags used to match the structure
* @protocol_key: Protocol key the service supports
* @protocol_id: Protocol id the service supports
* @protocol_version: Version of the protocol
* @protocol_revision: Revision of the protocol software
* @driver_data: Driver specific data
*
* Thunderbolt XDomain services are exposed as devices where each device
* carries the protocol information the service supports. Thunderbolt
* XDomain service drivers match against that information.
*/
struct tb_service_id {
__u32 match_flags;
char protocol_key[8 + 1];
__u32 protocol_id;
__u32 protocol_version;
__u32 protocol_revision;
kernel_ulong_t driver_data;
};
#define TBSVC_MATCH_PROTOCOL_KEY 0x0001
#define TBSVC_MATCH_PROTOCOL_ID 0x0002
#define TBSVC_MATCH_PROTOCOL_VERSION 0x0004
#define TBSVC_MATCH_PROTOCOL_REVISION 0x0008
#endif /* LINUX_MOD_DEVICETABLE_H */ #endif /* LINUX_MOD_DEVICETABLE_H */
This diff is collapsed.
...@@ -206,5 +206,12 @@ int main(void) ...@@ -206,5 +206,12 @@ int main(void)
DEVID_FIELD(fsl_mc_device_id, vendor); DEVID_FIELD(fsl_mc_device_id, vendor);
DEVID_FIELD(fsl_mc_device_id, obj_type); DEVID_FIELD(fsl_mc_device_id, obj_type);
DEVID(tb_service_id);
DEVID_FIELD(tb_service_id, match_flags);
DEVID_FIELD(tb_service_id, protocol_key);
DEVID_FIELD(tb_service_id, protocol_id);
DEVID_FIELD(tb_service_id, protocol_version);
DEVID_FIELD(tb_service_id, protocol_revision);
return 0; return 0;
} }
...@@ -1301,6 +1301,31 @@ static int do_fsl_mc_entry(const char *filename, void *symval, ...@@ -1301,6 +1301,31 @@ static int do_fsl_mc_entry(const char *filename, void *symval,
} }
ADD_TO_DEVTABLE("fslmc", fsl_mc_device_id, do_fsl_mc_entry); ADD_TO_DEVTABLE("fslmc", fsl_mc_device_id, do_fsl_mc_entry);
/* Looks like: tbsvc:kSpNvNrN */
static int do_tbsvc_entry(const char *filename, void *symval, char *alias)
{
DEF_FIELD(symval, tb_service_id, match_flags);
DEF_FIELD_ADDR(symval, tb_service_id, protocol_key);
DEF_FIELD(symval, tb_service_id, protocol_id);
DEF_FIELD(symval, tb_service_id, protocol_version);
DEF_FIELD(symval, tb_service_id, protocol_revision);
strcpy(alias, "tbsvc:");
if (match_flags & TBSVC_MATCH_PROTOCOL_KEY)
sprintf(alias + strlen(alias), "k%s", *protocol_key);
else
strcat(alias + strlen(alias), "k*");
ADD(alias, "p", match_flags & TBSVC_MATCH_PROTOCOL_ID, protocol_id);
ADD(alias, "v", match_flags & TBSVC_MATCH_PROTOCOL_VERSION,
protocol_version);
ADD(alias, "r", match_flags & TBSVC_MATCH_PROTOCOL_REVISION,
protocol_revision);
add_wildcard(alias);
return 1;
}
ADD_TO_DEVTABLE("tbsvc", tb_service_id, do_tbsvc_entry);
/* Does namelen bytes of name exactly match the symbol? */ /* Does namelen bytes of name exactly match the symbol? */
static bool sym_is(const char *name, unsigned namelen, const char *symbol) static bool sym_is(const char *name, unsigned namelen, const char *symbol)
{ {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment