summaryrefslogtreecommitdiff
path: root/drivers/thunderbolt/eeprom.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/thunderbolt/eeprom.c')
-rw-r--r--drivers/thunderbolt/eeprom.c456
1 files changed, 293 insertions, 163 deletions
diff --git a/drivers/thunderbolt/eeprom.c b/drivers/thunderbolt/eeprom.c
index 81e8ac4c5805..5477b9437048 100644
--- a/drivers/thunderbolt/eeprom.c
+++ b/drivers/thunderbolt/eeprom.c
@@ -7,24 +7,25 @@
*/
#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)
{
- return tb_sw_write(sw, ctl, TB_CFG_SWITCH, sw->cap_plug_events + 4, 1);
+ return tb_sw_write(sw, ctl, TB_CFG_SWITCH, sw->cap_plug_events + ROUTER_CS_4, 1);
}
-/**
- * tb_eeprom_ctl_write() - read control word
+/*
+ * tb_eeprom_ctl_read() - 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);
+ return tb_sw_read(sw, ctl, TB_CFG_SWITCH, sw->cap_plug_events + ROUTER_CS_4, 1);
}
enum tb_eeprom_transfer {
@@ -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
@@ -45,27 +46,27 @@ static int tb_eeprom_active(struct tb_switch *sw, bool enable)
if (res)
return res;
if (enable) {
- ctl.access_high = 1;
+ ctl.bit_banging_enable = 1;
res = tb_eeprom_ctl_write(sw, &ctl);
if (res)
return res;
- ctl.access_low = 0;
+ ctl.fl_cs = 0;
return tb_eeprom_ctl_write(sw, &ctl);
} else {
- ctl.access_low = 1;
+ ctl.fl_cs = 1;
res = tb_eeprom_ctl_write(sw, &ctl);
if (res)
return res;
- ctl.access_high = 0;
+ ctl.bit_banging_enable = 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.
+ * If TB_EEPROM_IN is passed, then the bit can be retrieved from ctl->fl_do.
+ * If TB_EEPROM_OUT is passed, then ctl->fl_di will be written.
*/
static int tb_eeprom_transfer(struct tb_switch *sw, struct tb_eeprom_ctl *ctl,
enum tb_eeprom_transfer direction)
@@ -76,7 +77,7 @@ static int tb_eeprom_transfer(struct tb_switch *sw, struct tb_eeprom_ctl *ctl,
if (res)
return res;
}
- ctl->clock = 1;
+ ctl->fl_sk = 1;
res = tb_eeprom_ctl_write(sw, ctl);
if (res)
return res;
@@ -85,11 +86,11 @@ static int tb_eeprom_transfer(struct tb_switch *sw, struct tb_eeprom_ctl *ctl,
if (res)
return res;
}
- ctl->clock = 0;
+ ctl->fl_sk = 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)
@@ -100,7 +101,7 @@ static int tb_eeprom_out(struct tb_switch *sw, u8 val)
if (res)
return res;
for (i = 0; i < 8; i++) {
- ctl.data_out = val & 0x80;
+ ctl.fl_di = val & 0x80;
res = tb_eeprom_transfer(sw, &ctl, TB_EEPROM_OUT);
if (res)
return res;
@@ -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)
@@ -125,18 +126,57 @@ static int tb_eeprom_in(struct tb_switch *sw, u8 *val)
res = tb_eeprom_transfer(sw, &ctl, TB_EEPROM_IN);
if (res)
return res;
- *val |= ctl.data_in;
+ *val |= ctl.fl_do;
}
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;
@@ -171,10 +211,13 @@ static u8 tb_crc8(u8 *data, int len)
static u32 tb_crc32(void *data, size_t len)
{
- return ~__crc32c_le(~0, data, len);
+ return ~crc32c(~0, data, len);
}
-#define TB_DROM_DATA_START 13
+#define TB_DROM_DATA_START 13
+#define TB_DROM_HEADER_SIZE 22
+#define USB4_DROM_HEADER_SIZE 16
+
struct tb_drom_header {
/* BYTE 0 */
u8 uid_crc8; /* checksum for uid */
@@ -184,9 +227,9 @@ struct tb_drom_header {
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 data_len:12;
+ u8 reserved:4;
+ /* BYTES 16-21 - Only for TBT DROM, nonexistent in USB4 DROM */
u16 vendor_id;
u16 model_id;
u8 model_rev;
@@ -208,7 +251,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 {
@@ -237,57 +280,35 @@ struct tb_drom_entry_port {
u8 unknown4:2;
} __packed;
+/* USB4 product descriptor */
+struct tb_drom_entry_desc {
+ struct tb_drom_entry_header header;
+ u16 bcdUSBSpec;
+ u16 idVendor;
+ u16 idProduct;
+ u16 bcdProductFWRevision;
+ u32 TID;
+ u8 productHWRevision;
+};
/**
- * 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.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
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;
@@ -323,6 +344,16 @@ static int tb_drom_parse_entry_generic(struct tb_switch *sw,
if (!sw->device_name)
return -ENOMEM;
break;
+ case 9: {
+ const struct tb_drom_entry_desc *desc =
+ (const struct tb_drom_entry_desc *)entry;
+
+ if (!sw->vendor && !sw->device) {
+ sw->vendor = desc->idVendor;
+ sw->device = desc->idProduct;
+ }
+ break;
+ }
}
return 0;
@@ -370,15 +401,15 @@ 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.
*/
-static int tb_drom_parse_entries(struct tb_switch *sw)
+static int tb_drom_parse_entries(struct tb_switch *sw, size_t header_size)
{
struct tb_drom_header *header = (void *) sw->drom;
- u16 pos = sizeof(*header);
+ u16 pos = header_size;
u16 drom_size = header->data_len + TB_DROM_DATA_START;
int res;
@@ -386,7 +417,7 @@ 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");
+ tb_sw_warn(sw, "DROM buffer overrun\n");
return -EIO;
}
@@ -406,7 +437,30 @@ static int tb_drom_parse_entries(struct tb_switch *sw)
return 0;
}
-/**
+static int tb_switch_drom_alloc(struct tb_switch *sw, size_t size)
+{
+ sw->drom = kzalloc(size, GFP_KERNEL);
+ if (!sw->drom)
+ return -ENOMEM;
+
+#ifdef CONFIG_DEBUG_FS
+ sw->drom_blob.data = sw->drom;
+ sw->drom_blob.size = size;
+#endif
+ return 0;
+}
+
+static void tb_switch_drom_free(struct tb_switch *sw)
+{
+#ifdef CONFIG_DEBUG_FS
+ sw->drom_blob.data = NULL;
+ sw->drom_blob.size = 0;
+#endif
+ kfree(sw->drom);
+ sw->drom = NULL;
+}
+
+/*
* 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)
@@ -414,13 +468,13 @@ static int tb_drom_copy_efi(struct tb_switch *sw, u16 *size)
struct device *dev = &sw->tb->nhi->pdev->dev;
int len, res;
- len = device_property_read_u8_array(dev, "ThunderboltDROM", NULL, 0);
+ len = device_property_count_u8(dev, "ThunderboltDROM");
if (len < 0 || len < sizeof(struct tb_drom_header))
return -EINVAL;
- sw->drom = kmalloc(len, GFP_KERNEL);
- if (!sw->drom)
- return -ENOMEM;
+ res = tb_switch_drom_alloc(sw, len);
+ if (res)
+ return res;
res = device_property_read_u8_array(dev, "ThunderboltDROM", sw->drom,
len);
@@ -435,21 +489,19 @@ static int tb_drom_copy_efi(struct tb_switch *sw, u16 *size)
return 0;
err:
- kfree(sw->drom);
- sw->drom = NULL;
+ tb_switch_drom_free(sw);
return -EINVAL;
}
static int tb_drom_copy_nvm(struct tb_switch *sw, u16 *size)
{
- u32 drom_offset;
+ u16 drom_offset;
int ret;
if (!sw->dma_port)
return -ENODEV;
- ret = tb_sw_read(sw, &drom_offset, TB_CFG_SWITCH,
- sw->cap_plug_events + 12, 1);
+ ret = tb_eeprom_get_drom_offset(sw, &drom_offset);
if (ret)
return ret;
@@ -463,13 +515,15 @@ static int tb_drom_copy_nvm(struct tb_switch *sw, u16 *size)
/* Size includes CRC8 + UID + CRC32 */
*size += 1 + 8 + 4;
- sw->drom = kzalloc(*size, GFP_KERNEL);
- if (!sw->drom)
- return -ENOMEM;
+ ret = tb_switch_drom_alloc(sw, *size);
+ if (ret)
+ return ret;
ret = dma_port_flash_read(sw->dma_port, drom_offset, sw->drom, *size);
- if (ret)
- goto err_free;
+ if (ret) {
+ tb_switch_drom_free(sw);
+ return ret;
+ }
/*
* Read UID from the minimal DROM because the one in NVM is just
@@ -477,97 +531,69 @@ static int tb_drom_copy_nvm(struct tb_switch *sw, u16 *size)
*/
tb_drom_read_uid_only(sw, &sw->uid);
return 0;
-
-err_free:
- kfree(sw->drom);
- sw->drom = NULL;
- return ret;
}
-/**
- * tb_drom_read - copy drom to sw->drom and parse it
- */
-int tb_drom_read(struct tb_switch *sw)
+static int usb4_copy_drom(struct tb_switch *sw, u16 *size)
{
- u16 drom_offset;
- u16 size;
- u32 crc;
- struct tb_drom_header *header;
- int res;
- if (sw->drom)
- return 0;
+ int ret;
- if (tb_route(sw) == 0) {
- /*
- * Apple's NHI EFI driver supplies a DROM for the root switch
- * in a device property. Use it if available.
- */
- if (tb_drom_copy_efi(sw, &size) == 0)
- goto parse;
-
- /* Non-Apple hardware has the DROM as part of NVM */
- if (tb_drom_copy_nvm(sw, &size) == 0)
- goto parse;
-
- /*
- * The root switch contains only a dummy drom (header only,
- * no entries). Hardcode the configuration here.
- */
- tb_drom_read_uid_only(sw, &sw->uid);
+ ret = usb4_switch_drom_read(sw, 14, size, sizeof(*size));
+ if (ret)
+ return ret;
- 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];
+ /* Size includes CRC8 + UID + CRC32 */
+ *size += 1 + 8 + 4;
+ ret = tb_switch_drom_alloc(sw, *size);
+ if (ret)
+ return ret;
- 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];
+ ret = usb4_switch_drom_read(sw, 0, sw->drom, *size);
+ if (ret)
+ tb_switch_drom_free(sw);
- /* Port 5 is inaccessible on this gen 1 controller */
- if (sw->config.device_id == PCI_DEVICE_ID_INTEL_LIGHT_RIDGE)
- sw->ports[5].disabled = true;
+ return ret;
+}
- return 0;
- }
+static int tb_drom_bit_bang(struct tb_switch *sw, u16 *size)
+{
+ int ret;
- res = tb_eeprom_get_drom_offset(sw, &drom_offset);
- if (res)
- return res;
+ ret = tb_eeprom_read_n(sw, 14, (u8 *)size, 2);
+ if (ret)
+ return ret;
- 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_dbg(sw, "reading drom (length: %#x)\n", size);
- if (size < sizeof(*header)) {
- tb_sw_warn(sw, "drom too small, aborting\n");
+ *size &= 0x3ff;
+ *size += TB_DROM_DATA_START;
+
+ tb_sw_dbg(sw, "reading DROM (length: %#x)\n", *size);
+ if (*size < sizeof(struct tb_drom_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;
+ ret = tb_switch_drom_alloc(sw, *size);
+ if (ret)
+ return ret;
+
+ ret = tb_eeprom_read_n(sw, 0, sw->drom, *size);
+ if (ret)
+ tb_switch_drom_free(sw);
-parse:
- header = (void *) sw->drom;
+ return ret;
+}
- if (header->data_len + TB_DROM_DATA_START != size) {
- tb_sw_warn(sw, "drom size mismatch, aborting\n");
- goto err;
- }
+static int tb_drom_parse_v1(struct tb_switch *sw)
+{
+ const struct tb_drom_header *header =
+ (const struct tb_drom_header *)sw->drom;
+ u32 crc;
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",
+ "DROM UID CRC8 mismatch (expected: %#x, got: %#x)\n",
header->uid_crc8, crc);
- goto err;
+ return -EIO;
}
if (!sw->uid)
sw->uid = header->uid;
@@ -577,18 +603,122 @@ parse:
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), continuing\n",
+ "DROM data CRC32 mismatch (expected: %#x, got: %#x), continuing\n",
header->data_crc32, crc);
}
- if (header->device_rom_revision > 2)
- tb_sw_warn(sw, "drom device_rom_revision %#x unknown\n",
- header->device_rom_revision);
+ return tb_drom_parse_entries(sw, TB_DROM_HEADER_SIZE);
+}
+
+static int usb4_drom_parse(struct tb_switch *sw)
+{
+ const struct tb_drom_header *header =
+ (const struct tb_drom_header *)sw->drom;
+ u32 crc;
+
+ 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), continuing\n",
+ header->data_crc32, crc);
+ }
+
+ return tb_drom_parse_entries(sw, USB4_DROM_HEADER_SIZE);
+}
+
+static int tb_drom_parse(struct tb_switch *sw, u16 size)
+{
+ const struct tb_drom_header *header = (const void *)sw->drom;
+ int ret;
+
+ if (header->data_len + TB_DROM_DATA_START != size) {
+ tb_sw_warn(sw, "DROM size mismatch\n");
+ ret = -EIO;
+ goto err;
+ }
+
+ tb_sw_dbg(sw, "DROM version: %d\n", header->device_rom_revision);
+
+ switch (header->device_rom_revision) {
+ case 3:
+ ret = usb4_drom_parse(sw);
+ break;
+ default:
+ tb_sw_warn(sw, "DROM device_rom_revision %#x unknown\n",
+ header->device_rom_revision);
+ fallthrough;
+ case 1:
+ ret = tb_drom_parse_v1(sw);
+ break;
+ }
+
+ if (ret) {
+ tb_sw_warn(sw, "parsing DROM failed\n");
+ goto err;
+ }
+
+ return 0;
- return tb_drom_parse_entries(sw);
err:
- kfree(sw->drom);
- sw->drom = NULL;
- return -EIO;
+ tb_switch_drom_free(sw);
+ return ret;
+}
+
+static int tb_drom_host_read(struct tb_switch *sw)
+{
+ u16 size;
+
+ if (tb_switch_is_usb4(sw)) {
+ usb4_switch_read_uid(sw, &sw->uid);
+ if (!usb4_copy_drom(sw, &size))
+ return tb_drom_parse(sw, size);
+ } else {
+ if (!tb_drom_copy_efi(sw, &size))
+ return tb_drom_parse(sw, size);
+
+ if (!tb_drom_copy_nvm(sw, &size))
+ return tb_drom_parse(sw, size);
+
+ tb_drom_read_uid_only(sw, &sw->uid);
+ }
+
+ return 0;
+}
+
+static int tb_drom_device_read(struct tb_switch *sw)
+{
+ u16 size;
+ int ret;
+
+ if (tb_switch_is_usb4(sw)) {
+ usb4_switch_read_uid(sw, &sw->uid);
+ ret = usb4_copy_drom(sw, &size);
+ } else {
+ ret = tb_drom_bit_bang(sw, &size);
+ }
+
+ if (ret)
+ return ret;
+
+ return tb_drom_parse(sw, size);
+}
+
+/**
+ * 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.
+ *
+ * Return: %0 on success, negative errno otherwise.
+ */
+int tb_drom_read(struct tb_switch *sw)
+{
+ if (sw->drom)
+ return 0;
+ if (!tb_route(sw))
+ return tb_drom_host_read(sw);
+ return tb_drom_device_read(sw);
}