aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/thunderbolt
diff options
context:
space:
mode:
authorAndreas Noever <andreas.noever@gmail.com>2014-06-12 17:11:46 -0400
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2014-06-19 17:14:35 -0400
commitcd22e73bdf5eff7e68a0f8bdfbce123ad43651f6 (patch)
tree0455e24a7e283483f52c5adb4d4e1056bb6c745c /drivers/thunderbolt
parent23dd5bb49d986f37977ed80dd2ca65040ead4392 (diff)
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 <andreas.noever@gmail.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/thunderbolt')
-rw-r--r--drivers/thunderbolt/eeprom.c266
-rw-r--r--drivers/thunderbolt/switch.c4
-rw-r--r--drivers/thunderbolt/tb.h7
3 files changed, 270 insertions, 7 deletions
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 @@
4 * Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com> 4 * Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com>
5 */ 5 */
6 6
7#include <linux/crc32.h>
7#include "tb.h" 8#include "tb.h"
8 9
9/** 10/**
@@ -152,9 +153,86 @@ static int tb_eeprom_read_n(struct tb_switch *sw, u16 offset, u8 *val,
152 return tb_eeprom_active(sw, false); 153 return tb_eeprom_active(sw, false);
153} 154}
154 155
155int tb_eeprom_read_uid(struct tb_switch *sw, u64 *uid) 156static u8 tb_crc8(u8 *data, int len)
157{
158 int i, j;
159 u8 val = 0xff;
160 for (i = 0; i < len; i++) {
161 val ^= data[i];
162 for (j = 0; j < 8; j++)
163 val = (val << 1) ^ ((val & 0x80) ? 7 : 0);
164 }
165 return val;
166}
167
168static u32 tb_crc32(void *data, size_t len)
169{
170 return ~__crc32c_le(~0, data, len);
171}
172
173#define TB_DROM_DATA_START 13
174struct tb_drom_header {
175 /* BYTE 0 */
176 u8 uid_crc8; /* checksum for uid */
177 /* BYTES 1-8 */
178 u64 uid;
179 /* BYTES 9-12 */
180 u32 data_crc32; /* checksum for data_len bytes starting at byte 13 */
181 /* BYTE 13 */
182 u8 device_rom_revision; /* should be <= 1 */
183 u16 data_len:10;
184 u8 __unknown1:6;
185 /* BYTES 16-21 */
186 u16 vendor_id;
187 u16 model_id;
188 u8 model_rev;
189 u8 eeprom_rev;
190} __packed;
191
192enum tb_drom_entry_type {
193 TB_DROM_ENTRY_GENERIC,
194 TB_DROM_ENTRY_PORT,
195};
196
197struct tb_drom_entry_header {
198 u8 len;
199 u8 index:6;
200 bool port_disabled:1; /* only valid if type is TB_DROM_ENTRY_PORT */
201 enum tb_drom_entry_type type:1;
202} __packed;
203
204struct tb_drom_entry_port {
205 /* BYTES 0-1 */
206 struct tb_drom_entry_header header;
207 /* BYTE 2 */
208 u8 dual_link_port_rid:4;
209 u8 link_nr:1;
210 u8 unknown1:2;
211 bool has_dual_link_port:1;
212
213 /* BYTE 3 */
214 u8 dual_link_port_nr:6;
215 u8 unknown2:2;
216
217 /* BYTES 4 - 5 TODO decode */
218 u8 micro2:4;
219 u8 micro1:4;
220 u8 micro3;
221
222 /* BYTES 5-6, TODO: verify (find hardware that has these set) */
223 u8 peer_port_rid:4;
224 u8 unknown3:3;
225 bool has_peer_port:1;
226 u8 peer_port_nr:6;
227 u8 unknown4:2;
228} __packed;
229
230
231/**
232 * tb_eeprom_get_drom_offset - get drom offset within eeprom
233 */
234int tb_eeprom_get_drom_offset(struct tb_switch *sw, u16 *offset)
156{ 235{
157 u8 data[9];
158 struct tb_cap_plug_events cap; 236 struct tb_cap_plug_events cap;
159 int res; 237 int res;
160 if (!sw->cap_plug_events) { 238 if (!sw->cap_plug_events) {
@@ -165,6 +243,7 @@ int tb_eeprom_read_uid(struct tb_switch *sw, u64 *uid)
165 sizeof(cap) / 4); 243 sizeof(cap) / 4);
166 if (res) 244 if (res)
167 return res; 245 return res;
246
168 if (!cap.eeprom_ctl.present || cap.eeprom_ctl.not_present) { 247 if (!cap.eeprom_ctl.present || cap.eeprom_ctl.not_present) {
169 tb_sw_warn(sw, "no NVM\n"); 248 tb_sw_warn(sw, "no NVM\n");
170 return -ENOSYS; 249 return -ENOSYS;
@@ -175,15 +254,194 @@ int tb_eeprom_read_uid(struct tb_switch *sw, u64 *uid)
175 cap.drom_offset); 254 cap.drom_offset);
176 return -ENXIO; 255 return -ENXIO;
177 } 256 }
257 *offset = cap.drom_offset;
258 return 0;
259}
260
261/**
262 * tb_drom_read_uid_only - read uid directly from drom
263 *
264 * Does not use the cached copy in sw->drom. Used during resume to check switch
265 * identity.
266 */
267int tb_drom_read_uid_only(struct tb_switch *sw, u64 *uid)
268{
269 u8 data[9];
270 u16 drom_offset;
271 u8 crc;
272 int res = tb_eeprom_get_drom_offset(sw, &drom_offset);
273 if (res)
274 return res;
178 275
179 /* read uid */ 276 /* read uid */
180 res = tb_eeprom_read_n(sw, cap.drom_offset, data, 9); 277 res = tb_eeprom_read_n(sw, drom_offset, data, 9);
181 if (res) 278 if (res)
182 return res; 279 return res;
183 /* TODO: check checksum in data[0] */ 280
281 crc = tb_crc8(data + 1, 8);
282 if (crc != data[0]) {
283 tb_sw_warn(sw, "uid crc8 missmatch (expected: %#x, got: %#x)\n",
284 data[0], crc);
285 return -EIO;
286 }
287
184 *uid = *(u64 *)(data+1); 288 *uid = *(u64 *)(data+1);
185 return 0; 289 return 0;
186} 290}
187 291
292static void tb_drom_parse_port_entry(struct tb_port *port,
293 struct tb_drom_entry_port *entry)
294{
295 port->link_nr = entry->link_nr;
296 if (entry->has_dual_link_port)
297 port->dual_link_port =
298 &port->sw->ports[entry->dual_link_port_nr];
299}
300
301static int tb_drom_parse_entry(struct tb_switch *sw,
302 struct tb_drom_entry_header *header)
303{
304 struct tb_port *port;
305 int res;
306 enum tb_port_type type;
188 307
308 if (header->type != TB_DROM_ENTRY_PORT)
309 return 0;
189 310
311 port = &sw->ports[header->index];
312 port->disabled = header->port_disabled;
313 if (port->disabled)
314 return 0;
315
316 res = tb_port_read(port, &type, TB_CFG_PORT, 2, 1);
317 if (res)
318 return res;
319 type &= 0xffffff;
320
321 if (type == TB_TYPE_PORT) {
322 struct tb_drom_entry_port *entry = (void *) header;
323 if (header->len != sizeof(*entry)) {
324 tb_sw_warn(sw,
325 "port entry has size %#x (expected %#lx)\n",
326 header->len, sizeof(struct tb_drom_entry_port));
327 return -EIO;
328 }
329 tb_drom_parse_port_entry(port, entry);
330 }
331 return 0;
332}
333
334/**
335 * tb_drom_parse_entries - parse the linked list of drom entries
336 *
337 * Drom must have been copied to sw->drom.
338 */
339static int tb_drom_parse_entries(struct tb_switch *sw)
340{
341 struct tb_drom_header *header = (void *) sw->drom;
342 u16 pos = sizeof(*header);
343 u16 drom_size = header->data_len + TB_DROM_DATA_START;
344
345 while (pos < drom_size) {
346 struct tb_drom_entry_header *entry = (void *) (sw->drom + pos);
347 if (pos + 1 == drom_size || pos + entry->len > drom_size
348 || !entry->len) {
349 tb_sw_warn(sw, "drom buffer overrun, aborting\n");
350 return -EIO;
351 }
352
353 tb_drom_parse_entry(sw, entry);
354
355 pos += entry->len;
356 }
357 return 0;
358}
359
360/**
361 * tb_drom_read - copy drom to sw->drom and parse it
362 */
363int tb_drom_read(struct tb_switch *sw)
364{
365 u16 drom_offset;
366 u16 size;
367 u32 crc;
368 struct tb_drom_header *header;
369 int res;
370 if (sw->drom)
371 return 0;
372
373 if (tb_route(sw) == 0) {
374 /*
375 * The root switch contains only a dummy drom (header only,
376 * no entries). Hardcode the configuration here.
377 */
378 tb_drom_read_uid_only(sw, &sw->uid);
379
380 sw->ports[1].link_nr = 0;
381 sw->ports[2].link_nr = 1;
382 sw->ports[1].dual_link_port = &sw->ports[2];
383 sw->ports[2].dual_link_port = &sw->ports[1];
384
385 sw->ports[3].link_nr = 0;
386 sw->ports[4].link_nr = 1;
387 sw->ports[3].dual_link_port = &sw->ports[4];
388 sw->ports[4].dual_link_port = &sw->ports[3];
389 return 0;
390 }
391
392 res = tb_eeprom_get_drom_offset(sw, &drom_offset);
393 if (res)
394 return res;
395
396 res = tb_eeprom_read_n(sw, drom_offset + 14, (u8 *) &size, 2);
397 if (res)
398 return res;
399 size &= 0x3ff;
400 size += TB_DROM_DATA_START;
401 tb_sw_info(sw, "reading drom (length: %#x)\n", size);
402 if (size < sizeof(*header)) {
403 tb_sw_warn(sw, "drom too small, aborting\n");
404 return -EIO;
405 }
406
407 sw->drom = kzalloc(size, GFP_KERNEL);
408 if (!sw->drom)
409 return -ENOMEM;
410 res = tb_eeprom_read_n(sw, drom_offset, sw->drom, size);
411 if (res)
412 goto err;
413
414 header = (void *) sw->drom;
415
416 if (header->data_len + TB_DROM_DATA_START != size) {
417 tb_sw_warn(sw, "drom size mismatch, aborting\n");
418 goto err;
419 }
420
421 crc = tb_crc8((u8 *) &header->uid, 8);
422 if (crc != header->uid_crc8) {
423 tb_sw_warn(sw,
424 "drom uid crc8 mismatch (expected: %#x, got: %#x), aborting\n",
425 header->uid_crc8, crc);
426 goto err;
427 }
428 sw->uid = header->uid;
429
430 crc = tb_crc32(sw->drom + TB_DROM_DATA_START, header->data_len);
431 if (crc != header->data_crc32) {
432 tb_sw_warn(sw,
433 "drom data crc32 mismatch (expected: %#x, got: %#x), aborting\n",
434 header->data_crc32, crc);
435 goto err;
436 }
437
438 if (header->device_rom_revision > 1)
439 tb_sw_warn(sw, "drom device_rom_revision %#x unknown\n",
440 header->device_rom_revision);
441
442 return tb_drom_parse_entries(sw);
443err:
444 kfree(sw->drom);
445 return -EIO;
446
447}
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)
400 } 400 }
401 sw->cap_plug_events = cap; 401 sw->cap_plug_events = cap;
402 402
403 if (tb_eeprom_read_uid(sw, &sw->uid)) 403 if (tb_drom_read_uid_only(sw, &sw->uid))
404 tb_sw_warn(sw, "could not read uid from eeprom\n"); 404 tb_sw_warn(sw, "could not read uid from eeprom\n");
405 else 405 else
406 tb_sw_info(sw, "uid: %#llx\n", sw->uid); 406 tb_sw_info(sw, "uid: %#llx\n", sw->uid);
@@ -442,7 +442,7 @@ int tb_switch_resume(struct tb_switch *sw)
442 u64 uid; 442 u64 uid;
443 tb_sw_info(sw, "resuming switch\n"); 443 tb_sw_info(sw, "resuming switch\n");
444 444
445 err = tb_eeprom_read_uid(sw, &uid); 445 err = tb_drom_read_uid_only(sw, &uid);
446 if (err) { 446 if (err) {
447 tb_sw_warn(sw, "uid read failed\n"); 447 tb_sw_warn(sw, "uid read failed\n");
448 return err; 448 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 {
22 u64 uid; 22 u64 uid;
23 int cap_plug_events; /* offset, zero if not found */ 23 int cap_plug_events; /* offset, zero if not found */
24 bool is_unplugged; /* unplugged, will go away */ 24 bool is_unplugged; /* unplugged, will go away */
25 u8 *drom;
25}; 26};
26 27
27/** 28/**
@@ -33,6 +34,9 @@ struct tb_port {
33 struct tb_port *remote; /* remote port, NULL if not connected */ 34 struct tb_port *remote; /* remote port, NULL if not connected */
34 int cap_phy; /* offset, zero if not found */ 35 int cap_phy; /* offset, zero if not found */
35 u8 port; /* port number on switch */ 36 u8 port; /* port number on switch */
37 bool disabled; /* disabled by eeprom */
38 struct tb_port *dual_link_port;
39 u8 link_nr:1;
36}; 40};
37 41
38/** 42/**
@@ -237,7 +241,8 @@ int tb_path_activate(struct tb_path *path);
237void tb_path_deactivate(struct tb_path *path); 241void tb_path_deactivate(struct tb_path *path);
238bool tb_path_is_invalid(struct tb_path *path); 242bool tb_path_is_invalid(struct tb_path *path);
239 243
240int tb_eeprom_read_uid(struct tb_switch *sw, u64 *uid); 244int tb_drom_read(struct tb_switch *sw);
245int tb_drom_read_uid_only(struct tb_switch *sw, u64 *uid);
241 246
242 247
243static inline int tb_route_length(u64 route) 248static inline int tb_route_length(u64 route)