diff options
Diffstat (limited to 'drivers/thunderbolt/xdomain.c')
-rw-r--r-- | drivers/thunderbolt/xdomain.c | 268 |
1 files changed, 214 insertions, 54 deletions
diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c index 9e9bf8771345..b242161868c2 100644 --- a/drivers/thunderbolt/xdomain.c +++ b/drivers/thunderbolt/xdomain.c @@ -8,6 +8,7 @@ */ #include <linux/device.h> +#include <linux/delay.h> #include <linux/kmod.h> #include <linux/module.h> #include <linux/pm_runtime.h> @@ -21,6 +22,7 @@ #define XDOMAIN_UUID_RETRIES 10 #define XDOMAIN_PROPERTIES_RETRIES 60 #define XDOMAIN_PROPERTIES_CHANGED_RETRIES 10 +#define XDOMAIN_BONDING_WAIT 100 /* ms */ struct xdomain_request_work { struct work_struct work; @@ -28,6 +30,10 @@ struct xdomain_request_work { struct tb *tb; }; +static bool tb_xdomain_enabled = true; +module_param_named(xdomain, tb_xdomain_enabled, bool, 0444); +MODULE_PARM_DESC(xdomain, "allow XDomain protocol (default: true)"); + /* Serializes access to the properties and protocol handlers below */ static DEFINE_MUTEX(xdomain_lock); @@ -45,6 +51,11 @@ static const uuid_t tb_xdp_uuid = UUID_INIT(0xb638d70e, 0x42ff, 0x40bb, 0x97, 0xc2, 0x90, 0xe2, 0xc0, 0xb2, 0xff, 0x07); +bool tb_is_xdomain_enabled(void) +{ + return tb_xdomain_enabled; +} + static bool tb_xdomain_match(const struct tb_cfg_request *req, const struct ctl_pkg *pkg) { @@ -501,6 +512,55 @@ void tb_unregister_protocol_handler(struct tb_protocol_handler *handler) } EXPORT_SYMBOL_GPL(tb_unregister_protocol_handler); +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 void finalize_property_block(void) +{ + const struct tb_property *nodename; + + /* + * On first XDomain connection we set up the the system + * nodename. This delayed here because userspace may not have it + * set when the driver is first probed. + */ + mutex_lock(&xdomain_lock); + nodename = tb_property_find(xdomain_property_dir, "deviceid", + TB_PROPERTY_TYPE_TEXT); + if (!nodename) { + tb_property_add_text(xdomain_property_dir, "deviceid", + utsname()->nodename); + rebuild_property_block(); + } + mutex_unlock(&xdomain_lock); +} + static void tb_xdp_handle_request(struct work_struct *work) { struct xdomain_request_work *xw = container_of(work, typeof(*xw), work); @@ -529,6 +589,8 @@ static void tb_xdp_handle_request(struct work_struct *work) goto out; } + finalize_property_block(); + switch (pkg->type) { case PROPERTIES_REQUEST: ret = tb_xdp_properties_response(tb, ctl, route, sequence, uuid, @@ -536,8 +598,6 @@ static void tb_xdp_handle_request(struct work_struct *work) 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); @@ -547,10 +607,12 @@ static void tb_xdp_handle_request(struct work_struct *work) * 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); + xd = tb_xdomain_find_by_route_locked(tb, route); if (xd) { - queue_delayed_work(tb->wq, &xd->get_properties_work, - msecs_to_jiffies(50)); + if (device_is_registered(&xd->dev)) { + queue_delayed_work(tb->wq, &xd->get_properties_work, + msecs_to_jiffies(50)); + } tb_xdomain_put(xd); } @@ -617,7 +679,7 @@ EXPORT_SYMBOL_GPL(tb_register_service_driver); /** * tb_unregister_service_driver() - Unregister XDomain service driver - * @xdrv: Driver to unregister + * @drv: Driver to unregister * * Unregisters XDomain service driver from the bus. */ @@ -703,7 +765,7 @@ static struct attribute *tb_service_attrs[] = { NULL, }; -static struct attribute_group tb_service_attr_group = { +static const struct attribute_group tb_service_attr_group = { .attrs = tb_service_attrs, }; @@ -726,6 +788,7 @@ 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); + tb_service_debugfs_remove(svc); ida_simple_remove(&xd->service_ids, svc->id); kfree(svc->key); kfree(svc); @@ -840,6 +903,8 @@ static void enumerate_services(struct tb_xdomain *xd) svc->dev.parent = &xd->dev; dev_set_name(&svc->dev, "%s.%d", dev_name(&xd->dev), svc->id); + tb_service_debugfs_init(svc); + if (device_register(&svc->dev)) { put_device(&svc->dev); break; @@ -892,6 +957,43 @@ static void tb_xdomain_restore_paths(struct tb_xdomain *xd) } } +static inline struct tb_switch *tb_xdomain_parent(struct tb_xdomain *xd) +{ + return tb_to_switch(xd->dev.parent); +} + +static int tb_xdomain_update_link_attributes(struct tb_xdomain *xd) +{ + bool change = false; + struct tb_port *port; + int ret; + + port = tb_port_at(xd->route, tb_xdomain_parent(xd)); + + ret = tb_port_get_link_speed(port); + if (ret < 0) + return ret; + + if (xd->link_speed != ret) + change = true; + + xd->link_speed = ret; + + ret = tb_port_get_link_width(port); + if (ret < 0) + return ret; + + if (xd->link_width != ret) + change = true; + + xd->link_width = ret; + + if (change) + kobject_uevent(&xd->dev.kobj, KOBJ_CHANGE); + + return 0; +} + static void tb_xdomain_get_uuid(struct work_struct *work) { struct tb_xdomain *xd = container_of(work, typeof(*xd), @@ -911,10 +1013,8 @@ static void tb_xdomain_get_uuid(struct work_struct *work) return; } - if (uuid_equal(&uuid, xd->local_uuid)) { + if (uuid_equal(&uuid, xd->local_uuid)) dev_dbg(&xd->dev, "intra-domain loop detected\n"); - return; - } /* * If the UUID is different, there is another domain connected @@ -1005,6 +1105,8 @@ static void tb_xdomain_get_properties(struct work_struct *work) xd->properties = dir; xd->property_block_gen = gen; + tb_xdomain_update_link_attributes(xd); + tb_xdomain_restore_paths(xd); mutex_unlock(&xd->lock); @@ -1111,16 +1213,42 @@ static ssize_t unique_id_show(struct device *dev, struct device_attribute *attr, } static DEVICE_ATTR_RO(unique_id); +static ssize_t speed_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct tb_xdomain *xd = container_of(dev, struct tb_xdomain, dev); + + return sprintf(buf, "%u.0 Gb/s\n", xd->link_speed); +} + +static DEVICE_ATTR(rx_speed, 0444, speed_show, NULL); +static DEVICE_ATTR(tx_speed, 0444, speed_show, NULL); + +static ssize_t lanes_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct tb_xdomain *xd = container_of(dev, struct tb_xdomain, dev); + + return sprintf(buf, "%u\n", xd->link_width); +} + +static DEVICE_ATTR(rx_lanes, 0444, lanes_show, NULL); +static DEVICE_ATTR(tx_lanes, 0444, lanes_show, NULL); + static struct attribute *xdomain_attrs[] = { &dev_attr_device.attr, &dev_attr_device_name.attr, + &dev_attr_rx_lanes.attr, + &dev_attr_rx_speed.attr, + &dev_attr_tx_lanes.attr, + &dev_attr_tx_speed.attr, &dev_attr_unique_id.attr, &dev_attr_vendor.attr, &dev_attr_vendor_name.attr, NULL, }; -static struct attribute_group xdomain_attr_group = { +static const struct attribute_group xdomain_attr_group = { .attrs = xdomain_attrs, }; @@ -1221,7 +1349,13 @@ 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_switch *parent_sw = tb_to_switch(parent); struct tb_xdomain *xd; + struct tb_port *down; + + /* Make sure the downstream domain is accessible */ + down = tb_port_at(route, parent_sw); + tb_port_unlock(down); xd = kzalloc(sizeof(*xd), GFP_KERNEL); if (!xd) @@ -1325,6 +1459,70 @@ void tb_xdomain_remove(struct tb_xdomain *xd) } /** + * tb_xdomain_lane_bonding_enable() - Enable lane bonding on XDomain + * @xd: XDomain connection + * + * Lane bonding is disabled by default for XDomains. This function tries + * to enable bonding by first enabling the port and waiting for the CL0 + * state. + * + * Return: %0 in case of success and negative errno in case of error. + */ +int tb_xdomain_lane_bonding_enable(struct tb_xdomain *xd) +{ + struct tb_port *port; + int ret; + + port = tb_port_at(xd->route, tb_xdomain_parent(xd)); + if (!port->dual_link_port) + return -ENODEV; + + ret = tb_port_enable(port->dual_link_port); + if (ret) + return ret; + + ret = tb_wait_for_port(port->dual_link_port, true); + if (ret < 0) + return ret; + if (!ret) + return -ENOTCONN; + + ret = tb_port_lane_bonding_enable(port); + if (ret) { + tb_port_warn(port, "failed to enable lane bonding\n"); + return ret; + } + + tb_xdomain_update_link_attributes(xd); + + dev_dbg(&xd->dev, "lane bonding enabled\n"); + return 0; +} +EXPORT_SYMBOL_GPL(tb_xdomain_lane_bonding_enable); + +/** + * tb_xdomain_lane_bonding_disable() - Disable lane bonding + * @xd: XDomain connection + * + * Lane bonding is disabled by default for XDomains. If bonding has been + * enabled, this function can be used to disable it. + */ +void tb_xdomain_lane_bonding_disable(struct tb_xdomain *xd) +{ + struct tb_port *port; + + port = tb_port_at(xd->route, tb_xdomain_parent(xd)); + if (port->dual_link_port) { + tb_port_lane_bonding_disable(port); + tb_port_disable(port->dual_link_port); + tb_xdomain_update_link_attributes(xd); + + dev_dbg(&xd->dev, "lane bonding disabled\n"); + } +} +EXPORT_SYMBOL_GPL(tb_xdomain_lane_bonding_disable); + +/** * 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 @@ -1405,10 +1603,9 @@ struct tb_xdomain_lookup { static struct tb_xdomain *switch_find_xdomain(struct tb_switch *sw, const struct tb_xdomain_lookup *lookup) { - int i; + struct tb_port *port; - for (i = 1; i <= sw->config.max_port_number; i++) { - struct tb_port *port = &sw->ports[i]; + tb_switch_for_each_port(sw, port) { struct tb_xdomain *xd; if (port->xdomain) { @@ -1565,35 +1762,6 @@ bool tb_xdomain_handle_request(struct tb *tb, enum tb_cfg_pkg_type type, 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; @@ -1698,8 +1866,6 @@ 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; @@ -1708,22 +1874,16 @@ int tb_xdomain_init(void) * Initialize standard set of properties without any service * directories. Those will be added by service drivers * themselves when they are loaded. + * + * We also add node name later when first connection is made. */ 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; + return 0; } void tb_xdomain_exit(void) |