mirror of
https://github.com/torvalds/linux.git
synced 2024-11-10 22:21:40 +00:00
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 <mario.limonciello@amd.com> [mw: renamed root switch to host router, split device handling too] Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
This commit is contained in:
parent
c8325b3227
commit
ebde5ba27c
@ -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);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user