Message ID | 20170918153049.44185-7-mika.westerberg@linux.intel.com |
---|---|
State | Not Applicable, archived |
Delegated to: | David Miller |
Headers | show |
Series | Thunderbolt networking | expand |
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 <michael.jamet@intel.com> > Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com> > Reviewed-by: Yehezkel Bernat <yehezkel.bernat@intel.com> > --- > 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/<xdomain>.<service > >/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/<xdomain>.<service > >/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/<xdomain>.<service > >/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/<xdomain>.<service > >/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/<xdomain>.<service > >/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/<xdomain>.<service > >/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 <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 > 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 <michael.jamet@intel.com> > + * Mika Westerberg <mika.westerberg@linux.intel.com> > + * > + * This program is free software; you can redistribute it and/or > modify > + * it under the terms of the GNU General Public License version 2 as > + * published by the Free Software Foundation. > + */ > + > +#include <linux/device.h> > +#include <linux/kmod.h> > +#include <linux/module.h> > +#include <linux/utsname.h> > +#include <linux/uuid.h> > +#include <linux/workqueue.h> > + > +#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 <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_ */ > 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) > {
On Mon, 2017-09-18 at 11:12 -0500, Dan Williams wrote: > 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 <michael.jamet@intel.com> > > Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com> > > Reviewed-by: Yehezkel Bernat <yehezkel.bernat@intel.com> > > --- > > 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/<xdomain>.<servi > > ce > > > /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/<xdomain>.<servi > > ce > > > /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/<xdomain>.<servi > > ce > > > /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/<xdomain>.<servi > > ce > > > /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/<xdomain>.<servi > > ce > > > /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/<xdomain>.<servi > > ce > > > /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()? Nevermind, I'm blind, you've handled that already in patch #16. Ignore me. Dan > 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 <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 > > 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 <michael.jamet@intel.com> > > + * Mika Westerberg <mika.westerberg@linux.intel.com> > > + * > > + * This program is free software; you can redistribute it and/or > > modify > > + * it under the terms of the GNU General Public License version 2 > > as > > + * published by the Free Software Foundation. > > + */ > > + > > +#include <linux/device.h> > > +#include <linux/kmod.h> > > +#include <linux/module.h> > > +#include <linux/utsname.h> > > +#include <linux/uuid.h> > > +#include <linux/workqueue.h> > > + > > +#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.w > > or > > 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_wo > > rk > > .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 <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_ */ > > 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) > > {
On Mon, Sep 18, 2017 at 06:30:39PM +0300, Mika Westerberg wrote: > +What: /sys/bus/thunderbolt/devices/<xdomain>.<service>/key > +Date: Dec 2017 > +KernelVersion: 4.14 I forgot to update these to 4.15. I'll fix them in v2.
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/<xdomain>.<service>/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/<xdomain>.<service>/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/<xdomain>.<service>/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/<xdomain>.<service>/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/<xdomain>.<service>/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/<xdomain>.<service>/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); +} + +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 <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 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 <michael.jamet@intel.com> + * Mika Westerberg <mika.westerberg@linux.intel.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/device.h> +#include <linux/kmod.h> +#include <linux/module.h> +#include <linux/utsname.h> +#include <linux/uuid.h> +#include <linux/workqueue.h> + +#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.work); + 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 <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_ */ 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) {