diff options
author | Andreas Noever <andreas.noever@gmail.com> | 2014-06-03 16:04:05 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2014-06-19 17:07:47 -0400 |
commit | 9da672a42878c58af5c50d7389dbae17bea9df38 (patch) | |
tree | d85b6d930f13d03b6ec0c718da7b834dd5221e4a | |
parent | ca389f716f6140d5349583a716bc629d63b06b1f (diff) |
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 <andreas.noever@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r-- | drivers/thunderbolt/switch.c | 97 | ||||
-rw-r--r-- | drivers/thunderbolt/tb.c | 44 | ||||
-rw-r--r-- | drivers/thunderbolt/tb.h | 16 |
3 files changed, 157 insertions, 0 deletions
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 | |||
@@ -53,6 +53,92 @@ static void tb_dump_port(struct tb *tb, struct tb_regs_port_header *port) | |||
53 | } | 53 | } |
54 | 54 | ||
55 | /** | 55 | /** |
56 | * tb_port_state() - get connectedness state of a port | ||
57 | * | ||
58 | * The port must have a TB_CAP_PHY (i.e. it should be a real port). | ||
59 | * | ||
60 | * Return: Returns an enum tb_port_state on success or an error code on failure. | ||
61 | */ | ||
62 | static int tb_port_state(struct tb_port *port) | ||
63 | { | ||
64 | struct tb_cap_phy phy; | ||
65 | int res; | ||
66 | if (port->cap_phy == 0) { | ||
67 | tb_port_WARN(port, "does not have a PHY\n"); | ||
68 | return -EINVAL; | ||
69 | } | ||
70 | res = tb_port_read(port, &phy, TB_CFG_PORT, port->cap_phy, 2); | ||
71 | if (res) | ||
72 | return res; | ||
73 | return phy.state; | ||
74 | } | ||
75 | |||
76 | /** | ||
77 | * tb_wait_for_port() - wait for a port to become ready | ||
78 | * | ||
79 | * Wait up to 1 second for a port to reach state TB_PORT_UP. If | ||
80 | * wait_if_unplugged is set then we also wait if the port is in state | ||
81 | * TB_PORT_UNPLUGGED (it takes a while for the device to be registered after | ||
82 | * switch resume). Otherwise we only wait if a device is registered but the link | ||
83 | * has not yet been established. | ||
84 | * | ||
85 | * Return: Returns an error code on failure. Returns 0 if the port is not | ||
86 | * connected or failed to reach state TB_PORT_UP within one second. Returns 1 | ||
87 | * if the port is connected and in state TB_PORT_UP. | ||
88 | */ | ||
89 | int tb_wait_for_port(struct tb_port *port, bool wait_if_unplugged) | ||
90 | { | ||
91 | int retries = 10; | ||
92 | int state; | ||
93 | if (!port->cap_phy) { | ||
94 | tb_port_WARN(port, "does not have PHY\n"); | ||
95 | return -EINVAL; | ||
96 | } | ||
97 | if (tb_is_upstream_port(port)) { | ||
98 | tb_port_WARN(port, "is the upstream port\n"); | ||
99 | return -EINVAL; | ||
100 | } | ||
101 | |||
102 | while (retries--) { | ||
103 | state = tb_port_state(port); | ||
104 | if (state < 0) | ||
105 | return state; | ||
106 | if (state == TB_PORT_DISABLED) { | ||
107 | tb_port_info(port, "is disabled (state: 0)\n"); | ||
108 | return 0; | ||
109 | } | ||
110 | if (state == TB_PORT_UNPLUGGED) { | ||
111 | if (wait_if_unplugged) { | ||
112 | /* used during resume */ | ||
113 | tb_port_info(port, | ||
114 | "is unplugged (state: 7), retrying...\n"); | ||
115 | msleep(100); | ||
116 | continue; | ||
117 | } | ||
118 | tb_port_info(port, "is unplugged (state: 7)\n"); | ||
119 | return 0; | ||
120 | } | ||
121 | if (state == TB_PORT_UP) { | ||
122 | tb_port_info(port, | ||
123 | "is connected, link is up (state: 2)\n"); | ||
124 | return 1; | ||
125 | } | ||
126 | |||
127 | /* | ||
128 | * After plug-in the state is TB_PORT_CONNECTING. Give it some | ||
129 | * time. | ||
130 | */ | ||
131 | tb_port_info(port, | ||
132 | "is connected, link is not up (state: %d), retrying...\n", | ||
133 | state); | ||
134 | msleep(100); | ||
135 | } | ||
136 | tb_port_warn(port, | ||
137 | "failed to reach state TB_PORT_UP. Ignoring port...\n"); | ||
138 | return 0; | ||
139 | } | ||
140 | |||
141 | /** | ||
56 | * tb_init_port() - initialize a port | 142 | * tb_init_port() - initialize a port |
57 | * | 143 | * |
58 | * This is a helper method for tb_switch_alloc. Does not check or initialize | 144 | * This is a helper method for tb_switch_alloc. Does not check or initialize |
@@ -63,6 +149,7 @@ static void tb_dump_port(struct tb *tb, struct tb_regs_port_header *port) | |||
63 | static int tb_init_port(struct tb_switch *sw, u8 port_nr) | 149 | static int tb_init_port(struct tb_switch *sw, u8 port_nr) |
64 | { | 150 | { |
65 | int res; | 151 | int res; |
152 | int cap; | ||
66 | struct tb_port *port = &sw->ports[port_nr]; | 153 | struct tb_port *port = &sw->ports[port_nr]; |
67 | port->sw = sw; | 154 | port->sw = sw; |
68 | port->port = port_nr; | 155 | port->port = port_nr; |
@@ -71,6 +158,16 @@ static int tb_init_port(struct tb_switch *sw, u8 port_nr) | |||
71 | if (res) | 158 | if (res) |
72 | return res; | 159 | return res; |
73 | 160 | ||
161 | /* Port 0 is the switch itself and has no PHY. */ | ||
162 | if (port->config.type == TB_TYPE_PORT && port_nr != 0) { | ||
163 | cap = tb_find_cap(port, TB_CFG_PORT, TB_CAP_PHY); | ||
164 | |||
165 | if (cap > 0) | ||
166 | port->cap_phy = cap; | ||
167 | else | ||
168 | tb_port_WARN(port, "non switch port without a PHY\n"); | ||
169 | } | ||
170 | |||
74 | tb_dump_port(sw->tb, &port->config); | 171 | tb_dump_port(sw->tb, &port->config); |
75 | 172 | ||
76 | /* TODO: Read dual link port, DP port and more from EEPROM. */ | 173 | /* 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 @@ | |||
11 | #include "tb.h" | 11 | #include "tb.h" |
12 | #include "tb_regs.h" | 12 | #include "tb_regs.h" |
13 | 13 | ||
14 | |||
15 | /* enumeration & hot plug handling */ | ||
16 | |||
17 | |||
18 | static void tb_scan_port(struct tb_port *port); | ||
19 | |||
20 | /** | ||
21 | * tb_scan_switch() - scan for and initialize downstream switches | ||
22 | */ | ||
23 | static void tb_scan_switch(struct tb_switch *sw) | ||
24 | { | ||
25 | int i; | ||
26 | for (i = 1; i <= sw->config.max_port_number; i++) | ||
27 | tb_scan_port(&sw->ports[i]); | ||
28 | } | ||
29 | |||
30 | /** | ||
31 | * tb_scan_port() - check for and initialize switches below port | ||
32 | */ | ||
33 | static void tb_scan_port(struct tb_port *port) | ||
34 | { | ||
35 | struct tb_switch *sw; | ||
36 | if (tb_is_upstream_port(port)) | ||
37 | return; | ||
38 | if (port->config.type != TB_TYPE_PORT) | ||
39 | return; | ||
40 | if (tb_wait_for_port(port, false) <= 0) | ||
41 | return; | ||
42 | if (port->remote) { | ||
43 | tb_port_WARN(port, "port already has a remote!\n"); | ||
44 | return; | ||
45 | } | ||
46 | sw = tb_switch_alloc(port->sw->tb, tb_downstream_route(port)); | ||
47 | if (!sw) | ||
48 | return; | ||
49 | port->remote = tb_upstream_port(sw); | ||
50 | tb_upstream_port(sw)->remote = port; | ||
51 | tb_scan_switch(sw); | ||
52 | } | ||
53 | |||
54 | |||
14 | /* hotplug handling */ | 55 | /* hotplug handling */ |
15 | 56 | ||
16 | struct tb_hotplug_event { | 57 | struct tb_hotplug_event { |
@@ -134,6 +175,9 @@ struct tb *thunderbolt_alloc_and_start(struct tb_nhi *nhi) | |||
134 | if (!tb->root_switch) | 175 | if (!tb->root_switch) |
135 | goto err_locked; | 176 | goto err_locked; |
136 | 177 | ||
178 | /* Full scan to discover devices added before the driver was loaded. */ | ||
179 | tb_scan_switch(tb->root_switch); | ||
180 | |||
137 | /* Allow tb_handle_hotplug to progress events */ | 181 | /* Allow tb_handle_hotplug to progress events */ |
138 | tb->hotplug_active = true; | 182 | tb->hotplug_active = true; |
139 | mutex_unlock(&tb->lock); | 183 | 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 { | |||
29 | struct tb_regs_port_header config; | 29 | struct tb_regs_port_header config; |
30 | struct tb_switch *sw; | 30 | struct tb_switch *sw; |
31 | struct tb_port *remote; /* remote port, NULL if not connected */ | 31 | struct tb_port *remote; /* remote port, NULL if not connected */ |
32 | int cap_phy; /* offset, zero if not found */ | ||
32 | u8 port; /* port number on switch */ | 33 | u8 port; /* port number on switch */ |
33 | }; | 34 | }; |
34 | 35 | ||
@@ -160,6 +161,8 @@ void thunderbolt_shutdown_and_free(struct tb *tb); | |||
160 | struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route); | 161 | struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route); |
161 | void tb_switch_free(struct tb_switch *sw); | 162 | void tb_switch_free(struct tb_switch *sw); |
162 | 163 | ||
164 | int tb_wait_for_port(struct tb_port *port, bool wait_if_unplugged); | ||
165 | |||
163 | int tb_find_cap(struct tb_port *port, enum tb_cfg_space space, u32 value); | 166 | int tb_find_cap(struct tb_port *port, enum tb_cfg_space space, u32 value); |
164 | 167 | ||
165 | 168 | ||
@@ -173,4 +176,17 @@ static inline bool tb_is_upstream_port(struct tb_port *port) | |||
173 | return port == tb_upstream_port(port->sw); | 176 | return port == tb_upstream_port(port->sw); |
174 | } | 177 | } |
175 | 178 | ||
179 | /** | ||
180 | * tb_downstream_route() - get route to downstream switch | ||
181 | * | ||
182 | * Port must not be the upstream port (otherwise a loop is created). | ||
183 | * | ||
184 | * Return: Returns a route to the switch behind @port. | ||
185 | */ | ||
186 | static inline u64 tb_downstream_route(struct tb_port *port) | ||
187 | { | ||
188 | return tb_route(port->sw) | ||
189 | | ((u64) port->port << (port->sw->config.depth * 8)); | ||
190 | } | ||
191 | |||
176 | #endif | 192 | #endif |