aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/thunderbolt/eeprom.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/thunderbolt/eeprom.c')
-rw-r--r--drivers/thunderbolt/eeprom.c200
1 files changed, 126 insertions, 74 deletions
diff --git a/drivers/thunderbolt/eeprom.c b/drivers/thunderbolt/eeprom.c
index ee5196479854..dd03d3096653 100644
--- a/drivers/thunderbolt/eeprom.c
+++ b/drivers/thunderbolt/eeprom.c
@@ -7,11 +7,12 @@
*/
#include <linux/crc32.h>
+#include <linux/delay.h>
#include <linux/property.h>
#include <linux/slab.h>
#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)
@@ -19,7 +20,7 @@ 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)
@@ -32,7 +33,7 @@ enum tb_eeprom_transfer {
TB_EEPROM_OUT,
};
-/**
+/*
* tb_eeprom_active - enable rom access
*
* WARNING: Always disable access after usage. Otherwise the controller will
@@ -61,7 +62,7 @@ static int tb_eeprom_active(struct tb_switch *sw, bool enable)
}
}
-/**
+/*
* tb_eeprom_transfer - transfer one bit
*
* If TB_EEPROM_IN is passed, then the bit can be retrieved from ctl->data_in.
@@ -89,7 +90,7 @@ static int tb_eeprom_transfer(struct tb_switch *sw, struct tb_eeprom_ctl *ctl,
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)
@@ -109,7 +110,7 @@ static int tb_eeprom_out(struct tb_switch *sw, u8 val)
return 0;
}
-/**
+/*
* tb_eeprom_in - read one byte from the bus
*/
static int tb_eeprom_in(struct tb_switch *sw, u8 *val)
@@ -130,13 +131,52 @@ static int tb_eeprom_in(struct tb_switch *sw, u8 *val)
return 0;
}
-/**
+/*
+ * tb_eeprom_get_drom_offset - get drom offset within eeprom
+ */
+static int tb_eeprom_get_drom_offset(struct tb_switch *sw, u16 *offset)
+{
+ 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 -ENODEV;
+ }
+ 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 -ENODEV;
+ }
+
+ if (cap.drom_offset > 0xffff) {
+ tb_sw_warn(sw, "drom offset is larger than 0xffff: %#x\n",
+ cap.drom_offset);
+ return -ENXIO;
+ }
+ *offset = cap.drom_offset;
+ 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)
{
+ u16 drom_offset;
int i, res;
+
+ res = tb_eeprom_get_drom_offset(sw, &drom_offset);
+ if (res)
+ return res;
+
+ offset += drom_offset;
+
res = tb_eeprom_active(sw, true);
if (res)
return res;
@@ -208,7 +248,7 @@ struct tb_drom_entry_header {
struct tb_drom_entry_generic {
struct tb_drom_entry_header header;
- u8 data[0];
+ u8 data[];
} __packed;
struct tb_drom_entry_port {
@@ -239,37 +279,9 @@ struct tb_drom_entry_port {
/**
- * tb_eeprom_get_drom_offset - get drom offset within eeprom
- */
-static int tb_eeprom_get_drom_offset(struct tb_switch *sw, u16 *offset)
-{
- 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;
- }
- *offset = cap.drom_offset;
- return 0;
-}
-
-/**
- * tb_drom_read_uid_only - read uid directly from drom
+ * tb_drom_read_uid_only() - Read UID directly from DROM
+ * @sw: Router whose UID to read
+ * @uid: UID is placed here
*
* Does not use the cached copy in sw->drom. Used during resume to check switch
* identity.
@@ -277,17 +289,11 @@ static int tb_eeprom_get_drom_offset(struct tb_switch *sw, u16 *offset)
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;
-
- if (drom_offset == 0)
- return -ENODEV;
+ int res;
/* read uid */
- res = tb_eeprom_read_n(sw, drom_offset, data, 9);
+ res = tb_eeprom_read_n(sw, 0, data, 9);
if (res)
return res;
@@ -370,7 +376,7 @@ static int tb_drom_parse_entry_port(struct tb_switch *sw,
return 0;
}
-/**
+/*
* tb_drom_parse_entries - parse the linked list of drom entries
*
* Drom must have been copied to sw->drom.
@@ -386,8 +392,8 @@ static int tb_drom_parse_entries(struct tb_switch *sw)
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_sw_warn(sw, "DROM buffer overrun\n");
+ return -EILSEQ;
}
switch (entry->type) {
@@ -406,7 +412,7 @@ static int tb_drom_parse_entries(struct tb_switch *sw)
return 0;
}
-/**
+/*
* tb_drom_copy_efi - copy drom supplied by EFI to sw->drom if present
*/
static int tb_drom_copy_efi(struct tb_switch *sw, u16 *size)
@@ -484,16 +490,54 @@ err_free:
return ret;
}
+static int usb4_copy_host_drom(struct tb_switch *sw, u16 *size)
+{
+ int ret;
+
+ ret = usb4_switch_drom_read(sw, 14, size, sizeof(*size));
+ if (ret)
+ return ret;
+
+ /* Size includes CRC8 + UID + CRC32 */
+ *size += 1 + 8 + 4;
+ sw->drom = kzalloc(*size, GFP_KERNEL);
+ if (!sw->drom)
+ return -ENOMEM;
+
+ ret = usb4_switch_drom_read(sw, 0, sw->drom, *size);
+ if (ret) {
+ kfree(sw->drom);
+ sw->drom = NULL;
+ }
+
+ return ret;
+}
+
+static int tb_drom_read_n(struct tb_switch *sw, u16 offset, u8 *val,
+ size_t count)
+{
+ if (tb_switch_is_usb4(sw))
+ return usb4_switch_drom_read(sw, offset, val, count);
+ return tb_eeprom_read_n(sw, offset, val, count);
+}
+
/**
- * tb_drom_read - copy drom to sw->drom and parse it
+ * tb_drom_read() - Copy DROM to sw->drom and parse it
+ * @sw: Router whose DROM to read and parse
+ *
+ * This function reads router DROM and if successful parses the entries and
+ * populates the fields in @sw accordingly. Can be called for any router
+ * generation.
+ *
+ * Returns %0 in case of success and negative errno otherwise.
*/
int tb_drom_read(struct tb_switch *sw)
{
- u16 drom_offset;
u16 size;
u32 crc;
struct tb_drom_header *header;
- int res;
+ int res, retries = 1;
+
if (sw->drom)
return 0;
@@ -510,29 +554,26 @@ int tb_drom_read(struct tb_switch *sw)
goto parse;
/*
- * The root switch contains only a dummy drom (header only,
- * no entries). Hardcode the configuration here.
+ * USB4 hosts may support reading DROM through router
+ * operations.
*/
- 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];
+ if (tb_switch_is_usb4(sw)) {
+ usb4_switch_read_uid(sw, &sw->uid);
+ if (!usb4_copy_host_drom(sw, &size))
+ goto parse;
+ } else {
+ /*
+ * The root switch contains only a dummy drom
+ * (header only, no entries). Hardcode the
+ * configuration here.
+ */
+ tb_drom_read_uid_only(sw, &sw->uid);
+ }
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);
+ res = tb_drom_read_n(sw, 14, (u8 *) &size, 2);
if (res)
return res;
size &= 0x3ff;
@@ -546,7 +587,7 @@ int tb_drom_read(struct tb_switch *sw)
sw->drom = kzalloc(size, GFP_KERNEL);
if (!sw->drom)
return -ENOMEM;
- res = tb_eeprom_read_n(sw, drom_offset, sw->drom, size);
+ res = tb_drom_read_n(sw, 0, sw->drom, size);
if (res)
goto err;
@@ -569,6 +610,7 @@ parse:
sw->uid = header->uid;
sw->vendor = header->vendor_id;
sw->device = header->model_id;
+ tb_check_quirks(sw);
crc = tb_crc32(sw->drom + TB_DROM_DATA_START, header->data_len);
if (crc != header->data_crc32) {
@@ -581,7 +623,17 @@ parse:
tb_sw_warn(sw, "drom device_rom_revision %#x unknown\n",
header->device_rom_revision);
- return tb_drom_parse_entries(sw);
+ res = tb_drom_parse_entries(sw);
+ /* If the DROM parsing fails, wait a moment and retry once */
+ if (res == -EILSEQ && retries--) {
+ tb_sw_warn(sw, "parsing DROM failed, retrying\n");
+ msleep(100);
+ res = tb_drom_read_n(sw, 0, sw->drom, size);
+ if (!res)
+ goto parse;
+ }
+
+ return res;
err:
kfree(sw->drom);
sw->drom = NULL;