From a25c8b2fc9636aaf29d9d9d89f92cdfd27a2a23d Mon Sep 17 00:00:00 2001 From: Andreas Noever Date: Tue, 3 Jun 2014 22:04:02 +0200 Subject: thunderbolt: Initialize root switch and ports This patch adds the structures tb_switch and tb_port as well as code to initialize the root switch. Signed-off-by: Andreas Noever Signed-off-by: Greg Kroah-Hartman --- drivers/thunderbolt/switch.c | 186 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 drivers/thunderbolt/switch.c (limited to 'drivers/thunderbolt/switch.c') diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c new file mode 100644 index 000000000000..e89121ffe44e --- /dev/null +++ b/drivers/thunderbolt/switch.c @@ -0,0 +1,186 @@ +/* + * Thunderbolt Cactus Ridge driver - switch/port utility functions + * + * Copyright (c) 2014 Andreas Noever + */ + +#include + +#include "tb.h" + +/* port utility functions */ + +static const char *tb_port_type(struct tb_regs_port_header *port) +{ + switch (port->type >> 16) { + case 0: + switch ((u8) port->type) { + case 0: + return "Inactive"; + case 1: + return "Port"; + case 2: + return "NHI"; + default: + return "unknown"; + } + case 0x2: + return "Ethernet"; + case 0x8: + return "SATA"; + case 0xe: + return "DP/HDMI"; + case 0x10: + return "PCIe"; + case 0x20: + return "USB"; + default: + return "unknown"; + } +} + +static void tb_dump_port(struct tb *tb, struct tb_regs_port_header *port) +{ + tb_info(tb, + " Port %d: %x:%x (Revision: %d, TB Version: %d, Type: %s (%#x))\n", + port->port_number, port->vendor_id, port->device_id, + port->revision, port->thunderbolt_version, tb_port_type(port), + port->type); + tb_info(tb, " Max hop id (in/out): %d/%d\n", + port->max_in_hop_id, port->max_out_hop_id); + tb_info(tb, " Max counters: %d\n", port->max_counters); + tb_info(tb, " NFC Credits: %#x\n", port->nfc_credits); +} + +/** + * tb_init_port() - initialize a port + * + * This is a helper method for tb_switch_alloc. Does not check or initialize + * any downstream switches. + * + * Return: Returns 0 on success or an error code on failure. + */ +static int tb_init_port(struct tb_switch *sw, u8 port_nr) +{ + int res; + struct tb_port *port = &sw->ports[port_nr]; + port->sw = sw; + port->port = port_nr; + port->remote = NULL; + res = tb_port_read(port, &port->config, TB_CFG_PORT, 0, 8); + if (res) + return res; + + tb_dump_port(sw->tb, &port->config); + + /* TODO: Read dual link port, DP port and more from EEPROM. */ + return 0; + +} + +/* switch utility functions */ + +static void tb_dump_switch(struct tb *tb, struct tb_regs_switch_header *sw) +{ + tb_info(tb, + " Switch: %x:%x (Revision: %d, TB Version: %d)\n", + sw->vendor_id, sw->device_id, sw->revision, + sw->thunderbolt_version); + tb_info(tb, " Max Port Number: %d\n", sw->max_port_number); + tb_info(tb, " Config:\n"); + tb_info(tb, + " Upstream Port Number: %d Depth: %d Route String: %#llx Enabled: %d, PlugEventsDelay: %dms\n", + sw->upstream_port_number, sw->depth, + (((u64) sw->route_hi) << 32) | sw->route_lo, + sw->enabled, sw->plug_events_delay); + tb_info(tb, + " unknown1: %#x unknown4: %#x\n", + sw->__unknown1, sw->__unknown4); +} + +/** + * tb_switch_free() - free a tb_switch and all downstream switches + */ +void tb_switch_free(struct tb_switch *sw) +{ + int i; + /* port 0 is the switch itself and never has a remote */ + for (i = 1; i <= sw->config.max_port_number; i++) { + if (tb_is_upstream_port(&sw->ports[i])) + continue; + if (sw->ports[i].remote) + tb_switch_free(sw->ports[i].remote->sw); + sw->ports[i].remote = NULL; + } + + kfree(sw->ports); + kfree(sw); +} + +/** + * tb_switch_alloc() - allocate and initialize a switch + * + * Return: Returns a NULL on failure. + */ +struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route) +{ + int i; + struct tb_switch *sw; + int upstream_port = tb_cfg_get_upstream_port(tb->ctl, route); + if (upstream_port < 0) + return NULL; + + sw = kzalloc(sizeof(*sw), GFP_KERNEL); + if (!sw) + return NULL; + + sw->tb = tb; + if (tb_cfg_read(tb->ctl, &sw->config, route, 0, 2, 0, 5)) + goto err; + tb_info(tb, + "initializing Switch at %#llx (depth: %d, up port: %d)\n", + route, tb_route_length(route), upstream_port); + tb_info(tb, "old switch config:\n"); + tb_dump_switch(tb, &sw->config); + + /* configure switch */ + sw->config.upstream_port_number = upstream_port; + sw->config.depth = tb_route_length(route); + sw->config.route_lo = route; + sw->config.route_hi = route >> 32; + sw->config.enabled = 1; + /* from here on we may use the tb_sw_* functions & macros */ + + if (sw->config.vendor_id != 0x8086) + tb_sw_warn(sw, "unknown switch vendor id %#x\n", + sw->config.vendor_id); + + if (sw->config.device_id != 0x1547 && sw->config.device_id != 0x1549) + tb_sw_warn(sw, "unsupported switch device id %#x\n", + sw->config.device_id); + + /* upload configuration */ + if (tb_sw_write(sw, 1 + (u32 *) &sw->config, TB_CFG_SWITCH, 1, 3)) + goto err; + + /* initialize ports */ + sw->ports = kcalloc(sw->config.max_port_number + 1, sizeof(*sw->ports), + GFP_KERNEL); + if (!sw->ports) + goto err; + + for (i = 0; i <= sw->config.max_port_number; i++) { + if (tb_init_port(sw, i)) + goto err; + /* TODO: check if port is disabled (EEPROM) */ + } + + /* TODO: I2C, IECS, EEPROM, link controller */ + + return sw; +err: + kfree(sw->ports); + kfree(sw); + return NULL; +} + -- cgit v1.2.3 From ca389f716f6140d5349583a716bc629d63b06b1f Mon Sep 17 00:00:00 2001 From: Andreas Noever Date: Tue, 3 Jun 2014 22:04:04 +0200 Subject: thunderbolt: Enable plug events Thunderbolt switches have a plug events capability. This patch adds the tb_plug_events_active method and uses it to activate plug events during switch allocation. Signed-off-by: Andreas Noever Signed-off-by: Greg Kroah-Hartman --- drivers/thunderbolt/switch.c | 52 ++++++++++++++++++++++++++++++++++++++++++++ drivers/thunderbolt/tb.h | 1 + 2 files changed, 53 insertions(+) (limited to 'drivers/thunderbolt/switch.c') diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index e89121ffe44e..6d193a230cf4 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -98,6 +98,45 @@ static void tb_dump_switch(struct tb *tb, struct tb_regs_switch_header *sw) sw->__unknown1, sw->__unknown4); } +/** + * tb_plug_events_active() - enable/disable plug events on a switch + * + * Also configures a sane plug_events_delay of 255ms. + * + * Return: Returns 0 on success or an error code on failure. + */ +static int tb_plug_events_active(struct tb_switch *sw, bool active) +{ + u32 data; + int res; + + sw->config.plug_events_delay = 0xff; + res = tb_sw_write(sw, ((u32 *) &sw->config) + 4, TB_CFG_SWITCH, 4, 1); + if (res) + return res; + + res = tb_sw_read(sw, &data, TB_CFG_SWITCH, sw->cap_plug_events + 1, 1); + if (res) + return res; + + if (active) { + data = data & 0xFFFFFF83; + switch (sw->config.device_id) { + case 0x1513: + case 0x151a: + case 0x1549: + break; + default: + data |= 4; + } + } else { + data = data | 0x7c; + } + return tb_sw_write(sw, &data, TB_CFG_SWITCH, + sw->cap_plug_events + 1, 1); +} + + /** * tb_switch_free() - free a tb_switch and all downstream switches */ @@ -113,6 +152,8 @@ void tb_switch_free(struct tb_switch *sw) sw->ports[i].remote = NULL; } + tb_plug_events_active(sw, false); + kfree(sw->ports); kfree(sw); } @@ -125,6 +166,7 @@ void tb_switch_free(struct tb_switch *sw) struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route) { int i; + int cap; struct tb_switch *sw; int upstream_port = tb_cfg_get_upstream_port(tb->ctl, route); if (upstream_port < 0) @@ -177,6 +219,16 @@ struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route) /* TODO: I2C, IECS, EEPROM, link controller */ + cap = tb_find_cap(&sw->ports[0], TB_CFG_SWITCH, TB_CAP_PLUG_EVENTS); + if (cap < 0) { + tb_sw_warn(sw, "cannot find TB_CAP_PLUG_EVENTS aborting\n"); + goto err; + } + sw->cap_plug_events = cap; + + if (tb_plug_events_active(sw, true)) + goto err; + return sw; err: kfree(sw->ports); diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 38e4c23c10fd..af123c4045e3 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -19,6 +19,7 @@ struct tb_switch { struct tb_regs_switch_header config; struct tb_port *ports; struct tb *tb; + int cap_plug_events; /* offset, zero if not found */ }; /** -- cgit v1.2.3 From 9da672a42878c58af5c50d7389dbae17bea9df38 Mon Sep 17 00:00:00 2001 From: Andreas Noever Date: Tue, 3 Jun 2014 22:04:05 +0200 Subject: thunderbolt: Scan for downstream switches Add utility methods tb_port_state and tb_wait_for_port. Add tb_scan_switch which recursively checks for downstream switches. Signed-off-by: Andreas Noever Signed-off-by: Greg Kroah-Hartman --- drivers/thunderbolt/switch.c | 97 ++++++++++++++++++++++++++++++++++++++++++++ drivers/thunderbolt/tb.c | 44 ++++++++++++++++++++ drivers/thunderbolt/tb.h | 16 ++++++++ 3 files changed, 157 insertions(+) (limited to 'drivers/thunderbolt/switch.c') diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index 6d193a230cf4..b31b8cef301d 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -52,6 +52,92 @@ static void tb_dump_port(struct tb *tb, struct tb_regs_port_header *port) tb_info(tb, " NFC Credits: %#x\n", port->nfc_credits); } +/** + * tb_port_state() - get connectedness state of a port + * + * The port must have a TB_CAP_PHY (i.e. it should be a real port). + * + * Return: Returns an enum tb_port_state on success or an error code on failure. + */ +static int tb_port_state(struct tb_port *port) +{ + struct tb_cap_phy phy; + int res; + if (port->cap_phy == 0) { + tb_port_WARN(port, "does not have a PHY\n"); + return -EINVAL; + } + res = tb_port_read(port, &phy, TB_CFG_PORT, port->cap_phy, 2); + if (res) + return res; + return phy.state; +} + +/** + * tb_wait_for_port() - wait for a port to become ready + * + * Wait up to 1 second for a port to reach state TB_PORT_UP. If + * wait_if_unplugged is set then we also wait if the port is in state + * TB_PORT_UNPLUGGED (it takes a while for the device to be registered after + * switch resume). Otherwise we only wait if a device is registered but the link + * has not yet been established. + * + * Return: Returns an error code on failure. Returns 0 if the port is not + * connected or failed to reach state TB_PORT_UP within one second. Returns 1 + * if the port is connected and in state TB_PORT_UP. + */ +int tb_wait_for_port(struct tb_port *port, bool wait_if_unplugged) +{ + int retries = 10; + int state; + if (!port->cap_phy) { + tb_port_WARN(port, "does not have PHY\n"); + return -EINVAL; + } + if (tb_is_upstream_port(port)) { + tb_port_WARN(port, "is the upstream port\n"); + return -EINVAL; + } + + while (retries--) { + state = tb_port_state(port); + if (state < 0) + return state; + if (state == TB_PORT_DISABLED) { + tb_port_info(port, "is disabled (state: 0)\n"); + return 0; + } + if (state == TB_PORT_UNPLUGGED) { + if (wait_if_unplugged) { + /* used during resume */ + tb_port_info(port, + "is unplugged (state: 7), retrying...\n"); + msleep(100); + continue; + } + tb_port_info(port, "is unplugged (state: 7)\n"); + return 0; + } + if (state == TB_PORT_UP) { + tb_port_info(port, + "is connected, link is up (state: 2)\n"); + return 1; + } + + /* + * After plug-in the state is TB_PORT_CONNECTING. Give it some + * time. + */ + tb_port_info(port, + "is connected, link is not up (state: %d), retrying...\n", + state); + msleep(100); + } + tb_port_warn(port, + "failed to reach state TB_PORT_UP. Ignoring port...\n"); + return 0; +} + /** * tb_init_port() - initialize a port * @@ -63,6 +149,7 @@ static void tb_dump_port(struct tb *tb, struct tb_regs_port_header *port) static int tb_init_port(struct tb_switch *sw, u8 port_nr) { int res; + int cap; struct tb_port *port = &sw->ports[port_nr]; port->sw = sw; port->port = port_nr; @@ -71,6 +158,16 @@ static int tb_init_port(struct tb_switch *sw, u8 port_nr) if (res) return res; + /* Port 0 is the switch itself and has no PHY. */ + if (port->config.type == TB_TYPE_PORT && port_nr != 0) { + cap = tb_find_cap(port, TB_CFG_PORT, TB_CAP_PHY); + + if (cap > 0) + port->cap_phy = cap; + else + tb_port_WARN(port, "non switch port without a PHY\n"); + } + tb_dump_port(sw->tb, &port->config); /* TODO: Read dual link port, DP port and more from EEPROM. */ diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index f1b6100b6cf0..3b716fd123f6 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -11,6 +11,47 @@ #include "tb.h" #include "tb_regs.h" + +/* enumeration & hot plug handling */ + + +static void tb_scan_port(struct tb_port *port); + +/** + * tb_scan_switch() - scan for and initialize downstream switches + */ +static void tb_scan_switch(struct tb_switch *sw) +{ + int i; + for (i = 1; i <= sw->config.max_port_number; i++) + tb_scan_port(&sw->ports[i]); +} + +/** + * tb_scan_port() - check for and initialize switches below port + */ +static void tb_scan_port(struct tb_port *port) +{ + struct tb_switch *sw; + if (tb_is_upstream_port(port)) + return; + if (port->config.type != TB_TYPE_PORT) + return; + if (tb_wait_for_port(port, false) <= 0) + return; + if (port->remote) { + tb_port_WARN(port, "port already has a remote!\n"); + return; + } + sw = tb_switch_alloc(port->sw->tb, tb_downstream_route(port)); + if (!sw) + return; + port->remote = tb_upstream_port(sw); + tb_upstream_port(sw)->remote = port; + tb_scan_switch(sw); +} + + /* hotplug handling */ struct tb_hotplug_event { @@ -134,6 +175,9 @@ struct tb *thunderbolt_alloc_and_start(struct tb_nhi *nhi) if (!tb->root_switch) goto err_locked; + /* Full scan to discover devices added before the driver was loaded. */ + tb_scan_switch(tb->root_switch); + /* Allow tb_handle_hotplug to progress events */ tb->hotplug_active = true; mutex_unlock(&tb->lock); diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index af123c4045e3..70a66fef0177 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -29,6 +29,7 @@ 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 */ }; @@ -160,6 +161,8 @@ void thunderbolt_shutdown_and_free(struct tb *tb); struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route); void tb_switch_free(struct tb_switch *sw); +int tb_wait_for_port(struct tb_port *port, bool wait_if_unplugged); + int tb_find_cap(struct tb_port *port, enum tb_cfg_space space, u32 value); @@ -173,4 +176,17 @@ static inline bool tb_is_upstream_port(struct tb_port *port) return port == tb_upstream_port(port->sw); } +/** + * tb_downstream_route() - get route to downstream switch + * + * Port must not be the upstream port (otherwise a loop is created). + * + * Return: Returns a route to the switch behind @port. + */ +static inline u64 tb_downstream_route(struct tb_port *port) +{ + return tb_route(port->sw) + | ((u64) port->port << (port->sw->config.depth * 8)); +} + #endif -- cgit v1.2.3 From 053596d9e26c86352c4b2b372f43f2746b97de45 Mon Sep 17 00:00:00 2001 From: Andreas Noever Date: Tue, 3 Jun 2014 22:04:06 +0200 Subject: thunderbolt: Handle hotplug events We receive a plug event callback whenever a thunderbolt device is added or removed. This patch fills in the tb_handle_hotplug method and starts reacting to these events by adding/removing switches from the hierarchy. Signed-off-by: Andreas Noever Signed-off-by: Greg Kroah-Hartman --- drivers/thunderbolt/switch.c | 42 +++++++++++++++++++++++++++++++++++++++- drivers/thunderbolt/tb.c | 46 +++++++++++++++++++++++++++++++++++++++++++- drivers/thunderbolt/tb.h | 3 +++ 3 files changed, 89 insertions(+), 2 deletions(-) (limited to 'drivers/thunderbolt/switch.c') diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index b31b8cef301d..d6c32e1e2b42 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -195,6 +195,24 @@ static void tb_dump_switch(struct tb *tb, struct tb_regs_switch_header *sw) sw->__unknown1, sw->__unknown4); } +struct tb_switch *get_switch_at_route(struct tb_switch *sw, u64 route) +{ + u8 next_port = route; /* + * Routes use a stride of 8 bits, + * eventhough a port index has 6 bits at most. + * */ + if (route == 0) + return sw; + if (next_port > sw->config.max_port_number) + return 0; + if (tb_is_upstream_port(&sw->ports[next_port])) + return 0; + if (!sw->ports[next_port].remote) + return 0; + return get_switch_at_route(sw->ports[next_port].remote->sw, + route >> TB_ROUTE_SHIFT); +} + /** * tb_plug_events_active() - enable/disable plug events on a switch * @@ -249,7 +267,8 @@ void tb_switch_free(struct tb_switch *sw) sw->ports[i].remote = NULL; } - tb_plug_events_active(sw, false); + if (!sw->is_unplugged) + tb_plug_events_active(sw, false); kfree(sw->ports); kfree(sw); @@ -333,3 +352,24 @@ err: return NULL; } +/** + * tb_sw_set_unpplugged() - set is_unplugged on switch and downstream switches + */ +void tb_sw_set_unpplugged(struct tb_switch *sw) +{ + int i; + if (sw == sw->tb->root_switch) { + tb_sw_WARN(sw, "cannot unplug root switch\n"); + return; + } + if (sw->is_unplugged) { + tb_sw_WARN(sw, "is_unplugged already set\n"); + return; + } + sw->is_unplugged = true; + for (i = 0; i <= sw->config.max_port_number; i++) { + if (!tb_is_upstream_port(&sw->ports[i]) && sw->ports[i].remote) + tb_sw_set_unpplugged(sw->ports[i].remote->sw); + } +} + diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index 3b716fd123f6..1efcacc72104 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -71,11 +71,55 @@ static void tb_handle_hotplug(struct work_struct *work) { struct tb_hotplug_event *ev = container_of(work, typeof(*ev), work); struct tb *tb = ev->tb; + struct tb_switch *sw; + struct tb_port *port; mutex_lock(&tb->lock); if (!tb->hotplug_active) goto out; /* during init, suspend or shutdown */ - /* do nothing for now */ + sw = get_switch_at_route(tb->root_switch, ev->route); + if (!sw) { + tb_warn(tb, + "hotplug event from non existent switch %llx:%x (unplug: %d)\n", + ev->route, ev->port, ev->unplug); + goto out; + } + if (ev->port > sw->config.max_port_number) { + tb_warn(tb, + "hotplug event from non existent port %llx:%x (unplug: %d)\n", + ev->route, ev->port, ev->unplug); + goto out; + } + port = &sw->ports[ev->port]; + if (tb_is_upstream_port(port)) { + tb_warn(tb, + "hotplug event for upstream port %llx:%x (unplug: %d)\n", + ev->route, ev->port, ev->unplug); + goto out; + } + if (ev->unplug) { + if (port->remote) { + tb_port_info(port, "unplugged\n"); + tb_sw_set_unpplugged(port->remote->sw); + tb_switch_free(port->remote->sw); + port->remote = NULL; + } else { + tb_port_info(port, + "got unplug event for disconnected port, ignoring\n"); + } + } else if (port->remote) { + tb_port_info(port, + "got plug event for connected port, ignoring\n"); + } else { + tb_port_info(port, "hotplug: scanning\n"); + tb_scan_port(port); + if (!port->remote) { + tb_port_info(port, "hotplug: no switch found\n"); + } else if (port->remote->sw->config.depth > 1) { + tb_sw_warn(port->remote->sw, + "hotplug: chaining not supported\n"); + } + } out: mutex_unlock(&tb->lock); kfree(ev); diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 70a66fef0177..661f1828527a 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -20,6 +20,7 @@ struct tb_switch { struct tb_port *ports; struct tb *tb; int cap_plug_events; /* offset, zero if not found */ + bool is_unplugged; /* unplugged, will go away */ }; /** @@ -160,6 +161,8 @@ void thunderbolt_shutdown_and_free(struct tb *tb); struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route); void tb_switch_free(struct tb_switch *sw); +void tb_sw_set_unpplugged(struct tb_switch *sw); +struct tb_switch *get_switch_at_route(struct tb_switch *sw, u64 route); int tb_wait_for_port(struct tb_port *port, bool wait_if_unplugged); -- cgit v1.2.3 From 520b670216a15fb949e6ec6a1af9b5dd55d219c7 Mon Sep 17 00:00:00 2001 From: Andreas Noever Date: Tue, 3 Jun 2014 22:04:07 +0200 Subject: thunderbolt: Add path setup code. A thunderbolt path is a unidirectional channel between two thunderbolt ports. Two such paths are needed to establish a pci tunnel. This patch introduces struct tb_path as well as a set of tb_path_* methods which are used to activate & deactivate paths. Signed-off-by: Andreas Noever Signed-off-by: Greg Kroah-Hartman --- drivers/thunderbolt/Makefile | 2 +- drivers/thunderbolt/path.c | 215 +++++++++++++++++++++++++++++++++++++++++++ drivers/thunderbolt/switch.c | 34 +++++++ drivers/thunderbolt/tb.h | 62 +++++++++++++ 4 files changed, 312 insertions(+), 1 deletion(-) create mode 100644 drivers/thunderbolt/path.c (limited to 'drivers/thunderbolt/switch.c') diff --git a/drivers/thunderbolt/Makefile b/drivers/thunderbolt/Makefile index 617b31480b2e..3532f3684efc 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 +thunderbolt-objs := nhi.o ctl.o tb.o switch.o cap.o path.o diff --git a/drivers/thunderbolt/path.c b/drivers/thunderbolt/path.c new file mode 100644 index 000000000000..8fcf8a7b6c22 --- /dev/null +++ b/drivers/thunderbolt/path.c @@ -0,0 +1,215 @@ +/* + * Thunderbolt Cactus Ridge driver - path/tunnel functionality + * + * Copyright (c) 2014 Andreas Noever + */ + +#include +#include + +#include "tb.h" + + +static void tb_dump_hop(struct tb_port *port, struct tb_regs_hop *hop) +{ + tb_port_info(port, " Hop through port %d to hop %d (%s)\n", + hop->out_port, hop->next_hop, + hop->enable ? "enabled" : "disabled"); + tb_port_info(port, " Weight: %d Priority: %d Credits: %d Drop: %d\n", + hop->weight, hop->priority, + hop->initial_credits, hop->drop_packages); + tb_port_info(port, " Counter enabled: %d Counter index: %d\n", + hop->counter_enable, hop->counter); + tb_port_info(port, " Flow Control (In/Eg): %d/%d Shared Buffer (In/Eg): %d/%d\n", + hop->ingress_fc, hop->egress_fc, + hop->ingress_shared_buffer, hop->egress_shared_buffer); + tb_port_info(port, " Unknown1: %#x Unknown2: %#x Unknown3: %#x\n", + hop->unknown1, hop->unknown2, hop->unknown3); +} + +/** + * tb_path_alloc() - allocate a thunderbolt path + * + * Return: Returns a tb_path on success or NULL on failure. + */ +struct tb_path *tb_path_alloc(struct tb *tb, int num_hops) +{ + struct tb_path *path = kzalloc(sizeof(*path), GFP_KERNEL); + if (!path) + return NULL; + path->hops = kcalloc(num_hops, sizeof(*path->hops), GFP_KERNEL); + if (!path->hops) { + kfree(path); + return NULL; + } + path->tb = tb; + path->path_length = num_hops; + return path; +} + +/** + * tb_path_free() - free a deactivated path + */ +void tb_path_free(struct tb_path *path) +{ + if (path->activated) { + tb_WARN(path->tb, "trying to free an activated path\n") + return; + } + kfree(path->hops); + kfree(path); +} + +static void __tb_path_deallocate_nfc(struct tb_path *path, int first_hop) +{ + int i, res; + for (i = first_hop; i < path->path_length; i++) { + res = tb_port_add_nfc_credits(path->hops[i].in_port, + -path->nfc_credits); + if (res) + tb_port_warn(path->hops[i].in_port, + "nfc credits deallocation failed for hop %d\n", + i); + } +} + +static void __tb_path_deactivate_hops(struct tb_path *path, int first_hop) +{ + int i, res; + struct tb_regs_hop hop = { }; + for (i = first_hop; i < path->path_length; i++) { + res = tb_port_write(path->hops[i].in_port, &hop, TB_CFG_HOPS, + 2 * path->hops[i].in_hop_index, 2); + if (res) + tb_port_warn(path->hops[i].in_port, + "hop deactivation failed for hop %d, index %d\n", + i, path->hops[i].in_hop_index); + } +} + +void tb_path_deactivate(struct tb_path *path) +{ + if (!path->activated) { + tb_WARN(path->tb, "trying to deactivate an inactive path\n"); + return; + } + tb_info(path->tb, + "deactivating path from %llx:%x to %llx:%x\n", + tb_route(path->hops[0].in_port->sw), + path->hops[0].in_port->port, + tb_route(path->hops[path->path_length - 1].out_port->sw), + path->hops[path->path_length - 1].out_port->port); + __tb_path_deactivate_hops(path, 0); + __tb_path_deallocate_nfc(path, 0); + path->activated = false; +} + +/** + * tb_path_activate() - activate a path + * + * Activate a path starting with the last hop and iterating backwards. The + * caller must fill path->hops before calling tb_path_activate(). + * + * Return: Returns 0 on success or an error code on failure. + */ +int tb_path_activate(struct tb_path *path) +{ + int i, res; + enum tb_path_port out_mask, in_mask; + if (path->activated) { + tb_WARN(path->tb, "trying to activate already activated path\n"); + return -EINVAL; + } + + tb_info(path->tb, + "activating path from %llx:%x to %llx:%x\n", + tb_route(path->hops[0].in_port->sw), + path->hops[0].in_port->port, + tb_route(path->hops[path->path_length - 1].out_port->sw), + path->hops[path->path_length - 1].out_port->port); + + /* Clear counters. */ + for (i = path->path_length - 1; i >= 0; i--) { + if (path->hops[i].in_counter_index == -1) + continue; + res = tb_port_clear_counter(path->hops[i].in_port, + path->hops[i].in_counter_index); + if (res) + goto err; + } + + /* Add non flow controlled credits. */ + for (i = path->path_length - 1; i >= 0; i--) { + res = tb_port_add_nfc_credits(path->hops[i].in_port, + path->nfc_credits); + if (res) { + __tb_path_deallocate_nfc(path, i); + goto err; + } + } + + /* Activate hops. */ + for (i = path->path_length - 1; i >= 0; i--) { + struct tb_regs_hop hop; + + /* dword 0 */ + hop.next_hop = path->hops[i].next_hop_index; + hop.out_port = path->hops[i].out_port->port; + /* TODO: figure out why these are good values */ + hop.initial_credits = (i == path->path_length - 1) ? 16 : 7; + hop.unknown1 = 0; + hop.enable = 1; + + /* dword 1 */ + out_mask = (i == path->path_length - 1) ? + TB_PATH_DESTINATION : TB_PATH_INTERNAL; + in_mask = (i == 0) ? TB_PATH_SOURCE : TB_PATH_INTERNAL; + hop.weight = path->weight; + hop.unknown2 = 0; + hop.priority = path->priority; + hop.drop_packages = path->drop_packages; + hop.counter = path->hops[i].in_counter_index; + hop.counter_enable = path->hops[i].in_counter_index != -1; + hop.ingress_fc = path->ingress_fc_enable & in_mask; + hop.egress_fc = path->egress_fc_enable & out_mask; + hop.ingress_shared_buffer = path->ingress_shared_buffer + & in_mask; + hop.egress_shared_buffer = path->egress_shared_buffer + & out_mask; + hop.unknown3 = 0; + + tb_port_info(path->hops[i].in_port, "Writing hop %d, index %d", + i, path->hops[i].in_hop_index); + tb_dump_hop(path->hops[i].in_port, &hop); + res = tb_port_write(path->hops[i].in_port, &hop, TB_CFG_HOPS, + 2 * path->hops[i].in_hop_index, 2); + if (res) { + __tb_path_deactivate_hops(path, i); + __tb_path_deallocate_nfc(path, 0); + goto err; + } + } + path->activated = true; + tb_info(path->tb, "path activation complete\n"); + return 0; +err: + tb_WARN(path->tb, "path activation failed\n"); + return res; +} + +/** + * tb_path_is_invalid() - check whether any ports on the path are invalid + * + * Return: Returns true if the path is invalid, false otherwise. + */ +bool tb_path_is_invalid(struct tb_path *path) +{ + int i = 0; + for (i = 0; i < path->path_length; i++) { + if (path->hops[i].in_port->sw->is_unplugged) + return true; + if (path->hops[i].out_port->sw->is_unplugged) + return true; + } + return false; +} diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index d6c32e1e2b42..667413f3ad7a 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -138,6 +138,40 @@ int tb_wait_for_port(struct tb_port *port, bool wait_if_unplugged) return 0; } +/** + * tb_port_add_nfc_credits() - add/remove non flow controlled credits to port + * + * Change the number of NFC credits allocated to @port by @credits. To remove + * NFC credits pass a negative amount of credits. + * + * Return: Returns 0 on success or an error code on failure. + */ +int tb_port_add_nfc_credits(struct tb_port *port, int credits) +{ + if (credits == 0) + return 0; + tb_port_info(port, + "adding %#x NFC credits (%#x -> %#x)", + credits, + port->config.nfc_credits, + port->config.nfc_credits + credits); + port->config.nfc_credits += credits; + return tb_port_write(port, &port->config.nfc_credits, + TB_CFG_PORT, 4, 1); +} + +/** + * tb_port_clear_counter() - clear a counter in TB_CFG_COUNTER + * + * Return: Returns 0 on success or an error code on failure. + */ +int tb_port_clear_counter(struct tb_port *port, int counter) +{ + u32 zero[3] = { 0, 0, 0 }; + tb_port_info(port, "clearing counter %d\n", counter); + return tb_port_write(port, zero, TB_CFG_COUNTERS, 3 * counter, 3); +} + /** * tb_init_port() - initialize a port * diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 661f1828527a..8bbdc2bc4d09 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -34,6 +34,60 @@ struct tb_port { u8 port; /* port number on switch */ }; +/** + * struct tb_path_hop - routing information for a tb_path + * + * Hop configuration is always done on the IN port of a switch. + * in_port and out_port have to be on the same switch. Packets arriving on + * in_port with "hop" = in_hop_index will get routed to through out_port. The + * next hop to take (on out_port->remote) is determined by next_hop_index. + * + * in_counter_index is the index of a counter (in TB_CFG_COUNTERS) on the in + * port. + */ +struct tb_path_hop { + struct tb_port *in_port; + struct tb_port *out_port; + int in_hop_index; + int in_counter_index; /* write -1 to disable counters for this hop. */ + int next_hop_index; +}; + +/** + * enum tb_path_port - path options mask + */ +enum tb_path_port { + TB_PATH_NONE = 0, + TB_PATH_SOURCE = 1, /* activate on the first hop (out of src) */ + TB_PATH_INTERNAL = 2, /* activate on other hops (not the first/last) */ + TB_PATH_DESTINATION = 4, /* activate on the last hop (into dst) */ + TB_PATH_ALL = 7, +}; + +/** + * struct tb_path - a unidirectional path between two ports + * + * A path consists of a number of hops (see tb_path_hop). To establish a PCIe + * tunnel two paths have to be created between the two PCIe ports. + * + */ +struct tb_path { + struct tb *tb; + int nfc_credits; /* non flow controlled credits */ + enum tb_path_port ingress_shared_buffer; + enum tb_path_port egress_shared_buffer; + enum tb_path_port ingress_fc_enable; + enum tb_path_port egress_fc_enable; + + int priority:3; + int weight:4; + bool drop_packages; + bool activated; + struct tb_path_hop *hops; + int path_length; /* number of hops */ +}; + + /** * struct tb - main thunderbolt bus structure */ @@ -165,9 +219,17 @@ void tb_sw_set_unpplugged(struct tb_switch *sw); struct tb_switch *get_switch_at_route(struct tb_switch *sw, u64 route); int tb_wait_for_port(struct tb_port *port, bool wait_if_unplugged); +int tb_port_add_nfc_credits(struct tb_port *port, int credits); +int tb_port_clear_counter(struct tb_port *port, int counter); int tb_find_cap(struct tb_port *port, enum tb_cfg_space space, u32 value); +struct tb_path *tb_path_alloc(struct tb *tb, int num_hops); +void tb_path_free(struct tb_path *path); +int tb_path_activate(struct tb_path *path); +void tb_path_deactivate(struct tb_path *path); +bool tb_path_is_invalid(struct tb_path *path); + static inline int tb_route_length(u64 route) { -- cgit v1.2.3 From c90553b3c4ac2389a71a5c012b6e5bb1160d48a7 Mon Sep 17 00:00:00 2001 From: Andreas Noever Date: Tue, 3 Jun 2014 22:04:11 +0200 Subject: thunderbolt: Read switch uid from EEPROM Add eeprom access code and read the uid during switch initialization. The UID will be used to check device identity after suspend. Signed-off-by: Andreas Noever Signed-off-by: Greg Kroah-Hartman --- drivers/thunderbolt/Makefile | 2 +- drivers/thunderbolt/eeprom.c | 189 +++++++++++++++++++++++++++++++++++++++++++ drivers/thunderbolt/switch.c | 5 ++ drivers/thunderbolt/tb.h | 3 + 4 files changed, 198 insertions(+), 1 deletion(-) create mode 100644 drivers/thunderbolt/eeprom.c (limited to 'drivers/thunderbolt/switch.c') diff --git a/drivers/thunderbolt/Makefile b/drivers/thunderbolt/Makefile index 0122ca698f78..5d1053cdfa54 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 +thunderbolt-objs := nhi.o ctl.o tb.o switch.o cap.o path.o tunnel_pci.o eeprom.o diff --git a/drivers/thunderbolt/eeprom.c b/drivers/thunderbolt/eeprom.c new file mode 100644 index 000000000000..f28e40231c9e --- /dev/null +++ b/drivers/thunderbolt/eeprom.c @@ -0,0 +1,189 @@ +/* + * Thunderbolt Cactus Ridge driver - eeprom access + * + * Copyright (c) 2014 Andreas Noever + */ + +#include "tb.h" + +/** + * tb_eeprom_ctl_write() - write control word + */ +static int tb_eeprom_ctl_write(struct tb_switch *sw, struct tb_eeprom_ctl *ctl) +{ + return tb_sw_write(sw, ctl, TB_CFG_SWITCH, sw->cap_plug_events + 4, 1); +} + +/** + * tb_eeprom_ctl_write() - read control word + */ +static int tb_eeprom_ctl_read(struct tb_switch *sw, struct tb_eeprom_ctl *ctl) +{ + return tb_sw_read(sw, ctl, TB_CFG_SWITCH, sw->cap_plug_events + 4, 1); +} + +enum tb_eeprom_transfer { + TB_EEPROM_IN, + TB_EEPROM_OUT, +}; + +/** + * tb_eeprom_active - enable rom access + * + * WARNING: Always disable access after usage. Otherwise the controller will + * fail to reprobe. + */ +static int tb_eeprom_active(struct tb_switch *sw, bool enable) +{ + struct tb_eeprom_ctl ctl; + int res = tb_eeprom_ctl_read(sw, &ctl); + if (res) + return res; + if (enable) { + ctl.access_high = 1; + res = tb_eeprom_ctl_write(sw, &ctl); + if (res) + return res; + ctl.access_low = 0; + return tb_eeprom_ctl_write(sw, &ctl); + } else { + ctl.access_low = 1; + res = tb_eeprom_ctl_write(sw, &ctl); + if (res) + return res; + ctl.access_high = 0; + return tb_eeprom_ctl_write(sw, &ctl); + } +} + +/** + * tb_eeprom_transfer - transfer one bit + * + * If TB_EEPROM_IN is passed, then the bit can be retrieved from ctl->data_in. + * If TB_EEPROM_OUT is passed, then ctl->data_out will be written. + */ +static int tb_eeprom_transfer(struct tb_switch *sw, struct tb_eeprom_ctl *ctl, + enum tb_eeprom_transfer direction) +{ + int res; + if (direction == TB_EEPROM_OUT) { + res = tb_eeprom_ctl_write(sw, ctl); + if (res) + return res; + } + ctl->clock = 1; + res = tb_eeprom_ctl_write(sw, ctl); + if (res) + return res; + if (direction == TB_EEPROM_IN) { + res = tb_eeprom_ctl_read(sw, ctl); + if (res) + return res; + } + ctl->clock = 0; + return tb_eeprom_ctl_write(sw, ctl); +} + +/** + * tb_eeprom_out - write one byte to the bus + */ +static int tb_eeprom_out(struct tb_switch *sw, u8 val) +{ + struct tb_eeprom_ctl ctl; + int i; + int res = tb_eeprom_ctl_read(sw, &ctl); + if (res) + return res; + for (i = 0; i < 8; i++) { + ctl.data_out = val & 0x80; + res = tb_eeprom_transfer(sw, &ctl, TB_EEPROM_OUT); + if (res) + return res; + val <<= 1; + } + return 0; +} + +/** + * tb_eeprom_in - read one byte from the bus + */ +static int tb_eeprom_in(struct tb_switch *sw, u8 *val) +{ + struct tb_eeprom_ctl ctl; + int i; + int res = tb_eeprom_ctl_read(sw, &ctl); + if (res) + return res; + *val = 0; + for (i = 0; i < 8; i++) { + *val <<= 1; + res = tb_eeprom_transfer(sw, &ctl, TB_EEPROM_IN); + if (res) + return res; + *val |= ctl.data_in; + } + return 0; +} + +/** + * tb_eeprom_read_n - read count bytes from offset into val + */ +static int tb_eeprom_read_n(struct tb_switch *sw, u16 offset, u8 *val, + size_t count) +{ + int i, res; + res = tb_eeprom_active(sw, true); + if (res) + return res; + res = tb_eeprom_out(sw, 3); + if (res) + return res; + res = tb_eeprom_out(sw, offset >> 8); + if (res) + return res; + res = tb_eeprom_out(sw, offset); + if (res) + return res; + for (i = 0; i < count; i++) { + res = tb_eeprom_in(sw, val + i); + if (res) + return res; + } + return tb_eeprom_active(sw, false); +} + +int tb_eeprom_read_uid(struct tb_switch *sw, u64 *uid) +{ + u8 data[9]; + struct tb_cap_plug_events cap; + int res; + if (!sw->cap_plug_events) { + tb_sw_warn(sw, "no TB_CAP_PLUG_EVENTS, cannot read eeprom\n"); + return -ENOSYS; + } + res = tb_sw_read(sw, &cap, TB_CFG_SWITCH, sw->cap_plug_events, + sizeof(cap) / 4); + if (res) + return res; + if (!cap.eeprom_ctl.present || cap.eeprom_ctl.not_present) { + tb_sw_warn(sw, "no NVM\n"); + return -ENOSYS; + } + + if (cap.drom_offset > 0xffff) { + tb_sw_warn(sw, "drom offset is larger than 0xffff: %#x\n", + cap.drom_offset); + return -ENXIO; + } + + /* read uid */ + res = tb_eeprom_read_n(sw, cap.drom_offset, data, 9); + if (res) + return res; + /* TODO: check checksum in data[0] */ + *uid = *(u64 *)(data+1); + return 0; +} + + + diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index 667413f3ad7a..aeb5c30f8d76 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -376,6 +376,11 @@ struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route) } sw->cap_plug_events = cap; + if (tb_eeprom_read_uid(sw, &sw->uid)) + tb_sw_warn(sw, "could not read uid from eeprom\n"); + else + tb_sw_info(sw, "uid: %#llx\n", sw->uid); + if (tb_plug_events_active(sw, true)) goto err; diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 508abc426563..a89087f2da71 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -19,6 +19,7 @@ struct tb_switch { struct tb_regs_switch_header config; struct tb_port *ports; struct tb *tb; + u64 uid; int cap_plug_events; /* offset, zero if not found */ bool is_unplugged; /* unplugged, will go away */ }; @@ -231,6 +232,8 @@ int tb_path_activate(struct tb_path *path); void tb_path_deactivate(struct tb_path *path); bool tb_path_is_invalid(struct tb_path *path); +int tb_eeprom_read_uid(struct tb_switch *sw, u64 *uid); + static inline int tb_route_length(u64 route) { -- cgit v1.2.3 From 23dd5bb49d986f37977ed80dd2ca65040ead4392 Mon Sep 17 00:00:00 2001 From: Andreas Noever Date: Tue, 3 Jun 2014 22:04:12 +0200 Subject: thunderbolt: Add suspend/hibernate support We use _noirq since we have to restore the pci tunnels before the pci core wakes the tunneled devices. Signed-off-by: Andreas Noever Signed-off-by: Greg Kroah-Hartman --- drivers/thunderbolt/nhi.c | 33 +++++++++++++++++ drivers/thunderbolt/switch.c | 84 ++++++++++++++++++++++++++++++++++++++++++++ drivers/thunderbolt/tb.c | 61 ++++++++++++++++++++++++++++++++ drivers/thunderbolt/tb.h | 5 +++ 4 files changed, 183 insertions(+) (limited to 'drivers/thunderbolt/switch.c') diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index d2b9ce857818..346b41e7d5d1 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -7,6 +7,7 @@ * Copyright (c) 2014 Andreas Noever */ +#include #include #include #include @@ -492,6 +493,22 @@ static irqreturn_t nhi_msi(int irq, void *data) return IRQ_HANDLED; } +static int nhi_suspend_noirq(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct tb *tb = pci_get_drvdata(pdev); + thunderbolt_suspend(tb); + return 0; +} + +static int nhi_resume_noirq(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct tb *tb = pci_get_drvdata(pdev); + thunderbolt_resume(tb); + return 0; +} + static void nhi_shutdown(struct tb_nhi *nhi) { int i; @@ -600,6 +617,21 @@ static void nhi_remove(struct pci_dev *pdev) nhi_shutdown(nhi); } +/* + * The tunneled pci bridges are siblings of us. Use resume_noirq to reenable + * the tunnels asap. A corresponding pci quirk blocks the downstream bridges + * resume_noirq until we are done. + */ +static const struct dev_pm_ops nhi_pm_ops = { + .suspend_noirq = nhi_suspend_noirq, + .resume_noirq = nhi_resume_noirq, + .freeze_noirq = nhi_suspend_noirq, /* + * we just disable hotplug, the + * pci-tunnels stay alive. + */ + .restore_noirq = nhi_resume_noirq, +}; + struct pci_device_id nhi_ids[] = { /* * We have to specify class, the TB bridges use the same device and @@ -626,6 +658,7 @@ static struct pci_driver nhi_driver = { .id_table = nhi_ids, .probe = nhi_probe, .remove = nhi_remove, + .driver.pm = &nhi_pm_ops, }; static int __init nhi_init(void) diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index aeb5c30f8d76..c2a24b6fb883 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -229,6 +229,30 @@ static void tb_dump_switch(struct tb *tb, struct tb_regs_switch_header *sw) sw->__unknown1, sw->__unknown4); } +/** + * reset_switch() - reconfigure route, enable and send TB_CFG_PKG_RESET + * + * Return: Returns 0 on success or an error code on failure. + */ +int tb_switch_reset(struct tb *tb, u64 route) +{ + struct tb_cfg_result res; + struct tb_regs_switch_header header = { + header.route_hi = route >> 32, + header.route_lo = route, + header.enabled = true, + }; + tb_info(tb, "resetting switch at %llx\n", route); + res.err = tb_cfg_write(tb->ctl, ((u32 *) &header) + 2, route, + 0, 2, 2, 2); + if (res.err) + return res.err; + res = tb_cfg_reset(tb->ctl, route, TB_CFG_DEFAULT_TIMEOUT); + if (res.err > 0) + return -EIO; + return res.err; +} + struct tb_switch *get_switch_at_route(struct tb_switch *sw, u64 route) { u8 next_port = route; /* @@ -412,3 +436,63 @@ void tb_sw_set_unpplugged(struct tb_switch *sw) } } +int tb_switch_resume(struct tb_switch *sw) +{ + int i, err; + u64 uid; + tb_sw_info(sw, "resuming switch\n"); + + err = tb_eeprom_read_uid(sw, &uid); + if (err) { + tb_sw_warn(sw, "uid read failed\n"); + return err; + } + if (sw->uid != uid) { + tb_sw_info(sw, + "changed while suspended (uid %#llx -> %#llx)\n", + sw->uid, uid); + return -ENODEV; + } + + /* upload configuration */ + err = tb_sw_write(sw, 1 + (u32 *) &sw->config, TB_CFG_SWITCH, 1, 3); + if (err) + return err; + + err = tb_plug_events_active(sw, true); + if (err) + return err; + + /* check for surviving downstream switches */ + for (i = 1; i <= sw->config.max_port_number; i++) { + struct tb_port *port = &sw->ports[i]; + if (tb_is_upstream_port(port)) + continue; + if (!port->remote) + continue; + if (tb_wait_for_port(port, true) <= 0 + || tb_switch_resume(port->remote->sw)) { + tb_port_warn(port, + "lost during suspend, disconnecting\n"); + tb_sw_set_unpplugged(port->remote->sw); + } + } + return 0; +} + +void tb_switch_suspend(struct tb_switch *sw) +{ + int i, err; + err = tb_plug_events_active(sw, false); + if (err) + return; + + for (i = 1; i <= sw->config.max_port_number; i++) { + if (!tb_is_upstream_port(&sw->ports[i]) && sw->ports[i].remote) + tb_switch_suspend(sw->ports[i].remote->sw); + } + /* + * TODO: invoke tb_cfg_prepare_to_sleep here? does not seem to have any + * effect? + */ +} diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index 177f61df464d..1aa6dd7dc68b 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -68,6 +68,28 @@ static void tb_free_invalid_tunnels(struct tb *tb) } } +/** + * tb_free_unplugged_children() - traverse hierarchy and free unplugged switches + */ +static void tb_free_unplugged_children(struct tb_switch *sw) +{ + int i; + for (i = 1; i <= sw->config.max_port_number; i++) { + struct tb_port *port = &sw->ports[i]; + if (tb_is_upstream_port(port)) + continue; + if (!port->remote) + continue; + if (port->remote->sw->is_unplugged) { + tb_switch_free(port->remote->sw); + port->remote = NULL; + } else { + tb_free_unplugged_children(port->remote->sw); + } + } +} + + /** * find_pci_up_port() - return the first PCIe up port on @sw or NULL */ @@ -368,3 +390,42 @@ err_locked: return NULL; } +void thunderbolt_suspend(struct tb *tb) +{ + tb_info(tb, "suspending...\n"); + mutex_lock(&tb->lock); + tb_switch_suspend(tb->root_switch); + tb_ctl_stop(tb->ctl); + tb->hotplug_active = false; /* signal tb_handle_hotplug to quit */ + mutex_unlock(&tb->lock); + tb_info(tb, "suspend finished\n"); +} + +void thunderbolt_resume(struct tb *tb) +{ + struct tb_pci_tunnel *tunnel, *n; + tb_info(tb, "resuming...\n"); + mutex_lock(&tb->lock); + tb_ctl_start(tb->ctl); + + /* remove any pci devices the firmware might have setup */ + tb_switch_reset(tb, 0); + + tb_switch_resume(tb->root_switch); + tb_free_invalid_tunnels(tb); + tb_free_unplugged_children(tb->root_switch); + list_for_each_entry_safe(tunnel, n, &tb->tunnel_list, list) + tb_pci_restart(tunnel); + if (!list_empty(&tb->tunnel_list)) { + /* + * the pcie links need some time to get going. + * 100ms works for me... + */ + tb_info(tb, "tunnels restarted, sleeping for 100ms\n"); + msleep(100); + } + /* Allow tb_handle_hotplug to progress events */ + tb->hotplug_active = true; + mutex_unlock(&tb->lock); + tb_info(tb, "resume finished\n"); +} diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index a89087f2da71..63e89d01047c 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -214,9 +214,14 @@ static inline int tb_port_write(struct tb_port *port, void *buffer, struct tb *thunderbolt_alloc_and_start(struct tb_nhi *nhi); void thunderbolt_shutdown_and_free(struct tb *tb); +void thunderbolt_suspend(struct tb *tb); +void thunderbolt_resume(struct tb *tb); struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route); void tb_switch_free(struct tb_switch *sw); +void tb_switch_suspend(struct tb_switch *sw); +int tb_switch_resume(struct tb_switch *sw); +int tb_switch_reset(struct tb *tb, u64 route); void tb_sw_set_unpplugged(struct tb_switch *sw); struct tb_switch *get_switch_at_route(struct tb_switch *sw, u64 route); -- cgit v1.2.3 From cd22e73bdf5eff7e68a0f8bdfbce123ad43651f6 Mon Sep 17 00:00:00 2001 From: Andreas Noever Date: Thu, 12 Jun 2014 23:11:46 +0200 Subject: thunderbolt: Read port configuration from eeprom. All Thunderbolt switches (except the root switch) contain a drom which contains information about the device. Right now we only read the UID. Add code to read and parse this drom. For now we are only interested in which ports are disabled and which ports are "dual link ports" (a physical thunderbolt port/socket contains two such ports). Signed-off-by: Andreas Noever Signed-off-by: Greg Kroah-Hartman --- drivers/thunderbolt/eeprom.c | 266 ++++++++++++++++++++++++++++++++++++++++++- drivers/thunderbolt/switch.c | 4 +- drivers/thunderbolt/tb.h | 7 +- 3 files changed, 270 insertions(+), 7 deletions(-) (limited to 'drivers/thunderbolt/switch.c') diff --git a/drivers/thunderbolt/eeprom.c b/drivers/thunderbolt/eeprom.c index f28e40231c9e..0d5a80b2d07a 100644 --- a/drivers/thunderbolt/eeprom.c +++ b/drivers/thunderbolt/eeprom.c @@ -4,6 +4,7 @@ * Copyright (c) 2014 Andreas Noever */ +#include #include "tb.h" /** @@ -152,9 +153,86 @@ static int tb_eeprom_read_n(struct tb_switch *sw, u16 offset, u8 *val, return tb_eeprom_active(sw, false); } -int tb_eeprom_read_uid(struct tb_switch *sw, u64 *uid) +static u8 tb_crc8(u8 *data, int len) +{ + int i, j; + u8 val = 0xff; + for (i = 0; i < len; i++) { + val ^= data[i]; + for (j = 0; j < 8; j++) + val = (val << 1) ^ ((val & 0x80) ? 7 : 0); + } + return val; +} + +static u32 tb_crc32(void *data, size_t len) +{ + return ~__crc32c_le(~0, data, len); +} + +#define TB_DROM_DATA_START 13 +struct tb_drom_header { + /* BYTE 0 */ + u8 uid_crc8; /* checksum for uid */ + /* BYTES 1-8 */ + u64 uid; + /* BYTES 9-12 */ + u32 data_crc32; /* checksum for data_len bytes starting at byte 13 */ + /* BYTE 13 */ + u8 device_rom_revision; /* should be <= 1 */ + u16 data_len:10; + u8 __unknown1:6; + /* BYTES 16-21 */ + u16 vendor_id; + u16 model_id; + u8 model_rev; + u8 eeprom_rev; +} __packed; + +enum tb_drom_entry_type { + TB_DROM_ENTRY_GENERIC, + TB_DROM_ENTRY_PORT, +}; + +struct tb_drom_entry_header { + u8 len; + u8 index:6; + bool port_disabled:1; /* only valid if type is TB_DROM_ENTRY_PORT */ + enum tb_drom_entry_type type:1; +} __packed; + +struct tb_drom_entry_port { + /* BYTES 0-1 */ + struct tb_drom_entry_header header; + /* BYTE 2 */ + u8 dual_link_port_rid:4; + u8 link_nr:1; + u8 unknown1:2; + bool has_dual_link_port:1; + + /* BYTE 3 */ + u8 dual_link_port_nr:6; + u8 unknown2:2; + + /* BYTES 4 - 5 TODO decode */ + u8 micro2:4; + u8 micro1:4; + u8 micro3; + + /* BYTES 5-6, TODO: verify (find hardware that has these set) */ + u8 peer_port_rid:4; + u8 unknown3:3; + bool has_peer_port:1; + u8 peer_port_nr:6; + u8 unknown4:2; +} __packed; + + +/** + * tb_eeprom_get_drom_offset - get drom offset within eeprom + */ +int tb_eeprom_get_drom_offset(struct tb_switch *sw, u16 *offset) { - u8 data[9]; struct tb_cap_plug_events cap; int res; if (!sw->cap_plug_events) { @@ -165,6 +243,7 @@ int tb_eeprom_read_uid(struct tb_switch *sw, u64 *uid) sizeof(cap) / 4); if (res) return res; + if (!cap.eeprom_ctl.present || cap.eeprom_ctl.not_present) { tb_sw_warn(sw, "no NVM\n"); return -ENOSYS; @@ -175,15 +254,194 @@ int tb_eeprom_read_uid(struct tb_switch *sw, u64 *uid) cap.drom_offset); return -ENXIO; } + *offset = cap.drom_offset; + return 0; +} + +/** + * tb_drom_read_uid_only - read uid directly from drom + * + * Does not use the cached copy in sw->drom. Used during resume to check switch + * identity. + */ +int tb_drom_read_uid_only(struct tb_switch *sw, u64 *uid) +{ + u8 data[9]; + u16 drom_offset; + u8 crc; + int res = tb_eeprom_get_drom_offset(sw, &drom_offset); + if (res) + return res; /* read uid */ - res = tb_eeprom_read_n(sw, cap.drom_offset, data, 9); + res = tb_eeprom_read_n(sw, drom_offset, data, 9); if (res) return res; - /* TODO: check checksum in data[0] */ + + crc = tb_crc8(data + 1, 8); + if (crc != data[0]) { + tb_sw_warn(sw, "uid crc8 missmatch (expected: %#x, got: %#x)\n", + data[0], crc); + return -EIO; + } + *uid = *(u64 *)(data+1); return 0; } +static void tb_drom_parse_port_entry(struct tb_port *port, + struct tb_drom_entry_port *entry) +{ + port->link_nr = entry->link_nr; + if (entry->has_dual_link_port) + port->dual_link_port = + &port->sw->ports[entry->dual_link_port_nr]; +} + +static int tb_drom_parse_entry(struct tb_switch *sw, + struct tb_drom_entry_header *header) +{ + struct tb_port *port; + int res; + enum tb_port_type type; + if (header->type != TB_DROM_ENTRY_PORT) + return 0; + port = &sw->ports[header->index]; + port->disabled = header->port_disabled; + if (port->disabled) + return 0; + + res = tb_port_read(port, &type, TB_CFG_PORT, 2, 1); + if (res) + return res; + type &= 0xffffff; + + if (type == TB_TYPE_PORT) { + struct tb_drom_entry_port *entry = (void *) header; + if (header->len != sizeof(*entry)) { + tb_sw_warn(sw, + "port entry has size %#x (expected %#lx)\n", + header->len, sizeof(struct tb_drom_entry_port)); + return -EIO; + } + tb_drom_parse_port_entry(port, entry); + } + return 0; +} + +/** + * tb_drom_parse_entries - parse the linked list of drom entries + * + * Drom must have been copied to sw->drom. + */ +static int tb_drom_parse_entries(struct tb_switch *sw) +{ + struct tb_drom_header *header = (void *) sw->drom; + u16 pos = sizeof(*header); + u16 drom_size = header->data_len + TB_DROM_DATA_START; + + while (pos < drom_size) { + struct tb_drom_entry_header *entry = (void *) (sw->drom + pos); + if (pos + 1 == drom_size || pos + entry->len > drom_size + || !entry->len) { + tb_sw_warn(sw, "drom buffer overrun, aborting\n"); + return -EIO; + } + + tb_drom_parse_entry(sw, entry); + + pos += entry->len; + } + return 0; +} + +/** + * tb_drom_read - copy drom to sw->drom and parse it + */ +int tb_drom_read(struct tb_switch *sw) +{ + u16 drom_offset; + u16 size; + u32 crc; + struct tb_drom_header *header; + int res; + if (sw->drom) + return 0; + + if (tb_route(sw) == 0) { + /* + * The root switch contains only a dummy drom (header only, + * no entries). Hardcode the configuration here. + */ + tb_drom_read_uid_only(sw, &sw->uid); + + sw->ports[1].link_nr = 0; + sw->ports[2].link_nr = 1; + sw->ports[1].dual_link_port = &sw->ports[2]; + sw->ports[2].dual_link_port = &sw->ports[1]; + + sw->ports[3].link_nr = 0; + sw->ports[4].link_nr = 1; + sw->ports[3].dual_link_port = &sw->ports[4]; + sw->ports[4].dual_link_port = &sw->ports[3]; + return 0; + } + + res = tb_eeprom_get_drom_offset(sw, &drom_offset); + if (res) + return res; + + res = tb_eeprom_read_n(sw, drom_offset + 14, (u8 *) &size, 2); + if (res) + return res; + size &= 0x3ff; + size += TB_DROM_DATA_START; + tb_sw_info(sw, "reading drom (length: %#x)\n", size); + if (size < sizeof(*header)) { + tb_sw_warn(sw, "drom too small, aborting\n"); + return -EIO; + } + + sw->drom = kzalloc(size, GFP_KERNEL); + if (!sw->drom) + return -ENOMEM; + res = tb_eeprom_read_n(sw, drom_offset, sw->drom, size); + if (res) + goto err; + + header = (void *) sw->drom; + + if (header->data_len + TB_DROM_DATA_START != size) { + tb_sw_warn(sw, "drom size mismatch, aborting\n"); + goto err; + } + + crc = tb_crc8((u8 *) &header->uid, 8); + if (crc != header->uid_crc8) { + tb_sw_warn(sw, + "drom uid crc8 mismatch (expected: %#x, got: %#x), aborting\n", + header->uid_crc8, crc); + goto err; + } + sw->uid = header->uid; + + crc = tb_crc32(sw->drom + TB_DROM_DATA_START, header->data_len); + if (crc != header->data_crc32) { + tb_sw_warn(sw, + "drom data crc32 mismatch (expected: %#x, got: %#x), aborting\n", + header->data_crc32, crc); + goto err; + } + + if (header->device_rom_revision > 1) + tb_sw_warn(sw, "drom device_rom_revision %#x unknown\n", + header->device_rom_revision); + + return tb_drom_parse_entries(sw); +err: + kfree(sw->drom); + return -EIO; + +} diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index c2a24b6fb883..9dfb8e18cdf7 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -400,7 +400,7 @@ struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route) } sw->cap_plug_events = cap; - if (tb_eeprom_read_uid(sw, &sw->uid)) + if (tb_drom_read_uid_only(sw, &sw->uid)) tb_sw_warn(sw, "could not read uid from eeprom\n"); else tb_sw_info(sw, "uid: %#llx\n", sw->uid); @@ -442,7 +442,7 @@ int tb_switch_resume(struct tb_switch *sw) u64 uid; tb_sw_info(sw, "resuming switch\n"); - err = tb_eeprom_read_uid(sw, &uid); + err = tb_drom_read_uid_only(sw, &uid); if (err) { tb_sw_warn(sw, "uid read failed\n"); return err; diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 63e89d01047c..18ade5e33ae9 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -22,6 +22,7 @@ struct tb_switch { u64 uid; int cap_plug_events; /* offset, zero if not found */ bool is_unplugged; /* unplugged, will go away */ + u8 *drom; }; /** @@ -33,6 +34,9 @@ struct tb_port { 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 *dual_link_port; + u8 link_nr:1; }; /** @@ -237,7 +241,8 @@ int tb_path_activate(struct tb_path *path); void tb_path_deactivate(struct tb_path *path); bool tb_path_is_invalid(struct tb_path *path); -int tb_eeprom_read_uid(struct tb_switch *sw, u64 *uid); +int tb_drom_read(struct tb_switch *sw); +int tb_drom_read_uid_only(struct tb_switch *sw, u64 *uid); static inline int tb_route_length(u64 route) -- cgit v1.2.3 From 343fcb8c70d76967ba64493ca984e40baad9d0f6 Mon Sep 17 00:00:00 2001 From: Andreas Noever Date: Thu, 12 Jun 2014 23:11:47 +0200 Subject: thunderbolt: Fix nontrivial endpoint devices. Fix issues observed with the Startech docking station: Fix the type of the route parameter in tb_ctl_rx. It should be u64 and not u8 (which only worked for short routes). A thunderbolt cable contains two lanes. If both endpoints support it a connection will be established on both lanes. Previously we tried to scan below both "dual link ports". Use the information extracted from the drom to only scan behind ports with lane_nr == 0. Endpoints with more complex thunderbolt controllers have some of their ports disabled (for example the NHI port or one of the HDMI/DP ports). Accessing them results in an error so we now ignore ports which are marked as disabled in the drom. Signed-off-by: Andreas Noever Signed-off-by: Greg Kroah-Hartman --- drivers/thunderbolt/ctl.c | 2 +- drivers/thunderbolt/switch.c | 42 +++++++++++++++++++++++++----------------- drivers/thunderbolt/tb.c | 5 +++++ 3 files changed, 31 insertions(+), 18 deletions(-) (limited to 'drivers/thunderbolt/switch.c') diff --git a/drivers/thunderbolt/ctl.c b/drivers/thunderbolt/ctl.c index 9b0120bede51..d04fee4acb2e 100644 --- a/drivers/thunderbolt/ctl.c +++ b/drivers/thunderbolt/ctl.c @@ -439,7 +439,7 @@ rx: */ static struct tb_cfg_result tb_ctl_rx(struct tb_ctl *ctl, void *buffer, size_t length, int timeout_msec, - u8 route, enum tb_cfg_pkg_type type) + u64 route, enum tb_cfg_pkg_type type) { struct tb_cfg_result res; struct ctl_pkg *pkg; diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index 9dfb8e18cdf7..0d50e7e7b29b 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -180,20 +180,17 @@ int tb_port_clear_counter(struct tb_port *port, int counter) * * Return: Returns 0 on success or an error code on failure. */ -static int tb_init_port(struct tb_switch *sw, u8 port_nr) +static int tb_init_port(struct tb_port *port) { int res; int cap; - struct tb_port *port = &sw->ports[port_nr]; - port->sw = sw; - port->port = port_nr; - port->remote = NULL; + res = tb_port_read(port, &port->config, TB_CFG_PORT, 0, 8); if (res) return res; /* Port 0 is the switch itself and has no PHY. */ - if (port->config.type == TB_TYPE_PORT && port_nr != 0) { + if (port->config.type == TB_TYPE_PORT && port->port != 0) { cap = tb_find_cap(port, TB_CFG_PORT, TB_CAP_PHY); if (cap > 0) @@ -202,7 +199,7 @@ static int tb_init_port(struct tb_switch *sw, u8 port_nr) tb_port_WARN(port, "non switch port without a PHY\n"); } - tb_dump_port(sw->tb, &port->config); + tb_dump_port(port->sw->tb, &port->config); /* TODO: Read dual link port, DP port and more from EEPROM. */ return 0; @@ -329,6 +326,7 @@ void tb_switch_free(struct tb_switch *sw) tb_plug_events_active(sw, false); kfree(sw->ports); + kfree(sw->drom); kfree(sw); } @@ -381,18 +379,16 @@ struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route) /* initialize ports */ sw->ports = kcalloc(sw->config.max_port_number + 1, sizeof(*sw->ports), - GFP_KERNEL); + GFP_KERNEL); if (!sw->ports) goto err; for (i = 0; i <= sw->config.max_port_number; i++) { - if (tb_init_port(sw, i)) - goto err; - /* TODO: check if port is disabled (EEPROM) */ + /* minimum setup for tb_find_cap and tb_drom_read to work */ + sw->ports[i].sw = sw; + sw->ports[i].port = i; } - /* TODO: I2C, IECS, EEPROM, link controller */ - cap = tb_find_cap(&sw->ports[0], TB_CFG_SWITCH, TB_CAP_PLUG_EVENTS); if (cap < 0) { tb_sw_warn(sw, "cannot find TB_CAP_PLUG_EVENTS aborting\n"); @@ -400,10 +396,21 @@ struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route) } sw->cap_plug_events = cap; - if (tb_drom_read_uid_only(sw, &sw->uid)) - tb_sw_warn(sw, "could not read uid from eeprom\n"); - else - tb_sw_info(sw, "uid: %#llx\n", sw->uid); + /* read drom */ + if (tb_drom_read(sw)) + tb_sw_warn(sw, "tb_eeprom_read_rom failed, continuing\n"); + tb_sw_info(sw, "uid: %#llx\n", sw->uid); + + for (i = 0; i <= sw->config.max_port_number; i++) { + if (sw->ports[i].disabled) { + tb_port_info(&sw->ports[i], "disabled by eeprom\n"); + continue; + } + if (tb_init_port(&sw->ports[i])) + goto err; + } + + /* TODO: I2C, IECS, link controller */ if (tb_plug_events_active(sw, true)) goto err; @@ -411,6 +418,7 @@ struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route) return sw; err: kfree(sw->ports); + kfree(sw->drom); kfree(sw); return NULL; } diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index 1aa6dd7dc68b..d2c3fe346e91 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -38,6 +38,11 @@ static void tb_scan_port(struct tb_port *port) return; if (port->config.type != TB_TYPE_PORT) return; + if (port->dual_link_port && port->link_nr) + return; /* + * Downstream switch is reachable through two ports. + * Only scan on the primary port (link_nr == 0). + */ if (tb_wait_for_port(port, false) <= 0) return; if (port->remote) { -- cgit v1.2.3 From 10fefe56bba413fb0525207c65cf50cf2a5afaff Mon Sep 17 00:00:00 2001 From: Sachin Kamat Date: Fri, 20 Jun 2014 14:32:30 +0530 Subject: thunderbolt: Fix build error in switch.c MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes the below error: drivers/thunderbolt/switch.c:347:2: error: implicit declaration of function ‘kzalloc’ [-Werror=implicit-function-declaration] drivers/thunderbolt/switch.c:381:2: error: implicit declaration of function ‘kcalloc’ [-Werror=implicit-function-declaration] Signed-off-by: Sachin Kamat Acked-by: Andreas Noever Signed-off-by: Greg Kroah-Hartman --- drivers/thunderbolt/switch.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/thunderbolt/switch.c') diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index 0d50e7e7b29b..26e76e4aa835 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -5,6 +5,7 @@ */ #include +#include #include "tb.h" -- cgit v1.2.3 From c9c2deef457c766a33c4862c9c198c20854d5fb6 Mon Sep 17 00:00:00 2001 From: Sachin Kamat Date: Fri, 20 Jun 2014 14:32:31 +0530 Subject: thunderbolt: Use NULL instead of 0 in switch.c The function returns a pointer. Hence return NULL instead of 0. Signed-off-by: Sachin Kamat Acked-by: Andreas Noever Signed-off-by: Greg Kroah-Hartman --- drivers/thunderbolt/switch.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers/thunderbolt/switch.c') diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index 26e76e4aa835..aeb982969629 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -260,11 +260,11 @@ struct tb_switch *get_switch_at_route(struct tb_switch *sw, u64 route) if (route == 0) return sw; if (next_port > sw->config.max_port_number) - return 0; + return NULL; if (tb_is_upstream_port(&sw->ports[next_port])) - return 0; + return NULL; if (!sw->ports[next_port].remote) - return 0; + return NULL; return get_switch_at_route(sw->ports[next_port].remote->sw, route >> TB_ROUTE_SHIFT); } -- cgit v1.2.3