Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1756025AbdIRQM0 (ORCPT ); Mon, 18 Sep 2017 12:12:26 -0400 Received: from mx1.redhat.com ([209.132.183.28]:42648 "EHLO mx1.redhat.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752409AbdIRQMW (ORCPT ); Mon, 18 Sep 2017 12:12:22 -0400 DMARC-Filter: OpenDMARC Filter v1.3.2 mx1.redhat.com EA7B8806CF Authentication-Results: ext-mx02.extmail.prod.ext.phx2.redhat.com; dmarc=none (p=none dis=none) header.from=redhat.com Authentication-Results: ext-mx02.extmail.prod.ext.phx2.redhat.com; spf=fail smtp.mailfrom=dcbw@redhat.com Message-ID: <1505751137.11871.2.camel@redhat.com> Subject: Re: [PATCH 06/16] thunderbolt: Add support for XDomain discovery protocol From: Dan Williams To: Mika Westerberg , Greg Kroah-Hartman , "David S . Miller" Cc: Andreas Noever , Michael Jamet , Yehezkel Bernat , Amir Levy , Mario.Limonciello@dell.com, Lukas Wunner , Andy Shevchenko , linux-kernel@vger.kernel.org, netdev@vger.kernel.org Date: Mon, 18 Sep 2017 11:12:17 -0500 In-Reply-To: <20170918153049.44185-7-mika.westerberg@linux.intel.com> References: <20170918153049.44185-1-mika.westerberg@linux.intel.com> <20170918153049.44185-7-mika.westerberg@linux.intel.com> Content-Type: text/plain; charset="UTF-8" Mime-Version: 1.0 Content-Transfer-Encoding: 8bit X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.26]); Mon, 18 Sep 2017 16:12:22 +0000 (UTC) Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 91655 Lines: 3270 On Mon, 2017-09-18 at 18:30 +0300, Mika Westerberg wrote: > 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: Michael Jamet > Signed-off-by: Mika Westerberg > Reviewed-by: Yehezkel Bernat > --- >  Documentation/ABI/testing/sysfs-bus-thunderbolt |   48 + >  drivers/thunderbolt/Makefile                    |    2 +- >  drivers/thunderbolt/ctl.c                       |   11 +- >  drivers/thunderbolt/ctl.h                       |    2 +- >  drivers/thunderbolt/domain.c                    |  197 ++- >  drivers/thunderbolt/icm.c                       |  218 +++- >  drivers/thunderbolt/nhi.h                       |    2 + >  drivers/thunderbolt/switch.c                    |    7 +- >  drivers/thunderbolt/tb.h                        |   39 +- >  drivers/thunderbolt/tb_msgs.h                   |  123 ++ >  drivers/thunderbolt/xdomain.c                   | 1576 > +++++++++++++++++++++++ >  include/linux/mod_devicetable.h                 |   26 + >  include/linux/thunderbolt.h                     |  242 ++++ >  scripts/mod/devicetable-offsets.c               |    7 + >  scripts/mod/file2alias.c                        |   25 + >  15 files changed, 2507 insertions(+), 18 deletions(-) >  create mode 100644 drivers/thunderbolt/xdomain.c > > diff --git a/Documentation/ABI/testing/sysfs-bus-thunderbolt > b/Documentation/ABI/testing/sysfs-bus-thunderbolt > index 392bef5bd399..cb48850bd79b 100644 > --- a/Documentation/ABI/testing/sysfs-bus-thunderbolt > +++ b/Documentation/ABI/testing/sysfs-bus-thunderbolt > @@ -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/. >/key > +Date: Dec 2017 > +KernelVersion: 4.14 > +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/. >/modalias > +Date: Dec 2017 > +KernelVersion: 4.14 > +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/. >/prtcid > +Date: Dec 2017 > +KernelVersion: 4.14 > +Contact: thunderbolt-software@lists.01.org > +Description: This contains XDomain protocol identifier the > XDomain > + service supports. > + > +What: /sys/bus/thunderbolt/devices/. >/prtcvers > +Date: Dec 2017 > +KernelVersion: 4.14 > +Contact: thunderbolt-software@lists.01.org > +Description: This contains XDomain protocol version the > XDomain > + service supports. > + > +What: /sys/bus/thunderbolt/devices/. >/prtcrevs > +Date: Dec 2017 > +KernelVersion: 4.14 > +Contact: thunderbolt-software@lists.01.org > +Description: This contains XDomain software version the > XDomain > + service supports. > + > +What: /sys/bus/thunderbolt/devices/. >/prtcstns > +Date: Dec 2017 > +KernelVersion: 4.14 > +Contact: thunderbolt-software@lists.01.org > +Description: This contains XDomain service specific settings > as > + bitmask. Format: %x > diff --git a/drivers/thunderbolt/Makefile > b/drivers/thunderbolt/Makefile > index 7afd21f5383a..f2f0de27252b 100644 > --- a/drivers/thunderbolt/Makefile > +++ b/drivers/thunderbolt/Makefile > @@ -1,3 +1,3 @@ >  obj-${CONFIG_THUNDERBOLT} := thunderbolt.o >  thunderbolt-objs := nhi.o ctl.o tb.o switch.o cap.o path.o > tunnel_pci.o 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 > diff --git a/drivers/thunderbolt/ctl.c b/drivers/thunderbolt/ctl.c > index e6a4c9458c76..46e393c5fd1d 100644 > --- a/drivers/thunderbolt/ctl.c > +++ b/drivers/thunderbolt/ctl.c > @@ -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; > diff --git a/drivers/thunderbolt/ctl.h b/drivers/thunderbolt/ctl.h > index d0f21e1e0b8b..85c49dd301ea 100644 > --- a/drivers/thunderbolt/ctl.h > +++ b/drivers/thunderbolt/ctl.h > @@ -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); > diff --git a/drivers/thunderbolt/domain.c > b/drivers/thunderbolt/domain.c > index 9f2dcd48974d..29d6436ec8ce 100644 > --- a/drivers/thunderbolt/domain.c > +++ b/drivers/thunderbolt/domain.c > @@ -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); Could you pass 'dev' to the probe function so that things like the network sub-driver can sensibly link the netdev to the parent hardware in sysfs with SET_NETDEV_DEV()? Dan > +} > + > +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 reseting 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(); >  } > diff --git a/drivers/thunderbolt/icm.c b/drivers/thunderbolt/icm.c > index 8c22b91ed040..ab02d13f40b7 100644 > --- a/drivers/thunderbolt/icm.c > +++ b/drivers/thunderbolt/icm.c > @@ -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; >   } > diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h > index 5b5bb2c436be..0e05828983db 100644 > --- a/drivers/thunderbolt/nhi.h > +++ b/drivers/thunderbolt/nhi.h > @@ -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, >  }; >   > diff --git a/drivers/thunderbolt/switch.c > b/drivers/thunderbolt/switch.c > index 53f40c57df59..dfc357d33e1e 100644 > --- a/drivers/thunderbolt/switch.c > +++ b/drivers/thunderbolt/switch.c > @@ -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) > diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h > index ea21d927bd09..74af9d4929ab 100644 > --- a/drivers/thunderbolt/tb.h > +++ b/drivers/thunderbolt/tb.h > @@ -9,6 +9,7 @@ >   >  #include >  #include > +#include >  #include >   >  #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 > diff --git a/drivers/thunderbolt/tb_msgs.h > b/drivers/thunderbolt/tb_msgs.h > index fe3039b05da6..2a76908537a6 100644 > --- a/drivers/thunderbolt/tb_msgs.h > +++ b/drivers/thunderbolt/tb_msgs.h > @@ -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; >  } __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; > +} __packed; > + > +struct icm_fr_event_xdomain_disconnected { > + struct icm_pkg_header hdr; > + u16 reserved; > + u16 link_info; > + uuid_t remote_uuid; > +} __packed; > + >  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]; >  } __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; > +} __packed; > + > +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; > +} __packed; > + >  /* Alpine Ridge only messages */ >   >  struct icm_ar_pkg_get_route { > @@ -240,4 +284,83 @@ struct icm_ar_pkg_get_route_response { >   u32 route_lo; >  } __packed; >   > +/* XDomain messages */ > + > +struct tb_xdomain_header { > + u32 route_hi; > + u32 route_lo; > + u32 length_sn; > +} __packed; > + > +#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; > +} __packed; > + > +struct tb_xdp_properties { > + struct tb_xdp_header hdr; > + uuid_t src_uuid; > + uuid_t dst_uuid; > + u16 offset; > + u16 reserved; > +} __packed; > + > +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]; > +} __packed; > + > +/* > + * 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; > +} __packed; > + > +struct tb_xdp_properties_changed_response { > + struct tb_xdp_header hdr; > +} __packed; > + > +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; > +} __packed; > + >  #endif > diff --git a/drivers/thunderbolt/xdomain.c > b/drivers/thunderbolt/xdomain.c > new file mode 100644 > index 000000000000..1b929be8fdd6 > --- /dev/null > +++ b/drivers/thunderbolt/xdomain.c > @@ -0,0 +1,1576 @@ > +/* > + * Thunderbolt XDomain discovery protocol support > + * > + * Copyright (C) 2017, Intel Corporation > + * Authors: Michael Jamet > + *          Mika Westerberg > + * > + * This program is free software; you can redistribute it and/or > modify > + * it under the terms of the GNU General Public License version 2 as > + * published by the Free Software Foundation. > + */ > + > +#include > +#include > +#include > +#include > +#include > +#include > + > +#include "tb.h" > + > +#define XDOMAIN_DEFAULT_TIMEOUT 5000 /* ms */ > +#define XDOMAIN_PROPERTIES_RETRIES 60 > +#define XDOMAIN_PROPERTIES_CHANGED_RETRIES 10 > + > +struct xdomain_request_work { > + struct work_struct work; > + struct tb_xdp_header *pkg; > + struct tb *tb; > +}; > + > +/* Serializes access to the properties and protocol handlers below > */ > +static DEFINE_MUTEX(xdomain_lock); > + > +/* Properties exposed to the remote domains */ > +static struct tb_property_dir *xdomain_property_dir; > +static u32 *xdomain_property_block; > +static u32 xdomain_property_block_len; > +static u32 xdomain_property_block_gen; > + > +/* Additional protocol handlers */ > +static LIST_HEAD(protocol_handlers); > + > +/* UUID for XDomain discovery protocol */ > +static const uuid_t tb_xdp_uuid = > + UUID_INIT(0xb638d70e, 0x42ff, 0x40bb, > +   0x97, 0xc2, 0x90, 0xe2, 0xc0, 0xb2, 0xff, 0x07); > + > +static bool tb_xdomain_match(const struct tb_cfg_request *req, > +      const struct ctl_pkg *pkg) > +{ > + switch (pkg->frame.eof) { > + case TB_CFG_PKG_ERROR: > + return true; > + > + case TB_CFG_PKG_XDOMAIN_RESP: { > + const struct tb_xdp_header *res_hdr = pkg->buffer; > + const struct tb_xdp_header *req_hdr = req->request; > + u8 req_seq, res_seq; > + > + if (pkg->frame.size < req->response_size / 4) > + return false; > + > + /* Make sure route matches */ > + if ((res_hdr->xd_hdr.route_hi & ~BIT(31)) != > +      req_hdr->xd_hdr.route_hi) > + return false; > + if ((res_hdr->xd_hdr.route_lo) != req_hdr- > >xd_hdr.route_lo) > + return false; > + > + /* Then check that the sequence number matches */ > + res_seq = res_hdr->xd_hdr.length_sn & > TB_XDOMAIN_SN_MASK; > + res_seq >>= TB_XDOMAIN_SN_SHIFT; > + req_seq = req_hdr->xd_hdr.length_sn & > TB_XDOMAIN_SN_MASK; > + req_seq >>= TB_XDOMAIN_SN_SHIFT; > + if (res_seq != req_seq) > + return false; > + > + /* Check that the XDomain protocol matches */ > + if (!uuid_equal(&res_hdr->uuid, &req_hdr->uuid)) > + return false; > + > + return true; > + } > + > + default: > + return false; > + } > +} > + > +static bool tb_xdomain_copy(struct tb_cfg_request *req, > +     const struct ctl_pkg *pkg) > +{ > + memcpy(req->response, pkg->buffer, req->response_size); > + req->result.err = 0; > + return true; > +} > + > +static void response_ready(void *data) > +{ > + tb_cfg_request_put(data); > +} > + > +static int __tb_xdomain_response(struct tb_ctl *ctl, const void > *response, > +  size_t size, enum tb_cfg_pkg_type > type) > +{ > + struct tb_cfg_request *req; > + > + req = tb_cfg_request_alloc(); > + if (!req) > + return -ENOMEM; > + > + req->match = tb_xdomain_match; > + req->copy = tb_xdomain_copy; > + req->request = response; > + req->request_size = size; > + req->request_type = type; > + > + return tb_cfg_request(ctl, req, response_ready, req); > +} > + > +/** > + * tb_xdomain_response() - Send a XDomain response message > + * @xd: XDomain to send the message > + * @response: Response to send > + * @size: Size of the response > + * @type: PDF type of the response > + * > + * This can be used to send a XDomain response message to the other > + * domain. No response for the message is expected. > + * > + * Return: %0 in case of success and negative errno in case of > failure > + */ > +int tb_xdomain_response(struct tb_xdomain *xd, const void *response, > + size_t size, enum tb_cfg_pkg_type type) > +{ > + return __tb_xdomain_response(xd->tb->ctl, response, size, > type); > +} > +EXPORT_SYMBOL_GPL(tb_xdomain_response); > + > +static int __tb_xdomain_request(struct tb_ctl *ctl, const void > *request, > + size_t request_size, enum tb_cfg_pkg_type request_type, void > *response, > + size_t response_size, enum tb_cfg_pkg_type response_type, > + unsigned int timeout_msec) > +{ > + struct tb_cfg_request *req; > + struct tb_cfg_result res; > + > + req = tb_cfg_request_alloc(); > + if (!req) > + return -ENOMEM; > + > + req->match = tb_xdomain_match; > + req->copy = tb_xdomain_copy; > + req->request = request; > + req->request_size = request_size; > + req->request_type = request_type; > + req->response = response; > + req->response_size = response_size; > + req->response_type = response_type; > + > + res = tb_cfg_request_sync(ctl, req, timeout_msec); > + > + tb_cfg_request_put(req); > + > + return res.err == 1 ? -EIO : res.err; > +} > + > +/** > + * tb_xdomain_request() - Send a XDomain request > + * @xd: XDomain to send the request > + * @request: Request to send > + * @request_size: Size of the request in bytes > + * @request_type: PDF type of the request > + * @response: Response is copied here > + * @response_size: Expected size of the response in bytes > + * @response_type: Expected PDF type of the response > + * @timeout_msec: Timeout in milliseconds to wait for the response > + * > + * This function can be used to send XDomain control channel > messages to > + * the other domain. The function waits until the response is > received > + * or when timeout triggers. Whichever comes first. > + * > + * Return: %0 in case of success and negative errno in case of > failure > + */ > +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) > +{ > + return __tb_xdomain_request(xd->tb->ctl, request, > request_size, > +     request_type, response, > response_size, > +     response_type, timeout_msec); > +} > +EXPORT_SYMBOL_GPL(tb_xdomain_request); > + > +static inline void tb_xdp_fill_header(struct tb_xdp_header *hdr, u64 > route, > + u8 sequence, enum tb_xdp_type type, size_t size) > +{ > + u32 length_sn; > + > + length_sn = (size - sizeof(hdr->xd_hdr)) / 4; > + length_sn |= (sequence << TB_XDOMAIN_SN_SHIFT) & > TB_XDOMAIN_SN_MASK; > + > + hdr->xd_hdr.route_hi = upper_32_bits(route); > + hdr->xd_hdr.route_lo = lower_32_bits(route); > + hdr->xd_hdr.length_sn = length_sn; > + hdr->type = type; > + memcpy(&hdr->uuid, &tb_xdp_uuid, sizeof(tb_xdp_uuid)); > +} > + > +static int tb_xdp_handle_error(const struct tb_xdp_header *hdr) > +{ > + const struct tb_xdp_error_response *error; > + > + if (hdr->type != ERROR_RESPONSE) > + return 0; > + > + error = (const struct tb_xdp_error_response *)hdr; > + > + switch (error->error) { > + case ERROR_UNKNOWN_PACKET: > + case ERROR_UNKNOWN_DOMAIN: > + return -EIO; > + case ERROR_NOT_SUPPORTED: > + return -ENOTSUPP; > + case ERROR_NOT_READY: > + return -EAGAIN; > + default: > + break; > + } > + > + return 0; > +} > + > +static int tb_xdp_error_response(struct tb_ctl *ctl, u64 route, u8 > sequence, > +  enum tb_xdp_error error) > +{ > + struct tb_xdp_error_response res; > + > + memset(&res, 0, sizeof(res)); > + tb_xdp_fill_header(&res.hdr, route, sequence, > ERROR_RESPONSE, > +    sizeof(res)); > + res.error = error; > + > + return __tb_xdomain_response(ctl, &res, sizeof(res), > +      TB_CFG_PKG_XDOMAIN_RESP); > +} > + > +static int tb_xdp_properties_request(struct tb_ctl *ctl, u64 route, > + const uuid_t *src_uuid, const uuid_t *dst_uuid, int retry, > + u32 **block, u32 *generation) > +{ > + struct tb_xdp_properties_response *res; > + struct tb_xdp_properties req; > + u16 data_len, len; > + size_t total_size; > + u32 *data = NULL; > + int ret; > + > + total_size = sizeof(*res) + > TB_XDP_PROPERTIES_MAX_DATA_LENGTH * 4; > + res = kzalloc(total_size, GFP_KERNEL); > + if (!res) > + return -ENOMEM; > + > + memset(&req, 0, sizeof(req)); > + tb_xdp_fill_header(&req.hdr, route, retry % 4, > PROPERTIES_REQUEST, > +    sizeof(req)); > + memcpy(&req.src_uuid, src_uuid, sizeof(*src_uuid)); > + memcpy(&req.dst_uuid, dst_uuid, sizeof(*dst_uuid)); > + > + len = 0; > + data_len = 0; > + > + do { > + ret = __tb_xdomain_request(ctl, &req, sizeof(req), > +    TB_CFG_PKG_XDOMAIN_REQ, > res, > +    total_size, > TB_CFG_PKG_XDOMAIN_RESP, > +    XDOMAIN_DEFAULT_TIMEOUT); > + if (ret) > + goto err; > + > + ret = tb_xdp_handle_error(&res->hdr); > + if (ret) > + goto err; > + > + /* > +  * Package length includes the whole payload without > the > +  * XDomain header. Validate first that the package > is at > +  * least size of the response structure. > +  */ > + len = res->hdr.xd_hdr.length_sn & > TB_XDOMAIN_LENGTH_MASK; > + if (len < sizeof(*res) / 4) { > + ret = -EINVAL; > + goto err; > + } > + > + len += sizeof(res->hdr.xd_hdr) / 4; > + len -= sizeof(*res) / 4; > + > + if (res->offset != req.offset) { > + ret = -EINVAL; > + goto err; > + } > + > + /* > +  * First time allocate block that has enough space > for > +  * the whole properties block. > +  */ > + if (!data) { > + data_len = res->data_length; > + if (data_len > TB_XDP_PROPERTIES_MAX_LENGTH) > { > + ret = -E2BIG; > + goto err; > + } > + > + data = kcalloc(data_len, sizeof(u32), > GFP_KERNEL); > + if (!data) { > + ret = -ENOMEM; > + goto err; > + } > + } > + > + memcpy(data + req.offset, res->data, len * 4); > + req.offset += len; > + } while (!data_len || req.offset < data_len); > + > + *block = data; > + *generation = res->generation; > + > + kfree(res); > + > + return data_len; > + > +err: > + kfree(data); > + kfree(res); > + > + return ret; > +} > + > +static int tb_xdp_properties_response(struct tb *tb, struct tb_ctl > *ctl, > + u64 route, u8 sequence, const uuid_t *src_uuid, > + const struct tb_xdp_properties *req) > +{ > + struct tb_xdp_properties_response *res; > + size_t total_size; > + u16 len; > + int ret; > + > + /* > +  * Currently we expect all requests to be directed to us. > The > +  * protocol supports forwarding, though which we might add > +  * support later on. > +  */ > + if (!uuid_equal(src_uuid, &req->dst_uuid)) { > + tb_xdp_error_response(ctl, route, sequence, > +       ERROR_UNKNOWN_DOMAIN); > + return 0; > + } > + > + mutex_lock(&xdomain_lock); > + > + if (req->offset >= xdomain_property_block_len) { > + mutex_unlock(&xdomain_lock); > + return -EINVAL; > + } > + > + len = xdomain_property_block_len - req->offset; > + len = min_t(u16, len, TB_XDP_PROPERTIES_MAX_DATA_LENGTH); > + total_size = sizeof(*res) + len * 4; > + > + res = kzalloc(total_size, GFP_KERNEL); > + if (!res) { > + mutex_unlock(&xdomain_lock); > + return -ENOMEM; > + } > + > + tb_xdp_fill_header(&res->hdr, route, sequence, > PROPERTIES_RESPONSE, > +    total_size); > + res->generation = xdomain_property_block_gen; > + res->data_length = xdomain_property_block_len; > + res->offset = req->offset; > + uuid_copy(&res->src_uuid, src_uuid); > + uuid_copy(&res->dst_uuid, &req->src_uuid); > + memcpy(res->data, &xdomain_property_block[req->offset], len > * 4); > + > + mutex_unlock(&xdomain_lock); > + > + ret = __tb_xdomain_response(ctl, res, total_size, > +     TB_CFG_PKG_XDOMAIN_RESP); > + > + kfree(res); > + return ret; > +} > + > +static int tb_xdp_properties_changed_request(struct tb_ctl *ctl, u64 > route, > +      int retry, const uuid_t > *uuid) > +{ > + struct tb_xdp_properties_changed_response res; > + struct tb_xdp_properties_changed req; > + int ret; > + > + memset(&req, 0, sizeof(req)); > + tb_xdp_fill_header(&req.hdr, route, retry % 4, > +    PROPERTIES_CHANGED_REQUEST, sizeof(req)); > + uuid_copy(&req.src_uuid, uuid); > + > + memset(&res, 0, sizeof(res)); > + ret = __tb_xdomain_request(ctl, &req, sizeof(req), > +    TB_CFG_PKG_XDOMAIN_REQ, &res, > sizeof(res), > +    TB_CFG_PKG_XDOMAIN_RESP, > +    XDOMAIN_DEFAULT_TIMEOUT); > + if (ret) > + return ret; > + > + return tb_xdp_handle_error(&res.hdr); > +} > + > +static int > +tb_xdp_properties_changed_response(struct tb_ctl *ctl, u64 route, u8 > sequence) > +{ > + struct tb_xdp_properties_changed_response res; > + > + memset(&res, 0, sizeof(res)); > + tb_xdp_fill_header(&res.hdr, route, sequence, > +    PROPERTIES_CHANGED_RESPONSE, > sizeof(res)); > + return __tb_xdomain_response(ctl, &res, sizeof(res), > +      TB_CFG_PKG_XDOMAIN_RESP); > +} > + > +/** > + * tb_register_protocol_handler() - Register protocol handler > + * @handler: Handler to register > + * > + * This allows XDomain service drivers to hook into incoming XDomain > + * messages. After this function is called the service driver needs > to > + * be able to handle calls to callback whenever a package with the > + * registered protocol is received. > + */ > +int tb_register_protocol_handler(struct tb_protocol_handler > *handler) > +{ > + if (!handler->uuid || !handler->callback) > + return -EINVAL; > + if (uuid_equal(handler->uuid, &tb_xdp_uuid)) > + return -EINVAL; > + > + mutex_lock(&xdomain_lock); > + list_add_tail(&handler->list, &protocol_handlers); > + mutex_unlock(&xdomain_lock); > + > + return 0; > +} > +EXPORT_SYMBOL_GPL(tb_register_protocol_handler); > + > +/** > + * tb_unregister_protocol_handler() - Unregister protocol handler > + * @handler: Handler to unregister > + * > + * Removes the previously registered protocol handler. > + */ > +void tb_unregister_protocol_handler(struct tb_protocol_handler > *handler) > +{ > + mutex_lock(&xdomain_lock); > + list_del_init(&handler->list); > + mutex_unlock(&xdomain_lock); > +} > +EXPORT_SYMBOL_GPL(tb_unregister_protocol_handler); > + > +static void tb_xdp_handle_request(struct work_struct *work) > +{ > + struct xdomain_request_work *xw = container_of(work, > typeof(*xw), work); > + const struct tb_xdp_header *pkg = xw->pkg; > + const struct tb_xdomain_header *xhdr = &pkg->xd_hdr; > + struct tb *tb = xw->tb; > + struct tb_ctl *ctl = tb->ctl; > + const uuid_t *uuid; > + int ret = 0; > + u8 sequence; > + u64 route; > + > + route = ((u64)xhdr->route_hi << 32 | xhdr->route_lo) & > ~BIT_ULL(63); > + sequence = xhdr->length_sn & TB_XDOMAIN_SN_MASK; > + sequence >>= TB_XDOMAIN_SN_SHIFT; > + > + mutex_lock(&tb->lock); > + if (tb->root_switch) > + uuid = tb->root_switch->uuid; > + else > + uuid = NULL; > + mutex_unlock(&tb->lock); > + > + if (!uuid) { > + tb_xdp_error_response(ctl, route, sequence, > ERROR_NOT_READY); > + goto out; > + } > + > + switch (pkg->type) { > + case PROPERTIES_REQUEST: > + ret = tb_xdp_properties_response(tb, ctl, route, > sequence, uuid, > + (const struct tb_xdp_properties *)pkg); > + break; > + > + case PROPERTIES_CHANGED_REQUEST: { > + const struct tb_xdp_properties_changed *xchg = > + (const struct tb_xdp_properties_changed > *)pkg; > + struct tb_xdomain *xd; > + > + ret = tb_xdp_properties_changed_response(ctl, route, > sequence); > + > + /* > +  * Since the properties have been changed, let's > update > +  * the xdomain related to this connection as well in > +  * case there is a change in services it offers. > +  */ > + xd = tb_xdomain_find_by_uuid_locked(tb, &xchg- > >src_uuid); > + if (xd) { > + queue_delayed_work(tb->wq, &xd- > >get_properties_work, > +    msecs_to_jiffies(50)); > + tb_xdomain_put(xd); > + } > + > + break; > + } > + > + default: > + break; > + } > + > + if (ret) { > + tb_warn(tb, "failed to send XDomain response for > %#x\n", > + pkg->type); > + } > + > +out: > + kfree(xw->pkg); > + kfree(xw); > +} > + > +static void > +tb_xdp_schedule_request(struct tb *tb, const struct tb_xdp_header > *hdr, > + size_t size) > +{ > + struct xdomain_request_work *xw; > + > + xw = kmalloc(sizeof(*xw), GFP_KERNEL); > + if (!xw) > + return; > + > + INIT_WORK(&xw->work, tb_xdp_handle_request); > + xw->pkg = kmemdup(hdr, size, GFP_KERNEL); > + xw->tb = tb; > + > + queue_work(tb->wq, &xw->work); > +} > + > +/** > + * tb_register_service_driver() - Register XDomain service driver > + * @drv: Driver to register > + * > + * Registers new service driver from @drv to the bus. > + */ > +int tb_register_service_driver(struct tb_service_driver *drv) > +{ > + drv->driver.bus = &tb_bus_type; > + return driver_register(&drv->driver); > +} > +EXPORT_SYMBOL_GPL(tb_register_service_driver); > + > +/** > + * tb_unregister_service_driver() - Unregister XDomain service > driver > + * @xdrv: Driver to unregister > + * > + * Unregisters XDomain service driver from the bus. > + */ > +void tb_unregister_service_driver(struct tb_service_driver *drv) > +{ > + driver_unregister(&drv->driver); > +} > +EXPORT_SYMBOL_GPL(tb_unregister_service_driver); > + > +static ssize_t key_show(struct device *dev, struct device_attribute > *attr, > + char *buf) > +{ > + struct tb_service *svc = container_of(dev, struct > tb_service, dev); > + > + /* > +  * It should be null terminated but anything else is pretty > much > +  * allowed. > +  */ > + return sprintf(buf, "%*pEp\n", (int)strlen(svc->key), svc- > >key); > +} > +static DEVICE_ATTR_RO(key); > + > +static int get_modalias(struct tb_service *svc, char *buf, size_t > size) > +{ > + return snprintf(buf, size, "tbsvc:k%sp%08Xv%08Xr%08X", svc- > >key, > + svc->prtcid, svc->prtcvers, svc->prtcrevs); > +} > + > +static ssize_t modalias_show(struct device *dev, struct > device_attribute *attr, > +      char *buf) > +{ > + struct tb_service *svc = container_of(dev, struct > tb_service, dev); > + > + /* Full buffer size except new line and null termination */ > + get_modalias(svc, buf, PAGE_SIZE - 2); > + return sprintf(buf, "%s\n", buf); > +} > +static DEVICE_ATTR_RO(modalias); > + > +static ssize_t prtcid_show(struct device *dev, struct > device_attribute *attr, > +    char *buf) > +{ > + struct tb_service *svc = container_of(dev, struct > tb_service, dev); > + > + return sprintf(buf, "%u\n", svc->prtcid); > +} > +static DEVICE_ATTR_RO(prtcid); > + > +static ssize_t prtcvers_show(struct device *dev, struct > device_attribute *attr, > +      char *buf) > +{ > + struct tb_service *svc = container_of(dev, struct > tb_service, dev); > + > + return sprintf(buf, "%u\n", svc->prtcvers); > +} > +static DEVICE_ATTR_RO(prtcvers); > + > +static ssize_t prtcrevs_show(struct device *dev, struct > device_attribute *attr, > +      char *buf) > +{ > + struct tb_service *svc = container_of(dev, struct > tb_service, dev); > + > + return sprintf(buf, "%u\n", svc->prtcrevs); > +} > +static DEVICE_ATTR_RO(prtcrevs); > + > +static ssize_t prtcstns_show(struct device *dev, struct > device_attribute *attr, > +      char *buf) > +{ > + struct tb_service *svc = container_of(dev, struct > tb_service, dev); > + > + return sprintf(buf, "0x%08x\n", svc->prtcstns); > +} > +static DEVICE_ATTR_RO(prtcstns); > + > +static struct attribute *tb_service_attrs[] = { > + &dev_attr_key.attr, > + &dev_attr_modalias.attr, > + &dev_attr_prtcid.attr, > + &dev_attr_prtcvers.attr, > + &dev_attr_prtcrevs.attr, > + &dev_attr_prtcstns.attr, > + NULL, > +}; > + > +static struct attribute_group tb_service_attr_group = { > + .attrs = tb_service_attrs, > +}; > + > +static const struct attribute_group *tb_service_attr_groups[] = { > + &tb_service_attr_group, > + NULL, > +}; > + > +static int tb_service_uevent(struct device *dev, struct > kobj_uevent_env *env) > +{ > + struct tb_service *svc = container_of(dev, struct > tb_service, dev); > + char modalias[64]; > + > + get_modalias(svc, modalias, sizeof(modalias)); > + return add_uevent_var(env, "MODALIAS=%s", modalias); > +} > + > +static void tb_service_release(struct device *dev) > +{ > + struct tb_service *svc = container_of(dev, struct > tb_service, dev); > + struct tb_xdomain *xd = tb_service_parent(svc); > + > + ida_simple_remove(&xd->service_ids, svc->id); > + kfree(svc->key); > + kfree(svc); > +} > + > +struct device_type tb_service_type = { > + .name = "thunderbolt_service", > + .groups = tb_service_attr_groups, > + .uevent = tb_service_uevent, > + .release = tb_service_release, > +}; > +EXPORT_SYMBOL_GPL(tb_service_type); > + > +static int remove_missing_service(struct device *dev, void *data) > +{ > + struct tb_xdomain *xd = data; > + struct tb_service *svc; > + > + svc = tb_to_service(dev); > + if (!svc) > + return 0; > + > + if (!tb_property_find(xd->properties, svc->key, > +       TB_PROPERTY_TYPE_DIRECTORY)) > + device_unregister(dev); > + > + return 0; > +} > + > +static int find_service(struct device *dev, void *data) > +{ > + const struct tb_property *p = data; > + struct tb_service *svc; > + > + svc = tb_to_service(dev); > + if (!svc) > + return 0; > + > + return !strcmp(svc->key, p->key); > +} > + > +static int populate_service(struct tb_service *svc, > +     struct tb_property *property) > +{ > + struct tb_property_dir *dir = property->value.dir; > + struct tb_property *p; > + > + /* Fill in standard properties */ > + p = tb_property_find(dir, "prtcid", TB_PROPERTY_TYPE_VALUE); > + if (p) > + svc->prtcid = p->value.immediate; > + p = tb_property_find(dir, "prtcvers", > TB_PROPERTY_TYPE_VALUE); > + if (p) > + svc->prtcvers = p->value.immediate; > + p = tb_property_find(dir, "prtcrevs", > TB_PROPERTY_TYPE_VALUE); > + if (p) > + svc->prtcrevs = p->value.immediate; > + p = tb_property_find(dir, "prtcstns", > TB_PROPERTY_TYPE_VALUE); > + if (p) > + svc->prtcstns = p->value.immediate; > + > + svc->key = kstrdup(property->key, GFP_KERNEL); > + if (!svc->key) > + return -ENOMEM; > + > + return 0; > +} > + > +static void enumerate_services(struct tb_xdomain *xd) > +{ > + struct tb_service *svc; > + struct tb_property *p; > + struct device *dev; > + > + /* > +  * First remove all services that are not available anymore > in > +  * the updated property block. > +  */ > + device_for_each_child_reverse(&xd->dev, xd, > remove_missing_service); > + > + /* Then re-enumerate properties creating new services as we > go */ > + tb_property_for_each(xd->properties, p) { > + if (p->type != TB_PROPERTY_TYPE_DIRECTORY) > + continue; > + > + /* If the service exists already we are fine */ > + dev = device_find_child(&xd->dev, p, find_service); > + if (dev) { > + put_device(dev); > + continue; > + } > + > + svc = kzalloc(sizeof(*svc), GFP_KERNEL); > + if (!svc) > + break; > + > + if (populate_service(svc, p)) { > + kfree(svc); > + break; > + } > + > + svc->id = ida_simple_get(&xd->service_ids, 0, 0, > GFP_KERNEL); > + svc->dev.bus = &tb_bus_type; > + svc->dev.type = &tb_service_type; > + svc->dev.parent = &xd->dev; > + dev_set_name(&svc->dev, "%s.%d", dev_name(&xd->dev), > svc->id); > + > + if (device_register(&svc->dev)) { > + put_device(&svc->dev); > + break; > + } > + } > +} > + > +static int populate_properties(struct tb_xdomain *xd, > +        struct tb_property_dir *dir) > +{ > + const struct tb_property *p; > + > + /* Required properties */ > + p = tb_property_find(dir, "deviceid", > TB_PROPERTY_TYPE_VALUE); > + if (!p) > + return -EINVAL; > + xd->device = p->value.immediate; > + > + p = tb_property_find(dir, "vendorid", > TB_PROPERTY_TYPE_VALUE); > + if (!p) > + return -EINVAL; > + xd->vendor = p->value.immediate; > + > + kfree(xd->device_name); > + xd->device_name = NULL; > + kfree(xd->vendor_name); > + xd->vendor_name = NULL; > + > + /* Optional properties */ > + p = tb_property_find(dir, "deviceid", > TB_PROPERTY_TYPE_TEXT); > + if (p) > + xd->device_name = kstrdup(p->value.text, > GFP_KERNEL); > + p = tb_property_find(dir, "vendorid", > TB_PROPERTY_TYPE_TEXT); > + if (p) > + xd->vendor_name = kstrdup(p->value.text, > GFP_KERNEL); > + > + return 0; > +} > + > +/* Called with @xd->lock held */ > +static void tb_xdomain_restore_paths(struct tb_xdomain *xd) > +{ > + if (!xd->resume) > + return; > + > + xd->resume = false; > + if (xd->transmit_path) { > + dev_dbg(&xd->dev, "re-establishing DMA path\n"); > + tb_domain_approve_xdomain_paths(xd->tb, xd); > + } > +} > + > +static void tb_xdomain_get_properties(struct work_struct *work) > +{ > + struct tb_xdomain *xd = container_of(work, typeof(*xd), > +      get_properties_work.wor > k); > + struct tb_property_dir *dir; > + struct tb *tb = xd->tb; > + bool update = false; > + u32 *block = NULL; > + u32 gen = 0; > + int ret; > + > + ret = tb_xdp_properties_request(tb->ctl, xd->route, xd- > >local_uuid, > + xd->remote_uuid, xd- > >properties_retries, > + &block, &gen); > + if (ret < 0) { > + if (xd->properties_retries-- > 0) { > + queue_delayed_work(xd->tb->wq, &xd- > >get_properties_work, > +    msecs_to_jiffies(1000)); > + } else { > + /* Give up now */ > + dev_err(&xd->dev, > + "failed read XDomain properties from > %pUb\n", > + xd->remote_uuid); > + } > + return; > + } > + > + xd->properties_retries = XDOMAIN_PROPERTIES_RETRIES; > + > + mutex_lock(&xd->lock); > + > + /* Only accept newer generation properties */ > + if (xd->properties && gen <= xd->property_block_gen) { > + /* > +  * On resume it is likely that the properties block > is > +  * not changed (unless the other end added or > removed > +  * services). However, we need to make sure the > existing > +  * DMA paths are restored properly. > +  */ > + tb_xdomain_restore_paths(xd); > + goto err_free_block; > + } > + > + dir = tb_property_parse_dir(block, ret); > + if (!dir) { > + dev_err(&xd->dev, "failed to parse XDomain > properties\n"); > + goto err_free_block; > + } > + > + ret = populate_properties(xd, dir); > + if (ret) { > + dev_err(&xd->dev, "missing XDomain properties in > response\n"); > + goto err_free_dir; > + } > + > + /* Release the existing one */ > + if (xd->properties) { > + tb_property_free_dir(xd->properties); > + update = true; > + } > + > + xd->properties = dir; > + xd->property_block_gen = gen; > + > + tb_xdomain_restore_paths(xd); > + > + mutex_unlock(&xd->lock); > + > + kfree(block); > + > + /* > +  * Now the device should be ready enough so we can add it to > the > +  * bus and let userspace know about it. If the device is > already > +  * registered, we notify the userspace that it has changed. > +  */ > + if (!update) { > + if (device_add(&xd->dev)) { > + dev_err(&xd->dev, "failed to add XDomain > device\n"); > + return; > + } > + } else { > + kobject_uevent(&xd->dev.kobj, KOBJ_CHANGE); > + } > + > + enumerate_services(xd); > + return; > + > +err_free_dir: > + tb_property_free_dir(dir); > +err_free_block: > + kfree(block); > + mutex_unlock(&xd->lock); > +} > + > +static void tb_xdomain_properties_changed(struct work_struct *work) > +{ > + struct tb_xdomain *xd = container_of(work, typeof(*xd), > +      properties_changed_work > .work); > + int ret; > + > + ret = tb_xdp_properties_changed_request(xd->tb->ctl, xd- > >route, > + xd->properties_changed_retries, xd- > >local_uuid); > + if (ret) { > + if (xd->properties_changed_retries-- > 0) > + queue_delayed_work(xd->tb->wq, > +    &xd- > >properties_changed_work, > +    msecs_to_jiffies(1000)); > + return; > + } > + > + xd->properties_changed_retries = > XDOMAIN_PROPERTIES_CHANGED_RETRIES; > +} > + > +static ssize_t device_show(struct device *dev, struct > device_attribute *attr, > +    char *buf) > +{ > + struct tb_xdomain *xd = container_of(dev, struct tb_xdomain, > dev); > + > + return sprintf(buf, "%#x\n", xd->device); > +} > +static DEVICE_ATTR_RO(device); > + > +static ssize_t > +device_name_show(struct device *dev, struct device_attribute *attr, > char *buf) > +{ > + struct tb_xdomain *xd = container_of(dev, struct tb_xdomain, > dev); > + int ret; > + > + if (mutex_lock_interruptible(&xd->lock)) > + return -ERESTARTSYS; > + ret = sprintf(buf, "%s\n", xd->device_name ? xd->device_name > : ""); > + mutex_unlock(&xd->lock); > + > + return ret; > +} > +static DEVICE_ATTR_RO(device_name); > + > +static ssize_t vendor_show(struct device *dev, struct > device_attribute *attr, > +    char *buf) > +{ > + struct tb_xdomain *xd = container_of(dev, struct tb_xdomain, > dev); > + > + return sprintf(buf, "%#x\n", xd->vendor); > +} > +static DEVICE_ATTR_RO(vendor); > + > +static ssize_t > +vendor_name_show(struct device *dev, struct device_attribute *attr, > char *buf) > +{ > + struct tb_xdomain *xd = container_of(dev, struct tb_xdomain, > dev); > + int ret; > + > + if (mutex_lock_interruptible(&xd->lock)) > + return -ERESTARTSYS; > + ret = sprintf(buf, "%s\n", xd->vendor_name ? xd->vendor_name > : ""); > + mutex_unlock(&xd->lock); > + > + return ret; > +} > +static DEVICE_ATTR_RO(vendor_name); > + > +static ssize_t unique_id_show(struct device *dev, struct > device_attribute *attr, > +       char *buf) > +{ > + struct tb_xdomain *xd = container_of(dev, struct tb_xdomain, > dev); > + > + return sprintf(buf, "%pUb\n", xd->remote_uuid); > +} > +static DEVICE_ATTR_RO(unique_id); > + > +static struct attribute *xdomain_attrs[] = { > + &dev_attr_device.attr, > + &dev_attr_device_name.attr, > + &dev_attr_unique_id.attr, > + &dev_attr_vendor.attr, > + &dev_attr_vendor_name.attr, > + NULL, > +}; > + > +static struct attribute_group xdomain_attr_group = { > + .attrs = xdomain_attrs, > +}; > + > +static const struct attribute_group *xdomain_attr_groups[] = { > + &xdomain_attr_group, > + NULL, > +}; > + > +static void tb_xdomain_release(struct device *dev) > +{ > + struct tb_xdomain *xd = container_of(dev, struct tb_xdomain, > dev); > + > + put_device(xd->dev.parent); > + > + tb_property_free_dir(xd->properties); > + ida_destroy(&xd->service_ids); > + > + kfree(xd->local_uuid); > + kfree(xd->remote_uuid); > + kfree(xd->device_name); > + kfree(xd->vendor_name); > + kfree(xd); > +} > + > +static void start_handshake(struct tb_xdomain *xd) > +{ > + xd->properties_retries = XDOMAIN_PROPERTIES_RETRIES; > + xd->properties_changed_retries = > XDOMAIN_PROPERTIES_CHANGED_RETRIES; > + > + /* Start exchanging properties with the other host */ > + queue_delayed_work(xd->tb->wq, &xd->properties_changed_work, > +    msecs_to_jiffies(100)); > + queue_delayed_work(xd->tb->wq, &xd->get_properties_work, > +    msecs_to_jiffies(1000)); > +} > + > +static void stop_handshake(struct tb_xdomain *xd) > +{ > + xd->properties_retries = 0; > + xd->properties_changed_retries = 0; > + > + cancel_delayed_work_sync(&xd->get_properties_work); > + cancel_delayed_work_sync(&xd->properties_changed_work); > +} > + > +static int __maybe_unused tb_xdomain_suspend(struct device *dev) > +{ > + stop_handshake(tb_to_xdomain(dev)); > + return 0; > +} > + > +static int __maybe_unused tb_xdomain_resume(struct device *dev) > +{ > + struct tb_xdomain *xd = tb_to_xdomain(dev); > + > + /* > +  * Ask tb_xdomain_get_properties() restore any existing DMA > +  * paths after properties are re-read. > +  */ > + xd->resume = true; > + start_handshake(xd); > + > + return 0; > +} > + > +static const struct dev_pm_ops tb_xdomain_pm_ops = { > + SET_SYSTEM_SLEEP_PM_OPS(tb_xdomain_suspend, > tb_xdomain_resume) > +}; > + > +struct device_type tb_xdomain_type = { > + .name = "thunderbolt_xdomain", > + .release = tb_xdomain_release, > + .pm = &tb_xdomain_pm_ops, > +}; > +EXPORT_SYMBOL_GPL(tb_xdomain_type); > + > +/** > + * tb_xdomain_alloc() - Allocate new XDomain object > + * @tb: Domain where the XDomain belongs > + * @parent: Parent device (the switch through the connection to the > + *     other domain is reached). > + * @route: Route string used to reach the other domain > + * @local_uuid: Our local domain UUID > + * @remote_uuid: UUID of the other domain > + * > + * Allocates new XDomain structure and returns pointer to that. The > + * object must be released by calling tb_xdomain_put(). > + */ > +struct tb_xdomain *tb_xdomain_alloc(struct tb *tb, struct device > *parent, > +     u64 route, const uuid_t > *local_uuid, > +     const uuid_t *remote_uuid) > +{ > + struct tb_xdomain *xd; > + > + xd = kzalloc(sizeof(*xd), GFP_KERNEL); > + if (!xd) > + return NULL; > + > + xd->tb = tb; > + xd->route = route; > + ida_init(&xd->service_ids); > + mutex_init(&xd->lock); > + INIT_DELAYED_WORK(&xd->get_properties_work, > tb_xdomain_get_properties); > + INIT_DELAYED_WORK(&xd->properties_changed_work, > +   tb_xdomain_properties_changed); > + > + xd->local_uuid = kmemdup(local_uuid, sizeof(uuid_t), > GFP_KERNEL); > + if (!xd->local_uuid) > + goto err_free; > + > + xd->remote_uuid = kmemdup(remote_uuid, sizeof(uuid_t), > GFP_KERNEL); > + if (!xd->remote_uuid) > + goto err_free_local_uuid; > + > + device_initialize(&xd->dev); > + xd->dev.parent = get_device(parent); > + xd->dev.bus = &tb_bus_type; > + xd->dev.type = &tb_xdomain_type; > + xd->dev.groups = xdomain_attr_groups; > + dev_set_name(&xd->dev, "%u-%llx", tb->index, route); > + > + return xd; > + > +err_free_local_uuid: > + kfree(xd->local_uuid); > +err_free: > + kfree(xd); > + > + return NULL; > +} > + > +/** > + * tb_xdomain_add() - Add XDomain to the bus > + * @xd: XDomain to add > + * > + * This function starts XDomain discovery protocol handshake and > + * eventually adds the XDomain to the bus. After calling this > function > + * the caller needs to call tb_xdomain_remove() in order to remove > and > + * release the object regardless whether the handshake succeeded or > not. > + */ > +void tb_xdomain_add(struct tb_xdomain *xd) > +{ > + /* Start exchanging properties with the other host */ > + start_handshake(xd); > +} > + > +static int unregister_service(struct device *dev, void *data) > +{ > + device_unregister(dev); > + return 0; > +} > + > +/** > + * tb_xdomain_remove() - Remove XDomain from the bus > + * @xd: XDomain to remove > + * > + * This will stop all ongoing configuration work and remove the > XDomain > + * along with any services from the bus. When the last reference to > @xd > + * is released the object will be released as well. > + */ > +void tb_xdomain_remove(struct tb_xdomain *xd) > +{ > + stop_handshake(xd); > + > + device_for_each_child_reverse(&xd->dev, xd, > unregister_service); > + > + if (!device_is_registered(&xd->dev)) > + put_device(&xd->dev); > + else > + device_unregister(&xd->dev); > +} > + > +/** > + * tb_xdomain_enable_paths() - Enable DMA paths for XDomain > connection > + * @xd: XDomain connection > + * @transmit_path: HopID of the transmit path the other end is using > to > + *    send packets > + * @transmit_ring: DMA ring used to receive packets from the other > end > + * @receive_path: HopID of the receive path the other end is using > to > + *   receive packets > + * @receive_ring: DMA ring used to send packets to the other end > + * > + * The function enables DMA paths accordingly so that after > successful > + * return the caller can send and receive packets using high-speed > DMA > + * path. > + * > + * Return: %0 in case of success and negative errno in case of error > + */ > +int tb_xdomain_enable_paths(struct tb_xdomain *xd, u16 > transmit_path, > +     u16 transmit_ring, u16 receive_path, > +     u16 receive_ring) > +{ > + int ret; > + > + mutex_lock(&xd->lock); > + > + if (xd->transmit_path) { > + ret = xd->transmit_path == transmit_path ? 0 : > -EBUSY; > + goto exit_unlock; > + } > + > + xd->transmit_path = transmit_path; > + xd->transmit_ring = transmit_ring; > + xd->receive_path = receive_path; > + xd->receive_ring = receive_ring; > + > + ret = tb_domain_approve_xdomain_paths(xd->tb, xd); > + > +exit_unlock: > + mutex_unlock(&xd->lock); > + > + return ret; > +} > +EXPORT_SYMBOL_GPL(tb_xdomain_enable_paths); > + > +/** > + * tb_xdomain_disable_paths() - Disable DMA paths for XDomain > connection > + * @xd: XDomain connection > + * > + * This does the opposite of tb_xdomain_enable_paths(). After call > to > + * this the caller is not expected to use the rings anymore. > + * > + * Return: %0 in case of success and negative errno in case of error > + */ > +int tb_xdomain_disable_paths(struct tb_xdomain *xd) > +{ > + int ret = 0; > + > + mutex_lock(&xd->lock); > + if (xd->transmit_path) { > + xd->transmit_path = 0; > + xd->transmit_ring = 0; > + xd->receive_path = 0; > + xd->receive_ring = 0; > + > + ret = tb_domain_disconnect_xdomain_paths(xd->tb, > xd); > + } > + mutex_unlock(&xd->lock); > + > + return ret; > +} > +EXPORT_SYMBOL_GPL(tb_xdomain_disable_paths); > + > +struct tb_xdomain_lookup { > + const uuid_t *uuid; > + u8 link; > + u8 depth; > +}; > + > +static struct tb_xdomain *switch_find_xdomain(struct tb_switch *sw, > + const struct tb_xdomain_lookup *lookup) > +{ > + int i; > + > + for (i = 1; i <= sw->config.max_port_number; i++) { > + struct tb_port *port = &sw->ports[i]; > + struct tb_xdomain *xd; > + > + if (tb_is_upstream_port(port)) > + continue; > + > + if (port->xdomain) { > + xd = port->xdomain; > + > + if (lookup->uuid) { > + if (uuid_equal(xd->remote_uuid, > lookup->uuid)) > + return xd; > + } else if (lookup->link == xd->link && > +    lookup->depth == xd->depth) { > + return xd; > + } > + } else if (port->remote) { > + xd = switch_find_xdomain(port->remote->sw, > lookup); > + if (xd) > + return xd; > + } > + } > + > + return NULL; > +} > + > +/** > + * tb_xdomain_find_by_uuid() - Find an XDomain by UUID > + * @tb: Domain where the XDomain belongs to > + * @uuid: UUID to look for > + * > + * Finds XDomain by walking through the Thunderbolt topology below > @tb. > + * The returned XDomain will have its reference count increased so > the > + * caller needs to call tb_xdomain_put() when it is done with the > + * object. > + * > + * This will find all XDomains including the ones that are not yet > added > + * to the bus (handshake is still in progress). > + * > + * The caller needs to hold @tb->lock. > + */ > +struct tb_xdomain *tb_xdomain_find_by_uuid(struct tb *tb, const > uuid_t *uuid) > +{ > + struct tb_xdomain_lookup lookup; > + struct tb_xdomain *xd; > + > + memset(&lookup, 0, sizeof(lookup)); > + lookup.uuid = uuid; > + > + xd = switch_find_xdomain(tb->root_switch, &lookup); > + if (xd) { > + get_device(&xd->dev); > + return xd; > + } > + > + return NULL; > +} > +EXPORT_SYMBOL_GPL(tb_xdomain_find_by_uuid); > + > +/** > + * tb_xdomain_find_by_link_depth() - Find an XDomain by link and > depth > + * @tb: Domain where the XDomain belongs to > + * @link: Root switch link number > + * @depth: Depth in the link > + * > + * Finds XDomain by walking through the Thunderbolt topology below > @tb. > + * The returned XDomain will have its reference count increased so > the > + * caller needs to call tb_xdomain_put() when it is done with the > + * object. > + * > + * This will find all XDomains including the ones that are not yet > added > + * to the bus (handshake is still in progress). > + * > + * The caller needs to hold @tb->lock. > + */ > +struct tb_xdomain *tb_xdomain_find_by_link_depth(struct tb *tb, u8 > link, > +  u8 depth) > +{ > + struct tb_xdomain_lookup lookup; > + struct tb_xdomain *xd; > + > + memset(&lookup, 0, sizeof(lookup)); > + lookup.link = link; > + lookup.depth = depth; > + > + xd = switch_find_xdomain(tb->root_switch, &lookup); > + if (xd) { > + get_device(&xd->dev); > + return xd; > + } > + > + return NULL; > +} > + > +bool tb_xdomain_handle_request(struct tb *tb, enum tb_cfg_pkg_type > type, > +        const void *buf, size_t size) > +{ > + const struct tb_protocol_handler *handler, *tmp; > + const struct tb_xdp_header *hdr = buf; > + unsigned int length; > + int ret = 0; > + > + /* We expect the packet is at least size of the header */ > + length = hdr->xd_hdr.length_sn & TB_XDOMAIN_LENGTH_MASK; > + if (length != size / 4 - sizeof(hdr->xd_hdr) / 4) > + return true; > + if (length < sizeof(*hdr) / 4 - sizeof(hdr->xd_hdr) / 4) > + return true; > + > + /* > +  * Handle XDomain discovery protocol packets directly here. > For > +  * other protocols (based on their UUID) we call registered > +  * handlers in turn. > +  */ > + if (uuid_equal(&hdr->uuid, &tb_xdp_uuid)) { > + if (type == TB_CFG_PKG_XDOMAIN_REQ) { > + tb_xdp_schedule_request(tb, hdr, size); > + return true; > + } > + return false; > + } > + > + mutex_lock(&xdomain_lock); > + list_for_each_entry_safe(handler, tmp, &protocol_handlers, > list) { > + if (!uuid_equal(&hdr->uuid, handler->uuid)) > + continue; > + > + mutex_unlock(&xdomain_lock); > + ret = handler->callback(buf, size, handler->data); > + mutex_lock(&xdomain_lock); > + > + if (ret) > + break; > + } > + mutex_unlock(&xdomain_lock); > + > + return ret > 0; > +} > + > +static int rebuild_property_block(void) > +{ > + u32 *block, len; > + int ret; > + > + ret = tb_property_format_dir(xdomain_property_dir, NULL, 0); > + if (ret < 0) > + return ret; > + > + len = ret; > + > + block = kcalloc(len, sizeof(u32), GFP_KERNEL); > + if (!block) > + return -ENOMEM; > + > + ret = tb_property_format_dir(xdomain_property_dir, block, > len); > + if (ret) { > + kfree(block); > + return ret; > + } > + > + kfree(xdomain_property_block); > + xdomain_property_block = block; > + xdomain_property_block_len = len; > + xdomain_property_block_gen++; > + > + return 0; > +} > + > +static int update_xdomain(struct device *dev, void *data) > +{ > + struct tb_xdomain *xd; > + > + xd = tb_to_xdomain(dev); > + if (xd) { > + queue_delayed_work(xd->tb->wq, &xd- > >properties_changed_work, > +    msecs_to_jiffies(50)); > + } > + > + return 0; > +} > + > +static void update_all_xdomains(void) > +{ > + bus_for_each_dev(&tb_bus_type, NULL, NULL, update_xdomain); > +} > + > +static bool remove_directory(const char *key, const struct > tb_property_dir *dir) > +{ > + struct tb_property *p; > + > + p = tb_property_find(xdomain_property_dir, key, > +      TB_PROPERTY_TYPE_DIRECTORY); > + if (p && p->value.dir == dir) { > + tb_property_remove(p); > + return true; > + } > + return false; > +} > + > +/** > + * tb_register_property_dir() - Register property directory to the > host > + * @key: Key (name) of the directory to add > + * @dir: Directory to add > + * > + * Service drivers can use this function to add new property > directory > + * to the host available properties. The other connected hosts are > + * notified so they can re-read properties of this host if they are > + * interested. > + * > + * Return: %0 on success and negative errno on failure > + */ > +int tb_register_property_dir(const char *key, struct tb_property_dir > *dir) > +{ > + int ret; > + > + if (!key || strlen(key) > 8) > + return -EINVAL; > + > + mutex_lock(&xdomain_lock); > + if (tb_property_find(xdomain_property_dir, key, > +      TB_PROPERTY_TYPE_DIRECTORY)) { > + ret = -EEXIST; > + goto err_unlock; > + } > + > + ret = tb_property_add_dir(xdomain_property_dir, key, dir); > + if (ret) > + goto err_unlock; > + > + ret = rebuild_property_block(); > + if (ret) { > + remove_directory(key, dir); > + goto err_unlock; > + } > + > + mutex_unlock(&xdomain_lock); > + update_all_xdomains(); > + return 0; > + > +err_unlock: > + mutex_unlock(&xdomain_lock); > + return ret; > +} > +EXPORT_SYMBOL_GPL(tb_register_property_dir); > + > +/** > + * tb_unregister_property_dir() - Removes property directory from > host > + * @key: Key (name) of the directory > + * @dir: Directory to remove > + * > + * This will remove the existing directory from this host and notify > the > + * connected hosts about the change. > + */ > +void tb_unregister_property_dir(const char *key, struct > tb_property_dir *dir) > +{ > + int ret = 0; > + > + mutex_lock(&xdomain_lock); > + if (remove_directory(key, dir)) > + ret = rebuild_property_block(); > + mutex_unlock(&xdomain_lock); > + > + if (!ret) > + update_all_xdomains(); > +} > +EXPORT_SYMBOL_GPL(tb_unregister_property_dir); > + > +int tb_xdomain_init(void) > +{ > + int ret; > + > + xdomain_property_dir = tb_property_create_dir(NULL); > + if (!xdomain_property_dir) > + return -ENOMEM; > + > + /* > +  * Initialize standard set of properties without any service > +  * directories. Those will be added by service drivers > +  * themselves when they are loaded. > +  */ > + tb_property_add_immediate(xdomain_property_dir, "vendorid", > +   PCI_VENDOR_ID_INTEL); > + tb_property_add_text(xdomain_property_dir, "vendorid", > "Intel Corp."); > + tb_property_add_immediate(xdomain_property_dir, "deviceid", > 0x1); > + tb_property_add_text(xdomain_property_dir, "deviceid", > +      utsname()->nodename); > + tb_property_add_immediate(xdomain_property_dir, "devicerv", > 0x80000100); > + > + ret = rebuild_property_block(); > + if (ret) { > + tb_property_free_dir(xdomain_property_dir); > + xdomain_property_dir = NULL; > + } > + > + return ret; > +} > + > +void tb_xdomain_exit(void) > +{ > + kfree(xdomain_property_block); > + tb_property_free_dir(xdomain_property_dir); > +} > diff --git a/include/linux/mod_devicetable.h > b/include/linux/mod_devicetable.h > index 694cebb50f72..7625c3b81f84 100644 > --- a/include/linux/mod_devicetable.h > +++ b/include/linux/mod_devicetable.h > @@ -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 */ > diff --git a/include/linux/thunderbolt.h > b/include/linux/thunderbolt.h > index 4011d6537a8c..79abdaf1c296 100644 > --- a/include/linux/thunderbolt.h > +++ b/include/linux/thunderbolt.h > @@ -17,6 +17,7 @@ >  #include >  #include >  #include > +#include >  #include >   >  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_ */ > diff --git a/scripts/mod/devicetable-offsets.c > b/scripts/mod/devicetable-offsets.c > index e4d90e50f6fe..57263f2f8f2f 100644 > --- a/scripts/mod/devicetable-offsets.c > +++ b/scripts/mod/devicetable-offsets.c > @@ -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; >  } > diff --git a/scripts/mod/file2alias.c b/scripts/mod/file2alias.c > index 29d6699d5a06..6ef6e63f96fd 100644 > --- a/scripts/mod/file2alias.c > +++ b/scripts/mod/file2alias.c > @@ -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) >  {