thunderbolt: Add validation for Titan Ridge devices

Titan Ridge devices may contain multiple controllers so we need to make
sure the supplied NVM image multi controller number matches the
controller in question. This is pretty much same than we had for Alpine
Ridge (X of N) but the difference is that this multi controller
information is found in DROM instead and the location is not fixed.

For this reason we implement a generic DROM entry parser and use it to
dynamically fill in correct location of multi controller entry based on
the controller active firmware.

In addition to that we add a check for the NVM flash size just like we
do for Titan Ridge host.

Signed-off-by: Andrei Emeltchenko <andrei.emeltchenko@intel.com>
Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
This commit is contained in:
Andrei Emeltchenko 2018-07-25 20:04:12 +03:00 committed by Richard Hughes
parent bacb769c90
commit 3d73b1fad5

View File

@ -39,6 +39,10 @@ typedef struct {
guint ports;
} FuThunderboltHwInfo;
enum {
DROM_ENTRY_MC = 0x6,
};
static const FuThunderboltHwInfo *
get_hw_info (guint16 id)
{
@ -242,6 +246,56 @@ read_ucode_section_len (guint32 offset,
return TRUE;
}
/*
* reads generic entries from DROM based on type field and fills
* location to point to the entry data if found
*/
static gboolean
read_drom_entry_location (const FuThunderboltFwObject *fw,
guint8 type,
FuThunderboltFwLocation *location,
GError **error)
{
const FuThunderboltFwLocation drom_len_loc = { .offset = 0x0E, .len = 2, .section = DROM_SECTION, .description = "DROM length" };
FuThunderboltFwLocation drom_entry_loc = { .len = 2, .section = DROM_SECTION, .description = "DROM generic entry" };
guint16 drom_size;
if (!read_uint16 (&drom_len_loc, fw, &drom_size, error))
return FALSE;
drom_size &= 0x0FFF;
/* drom_size is size of DROM block except for identification
* section and crc32 so add them here */
drom_size += 9 + 4;
/* DROM entries start right after the identification section */
drom_entry_loc.offset = 9 + 4 + 9;
do {
g_autoptr(GByteArray) entry = NULL;
guint8 entry_type;
guint8 entry_length;
entry = read_location (&drom_entry_loc, fw, error);
if (entry == NULL)
return FALSE;
entry_length = entry->data[0];
entry_type = entry->data[1] & 0x3F;
/* generic entry (port bit is not set) */
if ((entry->data[1] & (1 << 7)) == 0 && entry_type == type) {
location->len = entry_length - 2;
location->offset = drom_entry_loc.offset + 2;
return TRUE;
}
drom_entry_loc.offset += entry_length;
} while (drom_entry_loc.offset < drom_size);
return TRUE;
}
/*
* Takes a FwObject and fills its section array up
* Assumes sections[DIGITAL_SECTION].offset is already set
@ -438,20 +492,39 @@ get_host_locations (guint16 id)
}
static const FuThunderboltFwLocation *
get_device_locations (guint16 id)
get_device_locations (guint16 id, const FuThunderboltFwObject *fw, GError **error)
{
static const FuThunderboltFwLocation locations[] = {
static const FuThunderboltFwLocation AR[] = {
{ .offset = 0x124, .len = 1, .section = ARC_PARAMS_SECTION, .description = "X of N" },
{ 0 }
};
static FuThunderboltFwLocation TR[] = {
{ .offset = 0x45, .len = 1, .description = "Flash Size", .mask = 0x07 },
/* offset and len are read from DROM */
{ .section = DROM_SECTION, .description = "Multi Controller" },
{ 0 }
};
g_autoptr(GError) error_local = NULL;
switch (id) {
case 0x1578:
case 0x1576:
case 0x15D3:
case 0x15DA:
case 0x15C0:
return locations;
return AR;
case 0x15E7:
case 0x15EA:
case 0x15EF:
/* if the controller has multi controller entry need to
* compare it against the image. */
if (read_drom_entry_location (fw, DROM_ENTRY_MC, &TR[1],
&error_local))
return TR;
g_debug ("failed to parse DROM entry: %s", error_local->message);
/* fallthrough */
default:
return NULL;
}
@ -623,14 +696,20 @@ fu_plugin_thunderbolt_validate_image (GBytes *controller_fw,
if (hw_info->id == 0)
return UNKNOWN_DEVICE;
locations = is_host ?
get_host_locations (hw_info->id) :
get_device_locations (hw_info->id);
if (locations == NULL) {
g_set_error_literal (error,
FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED,
"FW locations to check not found for this controller");
return VALIDATION_FAILED;
if (is_host) {
locations = get_host_locations (hw_info->id);
if (locations == NULL) {
g_set_error_literal (error,
FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED,
"FW locations to check not found for this controller");
return VALIDATION_FAILED;
}
} else {
locations = get_device_locations (hw_info->id, &controller, error);
if (locations == NULL) {
/* error is set already by the above */
return VALIDATION_FAILED;
}
}
if (!compare_locations (&locations, &controller, &image, error))