From ebde5ba27c640e08e92c83fe30be0d9fa224eea9 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Thu, 23 Feb 2023 15:07:43 -0600 Subject: [PATCH] thunderbolt: Refactor DROM reading The NVM reading code has a series of gotos that potentially introduce unexpected behaviors with retries if something unexpected has failed to parse. Refactor the code to remove the gotos and drop the retry logic. Signed-off-by: Mario Limonciello [mw: renamed root switch to host router, split device handling too] Signed-off-by: Mika Westerberg --- drivers/thunderbolt/eeprom.c | 221 ++++++++++++++++++----------------- 1 file changed, 116 insertions(+), 105 deletions(-) diff --git a/drivers/thunderbolt/eeprom.c b/drivers/thunderbolt/eeprom.c index 3b96a55295a0..0f6099c18a94 100644 --- a/drivers/thunderbolt/eeprom.c +++ b/drivers/thunderbolt/eeprom.c @@ -416,7 +416,7 @@ static int tb_drom_parse_entries(struct tb_switch *sw, size_t header_size) if (pos + 1 == drom_size || pos + entry->len > drom_size || !entry->len) { tb_sw_warn(sw, "DROM buffer overrun\n"); - return -EILSEQ; + return -EIO; } switch (entry->type) { @@ -512,7 +512,7 @@ err_free: return ret; } -static int usb4_copy_host_drom(struct tb_switch *sw, u16 *size) +static int usb4_copy_drom(struct tb_switch *sw, u16 *size) { int ret; @@ -535,15 +535,40 @@ static int usb4_copy_host_drom(struct tb_switch *sw, u16 *size) return ret; } -static int tb_drom_read_n(struct tb_switch *sw, u16 offset, u8 *val, - size_t count) +static int tb_drom_bit_bang(struct tb_switch *sw, u16 *size) { - if (tb_switch_is_usb4(sw)) - return usb4_switch_drom_read(sw, offset, val, count); - return tb_eeprom_read_n(sw, offset, val, count); + int ret; + + ret = tb_eeprom_read_n(sw, 14, (u8 *)size, 2); + if (ret) + return ret; + + *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; + + ret = tb_eeprom_read_n(sw, 0, sw->drom, *size); + if (ret) + goto err; + + return 0; + +err: + kfree(sw->drom); + sw->drom = NULL; + return ret; } -static int tb_drom_parse(struct tb_switch *sw) +static int tb_drom_parse_v1(struct tb_switch *sw) { const struct tb_drom_header *header = (const struct tb_drom_header *)sw->drom; @@ -554,7 +579,7 @@ static int tb_drom_parse(struct tb_switch *sw) tb_sw_warn(sw, "DROM UID CRC8 mismatch (expected: %#x, got: %#x)\n", header->uid_crc8, crc); - return -EILSEQ; + return -EIO; } if (!sw->uid) sw->uid = header->uid; @@ -588,6 +613,85 @@ static int usb4_drom_parse(struct tb_switch *sw) 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; + +err: + kfree(sw->drom); + sw->drom = NULL; + + 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 @@ -600,103 +704,10 @@ static int usb4_drom_parse(struct tb_switch *sw) */ int tb_drom_read(struct tb_switch *sw) { - u16 size; - struct tb_drom_header *header; - int res, retries = 1; - if (sw->drom) return 0; - 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; - - /* - * USB4 hosts may support reading DROM through router - * operations. - */ - 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_drom_read_n(sw, 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"); - return -EIO; - } - - sw->drom = kzalloc(size, GFP_KERNEL); - if (!sw->drom) - return -ENOMEM; -read: - res = tb_drom_read_n(sw, 0, sw->drom, size); - if (res) - goto err; - -parse: - header = (void *) sw->drom; - - if (header->data_len + TB_DROM_DATA_START != size) { - tb_sw_warn(sw, "drom size mismatch\n"); - if (retries--) { - msleep(100); - goto read; - } - goto err; - } - - tb_sw_dbg(sw, "DROM version: %d\n", header->device_rom_revision); - - switch (header->device_rom_revision) { - case 3: - res = usb4_drom_parse(sw); - break; - default: - tb_sw_warn(sw, "DROM device_rom_revision %#x unknown\n", - header->device_rom_revision); - fallthrough; - case 1: - res = tb_drom_parse(sw); - break; - } - - /* If the DROM parsing fails, wait a moment and retry once */ - if (res == -EILSEQ && retries--) { - tb_sw_warn(sw, "parsing DROM failed\n"); - msleep(100); - goto read; - } - - if (!res) - return 0; - -err: - kfree(sw->drom); - sw->drom = NULL; - return -EIO; + if (!tb_route(sw)) + return tb_drom_host_read(sw); + return tb_drom_device_read(sw); }