Commit d1ff7024 authored by Mika Westerberg's avatar Mika Westerberg Committed by David S. Miller

thunderbolt: Add support for XDomain discovery protocol

When two hosts are connected over a Thunderbolt cable, there is a
protocol they can use to communicate capabilities supported by the host.
The discovery protocol uses automatically configured control channel
(ring 0) and is build on top of request/response transactions using
special XDomain primitives provided by the Thunderbolt base protocol.

The capabilities consists of a root directory block of basic properties
used for identification of the host, and then there can be zero or more
directories each describing a Thunderbolt service and its capabilities.

Once both sides have discovered what is supported the two hosts can
setup high-speed DMA paths and transfer data to the other side using
whatever protocol was agreed based on the properties. The software
protocol used to communicate which DMA paths to enable is service
specific.

This patch adds support for the XDomain discovery protocol to the
Thunderbolt bus. We model each remote host connection as a Linux XDomain
device. For each Thunderbolt service found supported on the XDomain
device, we create Linux Thunderbolt service device which Thunderbolt
service drivers can then bind to based on the protocol identification
information retrieved from the property directory describing the
service.

This code is based on the work done by Amir Levy and Michael Jamet.
Signed-off-by: default avatarMichael Jamet <michael.jamet@intel.com>
Signed-off-by: default avatarMika Westerberg <mika.westerberg@linux.intel.com>
Reviewed-by: default avatarYehezkel Bernat <yehezkel.bernat@intel.com>
Reviewed-by: default avatarAndy Shevchenko <andriy.shevchenko@linux.intel.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent e69b71f8
......@@ -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
based mailbox before the device is power cycled. Writing
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
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 property.o
thunderbolt-objs += domain.o dma_port.o icm.o property.o xdomain.o
......@@ -368,10 +368,10 @@ 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
*/
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)
{
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)
......@@ -444,6 +444,8 @@ static void tb_ctl_rx_callback(struct tb_ring *ring, struct ring_frame *frame,
break;
case TB_CFG_PKG_EVENT:
case TB_CFG_PKG_XDOMAIN_RESP:
case TB_CFG_PKG_XDOMAIN_REQ:
if (*(__be32 *)(pkg->buffer + frame->size) != crc32) {
tb_ctl_err(pkg->ctl,
"RX: checksum mismatch, dropping packet\n");
......@@ -451,8 +453,9 @@ static void tb_ctl_rx_callback(struct tb_ring *ring, struct ring_frame *frame,
}
/* Fall through */
case TB_CFG_PKG_ICM_EVENT:
tb_ctl_handle_event(pkg->ctl, frame->eof, pkg, frame->size);
goto rx;
if (tb_ctl_handle_event(pkg->ctl, frame->eof, pkg, frame->size))
goto rx;
break;
default:
break;
......
......@@ -16,7 +16,7 @@
/* control channel */
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);
struct tb_ctl *tb_ctl_alloc(struct tb_nhi *nhi, event_cb cb, void *cb_data);
......
......@@ -20,6 +20,98 @@
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[] = {
[TB_SECURITY_NONE] = "none",
[TB_SECURITY_USER] = "user",
......@@ -52,6 +144,10 @@ static const struct attribute_group *domain_attr_groups[] = {
struct bus_type tb_bus_type = {
.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)
......@@ -128,17 +224,26 @@ struct tb *tb_domain_alloc(struct tb_nhi *nhi, size_t privsize)
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)
{
struct tb *tb = data;
if (!tb->cm_ops->handle_event) {
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)
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)
{
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)
......@@ -453,4 +641,5 @@ void tb_domain_exit(void)
bus_unregister(&tb_bus_type);
ida_destroy(&tb_domain_ida);
tb_switch_exit();
tb_xdomain_exit();
}
......@@ -60,6 +60,8 @@
* @get_route: Find a route string for given switch
* @device_connected: Handle device connected 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 mutex request_lock;
......@@ -74,6 +76,10 @@ struct icm {
const struct icm_pkg_header *hdr);
void (*device_disconnected)(struct tb *tb,
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 {
......@@ -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)
{
return tb_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)
......@@ -320,6 +329,51 @@ static int icm_fr_challenge_switch_key(struct tb *tb, struct tb_switch *sw,
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)
{
struct tb_switch *parent_sw;
......@@ -475,6 +529,141 @@ icm_fr_device_disconnected(struct tb *tb, const struct icm_pkg_header *hdr)
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)
{
struct pci_dev *parent;
......@@ -594,6 +783,12 @@ static void icm_handle_notification(struct work_struct *work)
case ICM_EVENT_DEVICE_DISCONNECTED:
icm->device_disconnected(tb, n->pkg);
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);
......@@ -927,6 +1122,10 @@ static void icm_unplug_children(struct tb_switch *sw)
if (tb_is_upstream_port(port))
continue;
if (port->xdomain) {
port->xdomain->is_unplugged = true;
continue;
}
if (!port->remote)
continue;
......@@ -943,6 +1142,13 @@ static void icm_free_unplugged_children(struct tb_switch *sw)
if (tb_is_upstream_port(port))
continue;
if (port->xdomain && port->xdomain->is_unplugged) {
tb_xdomain_remove(port->xdomain);
port->xdomain = NULL;
continue;
}
if (!port->remote)
continue;
......@@ -1009,8 +1215,10 @@ static int icm_start(struct tb *tb)
tb->root_switch->no_nvm_upgrade = x86_apple_machine;
ret = tb_switch_add(tb->root_switch);
if (ret)
if (ret) {
tb_switch_put(tb->root_switch);
tb->root_switch = NULL;
}
return ret;
}
......@@ -1042,6 +1250,8 @@ static const struct tb_cm_ops icm_fr_ops = {
.add_switch_key = icm_fr_add_switch_key,
.challenge_switch_key = icm_fr_challenge_switch_key,
.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)
......@@ -1064,6 +1274,8 @@ struct tb *icm_probe(struct tb_nhi *nhi)
icm->get_route = icm_fr_get_route;
icm->device_connected = icm_fr_device_connected;
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;
break;
......@@ -1077,6 +1289,8 @@ struct tb *icm_probe(struct tb_nhi *nhi)
icm->get_route = icm_ar_get_route;
icm->device_connected = icm_fr_device_connected;
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;
break;
}
......
......@@ -157,6 +157,8 @@ enum nhi_mailbox_cmd {
NHI_MAILBOX_SAVE_DEVS = 0x05,
NHI_MAILBOX_DISCONNECT_PCIE_PATHS = 0x06,
NHI_MAILBOX_DRV_UNLOADS = 0x07,
NHI_MAILBOX_DISCONNECT_PA = 0x10,
NHI_MAILBOX_DISCONNECT_PB = 0x11,
NHI_MAILBOX_ALLOW_ALL_DEVS = 0x23,
};
......
......@@ -171,11 +171,11 @@ static int nvm_authenticate_host(struct tb_switch *sw)
/*
* 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).
*/
if (!sw->safe_mode) {
ret = tb_domain_disconnect_pcie_paths(sw->tb);
ret = tb_domain_disconnect_all_paths(sw->tb);
if (ret)
return ret;
/*
......@@ -1363,6 +1363,9 @@ void tb_switch_remove(struct tb_switch *sw)
if (sw->ports[i].remote)
tb_switch_remove(sw->ports[i].remote->sw);
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)
......
......@@ -9,6 +9,7 @@
#include <linux/nvmem-provider.h>
#include <linux/pci.h>
#include <linux/thunderbolt.h>
#include <linux/uuid.h>
#include "tb_regs.h"
......@@ -109,14 +110,25 @@ struct 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_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 *remote;
struct tb_xdomain *xdomain;
int cap_phy;
u8 port;
bool disabled;
struct tb_port *dual_link_port;
u8 link_nr:1;
};
......@@ -189,6 +201,8 @@ struct tb_path {
* @add_switch_key: Add key to switch
* @challenge_switch_key: Challenge switch using key
* @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 {
int (*driver_ready)(struct tb *tb);
......@@ -205,6 +219,8 @@ struct tb_cm_ops {
int (*challenge_switch_key)(struct tb *tb, struct tb_switch *sw,
const u8 *challenge, u8 *response);
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);
};
static inline void *tb_priv(struct tb *tb)
......@@ -331,6 +347,8 @@ extern struct device_type tb_switch_type;
int tb_domain_init(void);
void tb_domain_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);
int tb_domain_add(struct tb *tb);
......@@ -343,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_challenge_switch_key(struct tb *tb, struct tb_switch *sw);
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)
{
......@@ -422,4 +443,14 @@ static inline u64 tb_downstream_route(struct tb_port *port)
| ((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
......@@ -101,11 +101,14 @@ enum icm_pkg_code {
ICM_CHALLENGE_DEVICE = 0x5,
ICM_ADD_DEVICE_KEY = 0x6,
ICM_GET_ROUTE = 0xa,
ICM_APPROVE_XDOMAIN = 0x10,
};
enum icm_event_code {
ICM_EVENT_DEVICE_CONNECTED = 3,
ICM_EVENT_DEVICE_DISCONNECTED = 4,
ICM_EVENT_XDOMAIN_CONNECTED = 6,
ICM_EVENT_XDOMAIN_DISCONNECTED = 7,
};
struct icm_pkg_header {
......@@ -188,6 +191,25 @@ struct icm_fr_event_device_disconnected {
u16 link_info;
};
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_pkg_header hdr;
uuid_t ep_uuid;
......@@ -224,6 +246,28 @@ struct icm_fr_pkg_challenge_device_response {
u32 response[8];
};
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 */
struct icm_ar_pkg_get_route {
......@@ -240,4 +284,83 @@ struct icm_ar_pkg_get_route_response {
u32 route_lo;
};
/* 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
This diff is collapsed.
......@@ -683,5 +683,31 @@ struct fsl_mc_device_id {
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 */
......@@ -17,6 +17,7 @@
#include <linux/device.h>
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/mod_devicetable.h>
#include <linux/uuid.h>
enum tb_cfg_pkg_type {
......@@ -77,6 +78,8 @@ struct tb {
};
extern struct bus_type tb_bus_type;
extern struct device_type tb_service_type;
extern struct device_type tb_xdomain_type;
#define TB_LINKS_PER_PHY_PORT 2
......@@ -155,4 +158,243 @@ struct tb_property *tb_property_get_next(struct tb_property_dir *dir,
property; \
property = tb_property_get_next(dir, property))
int tb_register_property_dir(const char *key, struct tb_property_dir *dir);
void tb_unregister_property_dir(const char *key, struct tb_property_dir *dir);
/**
* struct tb_xdomain - Cross-domain (XDomain) connection
* @dev: XDomain device
* @tb: Pointer to the domain
* @remote_uuid: UUID of the remote domain (host)
* @local_uuid: Cached local UUID
* @route: Route string the other domain can be reached
* @vendor: Vendor ID of the remote domain
* @device: Device ID of the demote domain
* @lock: Lock to serialize access to the following fields of this structure
* @vendor_name: Name of the vendor (or %NULL if not known)
* @device_name: Name of the device (or %NULL if not known)
* @is_unplugged: The XDomain is unplugged
* @resume: The XDomain is being resumed
* @transmit_path: HopID which the remote end expects us to transmit
* @transmit_ring: Local ring (hop) where outgoing packets are pushed
* @receive_path: HopID which we expect the remote end to transmit
* @receive_ring: Local ring (hop) where incoming packets arrive
* @service_ids: Used to generate IDs for the services
* @properties: Properties exported by the remote domain
* @property_block_gen: Generation of @properties
* @properties_lock: Lock protecting @properties.
* @get_properties_work: Work used to get remote domain properties
* @properties_retries: Number of times left to read properties
* @properties_changed_work: Work used to notify the remote domain that
* our properties have changed
* @properties_changed_retries: Number of times left to send properties
* changed notification
* @link: Root switch link the remote domain is connected (ICM only)
* @depth: Depth in the chain the remote domain is connected (ICM only)
*
* This structure represents connection across two domains (hosts).
* Each XDomain contains zero or more services which are exposed as
* &struct tb_service objects.
*
* Service drivers may access this structure if they need to enumerate
* non-standard properties but they need hold @lock when doing so
* because properties can be changed asynchronously in response to
* changes in the remote domain.
*/
struct tb_xdomain {
struct device dev;
struct tb *tb;
uuid_t *remote_uuid;
const uuid_t *local_uuid;
u64 route;
u16 vendor;
u16 device;
struct mutex lock;
const char *vendor_name;
const char *device_name;
bool is_unplugged;
bool resume;
u16 transmit_path;
u16 transmit_ring;
u16 receive_path;
u16 receive_ring;
struct ida service_ids;
struct tb_property_dir *properties;
u32 property_block_gen;
struct delayed_work get_properties_work;
int properties_retries;
struct delayed_work properties_changed_work;
int properties_changed_retries;
u8 link;
u8 depth;
};
int tb_xdomain_enable_paths(struct tb_xdomain *xd, u16 transmit_path,
u16 transmit_ring, u16 receive_path,
u16 receive_ring);
int tb_xdomain_disable_paths(struct tb_xdomain *xd);
struct tb_xdomain *tb_xdomain_find_by_uuid(struct tb *tb, const uuid_t *uuid);
static inline struct tb_xdomain *
tb_xdomain_find_by_uuid_locked(struct tb *tb, const uuid_t *uuid)
{
struct tb_xdomain *xd;
mutex_lock(&tb->lock);
xd = tb_xdomain_find_by_uuid(tb, uuid);
mutex_unlock(&tb->lock);
return xd;
}
static inline struct tb_xdomain *tb_xdomain_get(struct tb_xdomain *xd)
{
if (xd)
get_device(&xd->dev);
return xd;
}
static inline void tb_xdomain_put(struct tb_xdomain *xd)
{
if (xd)
put_device(&xd->dev);
}
static inline bool tb_is_xdomain(const struct device *dev)
{
return dev->type == &tb_xdomain_type;
}
static inline struct tb_xdomain *tb_to_xdomain(struct device *dev)
{
if (tb_is_xdomain(dev))
return container_of(dev, struct tb_xdomain, dev);
return NULL;
}
int tb_xdomain_response(struct tb_xdomain *xd, const void *response,
size_t size, enum tb_cfg_pkg_type type);
int tb_xdomain_request(struct tb_xdomain *xd, 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,
unsigned int timeout_msec);
/**
* tb_protocol_handler - Protocol specific handler
* @uuid: XDomain messages with this UUID are dispatched to this handler
* @callback: Callback called with the XDomain message. Returning %1
* here tells the XDomain core that the message was handled
* by this handler and should not be forwared to other
* handlers.
* @data: Data passed with the callback
* @list: Handlers are linked using this
*
* Thunderbolt services can hook into incoming XDomain requests by
* registering protocol handler. Only limitation is that the XDomain
* discovery protocol UUID cannot be registered since it is handled by
* the core XDomain code.
*
* The @callback must check that the message is really directed to the
* service the driver implements.
*/
struct tb_protocol_handler {
const uuid_t *uuid;
int (*callback)(const void *buf, size_t size, void *data);
void *data;
struct list_head list;
};
int tb_register_protocol_handler(struct tb_protocol_handler *handler);
void tb_unregister_protocol_handler(struct tb_protocol_handler *handler);
/**
* struct tb_service - Thunderbolt service
* @dev: XDomain device
* @id: ID of the service (shown in sysfs)
* @key: Protocol key from the properties directory
* @prtcid: Protocol ID from the properties directory
* @prtcvers: Protocol version from the properties directory
* @prtcrevs: Protocol software revision from the properties directory
* @prtcstns: Protocol settings mask from the properties directory
*
* Each domain exposes set of services it supports as collection of
* properties. For each service there will be one corresponding
* &struct tb_service. Service drivers are bound to these.
*/
struct tb_service {
struct device dev;
int id;
const char *key;
u32 prtcid;
u32 prtcvers;
u32 prtcrevs;
u32 prtcstns;
};
static inline struct tb_service *tb_service_get(struct tb_service *svc)
{
if (svc)
get_device(&svc->dev);
return svc;
}
static inline void tb_service_put(struct tb_service *svc)
{
if (svc)
put_device(&svc->dev);
}
static inline bool tb_is_service(const struct device *dev)
{
return dev->type == &tb_service_type;
}
static inline struct tb_service *tb_to_service(struct device *dev)
{
if (tb_is_service(dev))
return container_of(dev, struct tb_service, dev);
return NULL;
}
/**
* tb_service_driver - Thunderbolt service driver
* @driver: Driver structure
* @probe: Called when the driver is probed
* @remove: Called when the driver is removed (optional)
* @shutdown: Called at shutdown time to stop the service (optional)
* @id_table: Table of service identifiers the driver supports
*/
struct tb_service_driver {
struct device_driver driver;
int (*probe)(struct tb_service *svc, const struct tb_service_id *id);
void (*remove)(struct tb_service *svc);
void (*shutdown)(struct tb_service *svc);
const struct tb_service_id *id_table;
};
#define TB_SERVICE(key, id) \
.match_flags = TBSVC_MATCH_PROTOCOL_KEY | \
TBSVC_MATCH_PROTOCOL_ID, \
.protocol_key = (key), \
.protocol_id = (id)
int tb_register_service_driver(struct tb_service_driver *drv);
void tb_unregister_service_driver(struct tb_service_driver *drv);
static inline void *tb_service_get_drvdata(const struct tb_service *svc)
{
return dev_get_drvdata(&svc->dev);
}
static inline void tb_service_set_drvdata(struct tb_service *svc, void *data)
{
dev_set_drvdata(&svc->dev, data);
}
static inline struct tb_xdomain *tb_service_parent(struct tb_service *svc)
{
return tb_to_xdomain(svc->dev.parent);
}
#endif /* THUNDERBOLT_H_ */
......@@ -206,5 +206,12 @@ int main(void)
DEVID_FIELD(fsl_mc_device_id, vendor);
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;
}
......@@ -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);
/* 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? */
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