diff options
author | Andreas Noever <andreas.noever@gmail.com> | 2014-06-03 16:04:06 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2014-06-19 17:07:47 -0400 |
commit | 053596d9e26c86352c4b2b372f43f2746b97de45 (patch) | |
tree | 7568b6b0dc29e9030bf1d6ffd38f522d6907e980 /drivers/thunderbolt | |
parent | 9da672a42878c58af5c50d7389dbae17bea9df38 (diff) |
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 <andreas.noever@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/thunderbolt')
-rw-r--r-- | drivers/thunderbolt/switch.c | 42 | ||||
-rw-r--r-- | drivers/thunderbolt/tb.c | 46 | ||||
-rw-r--r-- | drivers/thunderbolt/tb.h | 3 |
3 files changed, 89 insertions, 2 deletions
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) | |||
195 | sw->__unknown1, sw->__unknown4); | 195 | sw->__unknown1, sw->__unknown4); |
196 | } | 196 | } |
197 | 197 | ||
198 | struct tb_switch *get_switch_at_route(struct tb_switch *sw, u64 route) | ||
199 | { | ||
200 | u8 next_port = route; /* | ||
201 | * Routes use a stride of 8 bits, | ||
202 | * eventhough a port index has 6 bits at most. | ||
203 | * */ | ||
204 | if (route == 0) | ||
205 | return sw; | ||
206 | if (next_port > sw->config.max_port_number) | ||
207 | return 0; | ||
208 | if (tb_is_upstream_port(&sw->ports[next_port])) | ||
209 | return 0; | ||
210 | if (!sw->ports[next_port].remote) | ||
211 | return 0; | ||
212 | return get_switch_at_route(sw->ports[next_port].remote->sw, | ||
213 | route >> TB_ROUTE_SHIFT); | ||
214 | } | ||
215 | |||
198 | /** | 216 | /** |
199 | * tb_plug_events_active() - enable/disable plug events on a switch | 217 | * tb_plug_events_active() - enable/disable plug events on a switch |
200 | * | 218 | * |
@@ -249,7 +267,8 @@ void tb_switch_free(struct tb_switch *sw) | |||
249 | sw->ports[i].remote = NULL; | 267 | sw->ports[i].remote = NULL; |
250 | } | 268 | } |
251 | 269 | ||
252 | tb_plug_events_active(sw, false); | 270 | if (!sw->is_unplugged) |
271 | tb_plug_events_active(sw, false); | ||
253 | 272 | ||
254 | kfree(sw->ports); | 273 | kfree(sw->ports); |
255 | kfree(sw); | 274 | kfree(sw); |
@@ -333,3 +352,24 @@ err: | |||
333 | return NULL; | 352 | return NULL; |
334 | } | 353 | } |
335 | 354 | ||
355 | /** | ||
356 | * tb_sw_set_unpplugged() - set is_unplugged on switch and downstream switches | ||
357 | */ | ||
358 | void tb_sw_set_unpplugged(struct tb_switch *sw) | ||
359 | { | ||
360 | int i; | ||
361 | if (sw == sw->tb->root_switch) { | ||
362 | tb_sw_WARN(sw, "cannot unplug root switch\n"); | ||
363 | return; | ||
364 | } | ||
365 | if (sw->is_unplugged) { | ||
366 | tb_sw_WARN(sw, "is_unplugged already set\n"); | ||
367 | return; | ||
368 | } | ||
369 | sw->is_unplugged = true; | ||
370 | for (i = 0; i <= sw->config.max_port_number; i++) { | ||
371 | if (!tb_is_upstream_port(&sw->ports[i]) && sw->ports[i].remote) | ||
372 | tb_sw_set_unpplugged(sw->ports[i].remote->sw); | ||
373 | } | ||
374 | } | ||
375 | |||
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) | |||
71 | { | 71 | { |
72 | struct tb_hotplug_event *ev = container_of(work, typeof(*ev), work); | 72 | struct tb_hotplug_event *ev = container_of(work, typeof(*ev), work); |
73 | struct tb *tb = ev->tb; | 73 | struct tb *tb = ev->tb; |
74 | struct tb_switch *sw; | ||
75 | struct tb_port *port; | ||
74 | mutex_lock(&tb->lock); | 76 | mutex_lock(&tb->lock); |
75 | if (!tb->hotplug_active) | 77 | if (!tb->hotplug_active) |
76 | goto out; /* during init, suspend or shutdown */ | 78 | goto out; /* during init, suspend or shutdown */ |
77 | 79 | ||
78 | /* do nothing for now */ | 80 | sw = get_switch_at_route(tb->root_switch, ev->route); |
81 | if (!sw) { | ||
82 | tb_warn(tb, | ||
83 | "hotplug event from non existent switch %llx:%x (unplug: %d)\n", | ||
84 | ev->route, ev->port, ev->unplug); | ||
85 | goto out; | ||
86 | } | ||
87 | if (ev->port > sw->config.max_port_number) { | ||
88 | tb_warn(tb, | ||
89 | "hotplug event from non existent port %llx:%x (unplug: %d)\n", | ||
90 | ev->route, ev->port, ev->unplug); | ||
91 | goto out; | ||
92 | } | ||
93 | port = &sw->ports[ev->port]; | ||
94 | if (tb_is_upstream_port(port)) { | ||
95 | tb_warn(tb, | ||
96 | "hotplug event for upstream port %llx:%x (unplug: %d)\n", | ||
97 | ev->route, ev->port, ev->unplug); | ||
98 | goto out; | ||
99 | } | ||
100 | if (ev->unplug) { | ||
101 | if (port->remote) { | ||
102 | tb_port_info(port, "unplugged\n"); | ||
103 | tb_sw_set_unpplugged(port->remote->sw); | ||
104 | tb_switch_free(port->remote->sw); | ||
105 | port->remote = NULL; | ||
106 | } else { | ||
107 | tb_port_info(port, | ||
108 | "got unplug event for disconnected port, ignoring\n"); | ||
109 | } | ||
110 | } else if (port->remote) { | ||
111 | tb_port_info(port, | ||
112 | "got plug event for connected port, ignoring\n"); | ||
113 | } else { | ||
114 | tb_port_info(port, "hotplug: scanning\n"); | ||
115 | tb_scan_port(port); | ||
116 | if (!port->remote) { | ||
117 | tb_port_info(port, "hotplug: no switch found\n"); | ||
118 | } else if (port->remote->sw->config.depth > 1) { | ||
119 | tb_sw_warn(port->remote->sw, | ||
120 | "hotplug: chaining not supported\n"); | ||
121 | } | ||
122 | } | ||
79 | out: | 123 | out: |
80 | mutex_unlock(&tb->lock); | 124 | mutex_unlock(&tb->lock); |
81 | kfree(ev); | 125 | 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 { | |||
20 | struct tb_port *ports; | 20 | struct tb_port *ports; |
21 | struct tb *tb; | 21 | struct tb *tb; |
22 | int cap_plug_events; /* offset, zero if not found */ | 22 | int cap_plug_events; /* offset, zero if not found */ |
23 | bool is_unplugged; /* unplugged, will go away */ | ||
23 | }; | 24 | }; |
24 | 25 | ||
25 | /** | 26 | /** |
@@ -160,6 +161,8 @@ void thunderbolt_shutdown_and_free(struct tb *tb); | |||
160 | 161 | ||
161 | struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route); | 162 | struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route); |
162 | void tb_switch_free(struct tb_switch *sw); | 163 | void tb_switch_free(struct tb_switch *sw); |
164 | void tb_sw_set_unpplugged(struct tb_switch *sw); | ||
165 | struct tb_switch *get_switch_at_route(struct tb_switch *sw, u64 route); | ||
163 | 166 | ||
164 | int tb_wait_for_port(struct tb_port *port, bool wait_if_unplugged); | 167 | int tb_wait_for_port(struct tb_port *port, bool wait_if_unplugged); |
165 | 168 | ||