diff options
Diffstat (limited to 'drivers/thunderbolt/xdomain.c')
-rw-r--r-- | drivers/thunderbolt/xdomain.c | 130 |
1 files changed, 103 insertions, 27 deletions
diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c index 5b5566862318..11a50c86a1e4 100644 --- a/drivers/thunderbolt/xdomain.c +++ b/drivers/thunderbolt/xdomain.c @@ -250,7 +250,7 @@ static int tb_xdp_handle_error(const struct tb_xdp_error_response *res) case ERROR_UNKNOWN_DOMAIN: return -EIO; case ERROR_NOT_SUPPORTED: - return -ENOTSUPP; + return -EOPNOTSUPP; case ERROR_NOT_READY: return -EAGAIN; default: @@ -703,6 +703,27 @@ out_unlock: mutex_unlock(&xdomain_lock); } +static void start_handshake(struct tb_xdomain *xd) +{ + xd->state = XDOMAIN_STATE_INIT; + queue_delayed_work(xd->tb->wq, &xd->state_work, + msecs_to_jiffies(XDOMAIN_SHORT_TIMEOUT)); +} + +/* Can be called from state_work */ +static void __stop_handshake(struct tb_xdomain *xd) +{ + cancel_delayed_work_sync(&xd->properties_changed_work); + xd->properties_changed_retries = 0; + xd->state_retries = 0; +} + +static void stop_handshake(struct tb_xdomain *xd) +{ + cancel_delayed_work_sync(&xd->state_work); + __stop_handshake(xd); +} + static void tb_xdp_handle_request(struct work_struct *work) { struct xdomain_request_work *xw = container_of(work, typeof(*xw), work); @@ -765,6 +786,15 @@ static void tb_xdp_handle_request(struct work_struct *work) case UUID_REQUEST: tb_dbg(tb, "%llx: received XDomain UUID request\n", route); ret = tb_xdp_uuid_response(ctl, route, sequence, uuid); + /* + * If we've stopped the discovery with an error such as + * timing out, we will restart the handshake now that we + * received UUID request from the remote host. + */ + if (!ret && xd && xd->state == XDOMAIN_STATE_ERROR) { + dev_dbg(&xd->dev, "restarting handshake\n"); + start_handshake(xd); + } break; case LINK_STATE_STATUS_REQUEST: @@ -967,12 +997,12 @@ static void tb_service_release(struct device *dev) struct tb_xdomain *xd = tb_service_parent(svc); tb_service_debugfs_remove(svc); - ida_simple_remove(&xd->service_ids, svc->id); + ida_free(&xd->service_ids, svc->id); kfree(svc->key); kfree(svc); } -struct device_type tb_service_type = { +const struct device_type tb_service_type = { .name = "thunderbolt_service", .groups = tb_service_attr_groups, .uevent = tb_service_uevent, @@ -1069,7 +1099,7 @@ static void enumerate_services(struct tb_xdomain *xd) break; } - id = ida_simple_get(&xd->service_ids, 0, 0, GFP_KERNEL); + id = ida_alloc(&xd->service_ids, GFP_KERNEL); if (id < 0) { kfree(svc->key); kfree(svc); @@ -1432,6 +1462,11 @@ static int tb_xdomain_get_properties(struct tb_xdomain *xd) tb_port_disable(port->dual_link_port); } + dev_dbg(&xd->dev, "current link speed %u.0 Gb/s\n", + xd->link_speed); + dev_dbg(&xd->dev, "current link width %s\n", + tb_width_name(xd->link_width)); + if (device_add(&xd->dev)) { dev_err(&xd->dev, "failed to add XDomain device\n"); return -ENODEV; @@ -1521,6 +1556,13 @@ static void tb_xdomain_queue_properties_changed(struct tb_xdomain *xd) msecs_to_jiffies(XDOMAIN_SHORT_TIMEOUT)); } +static void tb_xdomain_failed(struct tb_xdomain *xd) +{ + xd->state = XDOMAIN_STATE_ERROR; + queue_delayed_work(xd->tb->wq, &xd->state_work, + msecs_to_jiffies(XDOMAIN_DEFAULT_TIMEOUT)); +} + static void tb_xdomain_state_work(struct work_struct *work) { struct tb_xdomain *xd = container_of(work, typeof(*xd), state_work.work); @@ -1547,7 +1589,7 @@ static void tb_xdomain_state_work(struct work_struct *work) if (ret) { if (ret == -EAGAIN) goto retry_state; - xd->state = XDOMAIN_STATE_ERROR; + tb_xdomain_failed(xd); } else { tb_xdomain_queue_properties_changed(xd); if (xd->bonding_possible) @@ -1612,7 +1654,7 @@ static void tb_xdomain_state_work(struct work_struct *work) if (ret) { if (ret == -EAGAIN) goto retry_state; - xd->state = XDOMAIN_STATE_ERROR; + tb_xdomain_failed(xd); } else { xd->state = XDOMAIN_STATE_ENUMERATED; } @@ -1623,6 +1665,8 @@ static void tb_xdomain_state_work(struct work_struct *work) break; case XDOMAIN_STATE_ERROR: + dev_dbg(&xd->dev, "discovery failed, stopping handshake\n"); + __stop_handshake(xd); break; default: @@ -1747,13 +1791,13 @@ static ssize_t rx_lanes_show(struct device *dev, struct device_attribute *attr, switch (xd->link_width) { case TB_LINK_WIDTH_SINGLE: - case TB_LINK_WIDTH_ASYM_RX: + case TB_LINK_WIDTH_ASYM_TX: width = 1; break; case TB_LINK_WIDTH_DUAL: width = 2; break; - case TB_LINK_WIDTH_ASYM_TX: + case TB_LINK_WIDTH_ASYM_RX: width = 3; break; default: @@ -1773,13 +1817,13 @@ static ssize_t tx_lanes_show(struct device *dev, struct device_attribute *attr, switch (xd->link_width) { case TB_LINK_WIDTH_SINGLE: - case TB_LINK_WIDTH_ASYM_TX: + case TB_LINK_WIDTH_ASYM_RX: width = 1; break; case TB_LINK_WIDTH_DUAL: width = 2; break; - case TB_LINK_WIDTH_ASYM_RX: + case TB_LINK_WIDTH_ASYM_TX: width = 3; break; default: @@ -1833,21 +1877,6 @@ static void tb_xdomain_release(struct device *dev) kfree(xd); } -static void start_handshake(struct tb_xdomain *xd) -{ - xd->state = XDOMAIN_STATE_INIT; - queue_delayed_work(xd->tb->wq, &xd->state_work, - msecs_to_jiffies(XDOMAIN_SHORT_TIMEOUT)); -} - -static void stop_handshake(struct tb_xdomain *xd) -{ - cancel_delayed_work_sync(&xd->properties_changed_work); - cancel_delayed_work_sync(&xd->state_work); - xd->properties_changed_retries = 0; - xd->state_retries = 0; -} - static int __maybe_unused tb_xdomain_suspend(struct device *dev) { stop_handshake(tb_to_xdomain(dev)); @@ -1864,13 +1893,57 @@ 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 = { +const struct device_type tb_xdomain_type = { .name = "thunderbolt_xdomain", .release = tb_xdomain_release, .pm = &tb_xdomain_pm_ops, }; EXPORT_SYMBOL_GPL(tb_xdomain_type); +static void tb_xdomain_link_init(struct tb_xdomain *xd, struct tb_port *down) +{ + if (!down->dual_link_port) + return; + + /* + * Gen 4 links come up already as bonded so only update the port + * structures here. + */ + if (tb_port_get_link_generation(down) >= 4) { + down->bonded = true; + down->dual_link_port->bonded = true; + } else { + xd->bonding_possible = true; + } +} + +static void tb_xdomain_link_exit(struct tb_xdomain *xd) +{ + struct tb_port *down = tb_xdomain_downstream_port(xd); + + if (!down->dual_link_port) + return; + + if (tb_port_get_link_generation(down) >= 4) { + down->bonded = false; + down->dual_link_port->bonded = false; + } else if (xd->link_width > TB_LINK_WIDTH_SINGLE) { + /* + * Just return port structures back to way they were and + * update credits. No need to update userspace because + * the XDomain is removed soon anyway. + */ + tb_port_lane_bonding_disable(down); + tb_port_update_credits(down); + } else if (down->dual_link_port) { + /* + * Re-enable the lane 1 adapter we disabled at the end + * of tb_xdomain_get_properties(). + */ + tb_port_enable(down->dual_link_port); + } +} + /** * tb_xdomain_alloc() - Allocate new XDomain object * @tb: Domain where the XDomain belongs @@ -1921,7 +1994,8 @@ struct tb_xdomain *tb_xdomain_alloc(struct tb *tb, struct device *parent, goto err_free_local_uuid; } else { xd->needs_uuid = true; - xd->bonding_possible = !!down->dual_link_port; + + tb_xdomain_link_init(xd, down); } device_initialize(&xd->dev); @@ -1990,6 +2064,8 @@ void tb_xdomain_remove(struct tb_xdomain *xd) device_for_each_child_reverse(&xd->dev, xd, unregister_service); + tb_xdomain_link_exit(xd); + /* * Undo runtime PM here explicitly because it is possible that * the XDomain was never added to the bus and thus device_del() |