mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-16 13:54:31 +00:00
797 lines
25 KiB
C
797 lines
25 KiB
C
/*
|
|
* Copyright (C) 2017 Intel Corporation.
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1+
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "fu-thunderbolt-image.h"
|
|
|
|
#include <string.h>
|
|
#include <libfwupd/fwupd-error.h>
|
|
|
|
enum FuThunderboltSection {
|
|
DIGITAL_SECTION,
|
|
DROM_SECTION,
|
|
ARC_PARAMS_SECTION,
|
|
DRAM_UCODE_SECTION,
|
|
SECTION_COUNT
|
|
};
|
|
|
|
typedef struct {
|
|
enum FuThunderboltSection section; /* default is DIGITAL_SECTION */
|
|
guint32 offset;
|
|
guint32 len;
|
|
guint8 mask; /* 0 means "no mask" */
|
|
const gchar *description;
|
|
} FuThunderboltFwLocation;
|
|
|
|
typedef struct {
|
|
const guint8 *data;
|
|
gsize len;
|
|
guint32 *sections;
|
|
} FuThunderboltFwObject;
|
|
|
|
typedef struct {
|
|
guint16 id;
|
|
guint gen;
|
|
guint ports;
|
|
} FuThunderboltHwInfo;
|
|
|
|
enum {
|
|
DROM_ENTRY_MC = 0x6,
|
|
};
|
|
|
|
static const FuThunderboltHwInfo *
|
|
get_hw_info (guint16 id)
|
|
{
|
|
static const FuThunderboltHwInfo hw_info_arr[] = {
|
|
{ 0x156D, 2, 2 }, /* FR 4C */
|
|
{ 0x156B, 2, 1 }, /* FR 2C */
|
|
{ 0x157E, 2, 1 }, /* WR */
|
|
|
|
{ 0x1578, 3, 2 }, /* AR 4C */
|
|
{ 0x1576, 3, 1 }, /* AR 2C */
|
|
{ 0x15C0, 3, 1 }, /* AR LP */
|
|
{ 0x15D3, 3, 2 }, /* AR-C 4C */
|
|
{ 0x15DA, 3, 1 }, /* AR-C 2C */
|
|
|
|
{ 0x15E7, 3, 1 }, /* TR 2C */
|
|
{ 0x15EA, 3, 2 }, /* TR 4C */
|
|
{ 0x15EF, 3, 2 }, /* TR 4C device */
|
|
|
|
{ 0 }
|
|
};
|
|
|
|
for (gint i = 0; hw_info_arr[i].id != 0; i++)
|
|
if (hw_info_arr[i].id == id)
|
|
return hw_info_arr + i;
|
|
return NULL;
|
|
}
|
|
|
|
static inline gboolean
|
|
valid_farb_pointer (guint32 pointer)
|
|
{
|
|
return pointer != 0 && pointer != 0xFFFFFF;
|
|
}
|
|
|
|
static inline gboolean
|
|
valid_pd_pointer (guint32 pointer)
|
|
{
|
|
return pointer != 0 && pointer != 0xFFFFFFFF;
|
|
}
|
|
|
|
/* returns NULL on error */
|
|
static GByteArray *
|
|
read_location (const FuThunderboltFwLocation *location,
|
|
const FuThunderboltFwObject *fw,
|
|
GError **error)
|
|
{
|
|
guint32 location_start = fw->sections[location->section] + location->offset;
|
|
g_autoptr(GByteArray) read = g_byte_array_new ();
|
|
|
|
if (location_start > fw->len || location_start + location->len > fw->len) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR, FWUPD_ERROR_READ,
|
|
"Given location is outside of the given FW (%s)",
|
|
location->description ? location->description : "N/A");
|
|
return NULL;
|
|
}
|
|
|
|
read = g_byte_array_append (read,
|
|
fw->data + location_start,
|
|
location->len);
|
|
|
|
if (location->mask)
|
|
read->data[0] &= location->mask;
|
|
return g_steal_pointer (&read);
|
|
}
|
|
|
|
static gboolean
|
|
read_farb_pointer_impl (const FuThunderboltFwLocation *location,
|
|
const FuThunderboltFwObject *fw,
|
|
guint32 *value,
|
|
GError **error)
|
|
{
|
|
g_autoptr(GByteArray) farb = read_location (location, fw, error);
|
|
if (farb == NULL)
|
|
return FALSE;
|
|
*value = 0;
|
|
memcpy (value, farb->data, farb->len);
|
|
*value = GUINT32_FROM_LE (*value);
|
|
return TRUE;
|
|
}
|
|
|
|
/* returns invalid FARB pointer on error */
|
|
static guint32
|
|
read_farb_pointer (const FuThunderboltFwObject *fw, GError **error)
|
|
{
|
|
const FuThunderboltFwLocation farb0 = { .offset = 0, .len = 3, .description = "farb0" };
|
|
const FuThunderboltFwLocation farb1 = { .offset = 0x1000, .len = 3, .description = "farb1" };
|
|
|
|
guint32 value;
|
|
if (!read_farb_pointer_impl (&farb0, fw, &value, error))
|
|
return 0;
|
|
if (valid_farb_pointer (value))
|
|
return value;
|
|
|
|
if (!read_farb_pointer_impl (&farb1, fw, &value, error))
|
|
return 0;
|
|
if (!valid_farb_pointer (value)) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE,
|
|
"Invalid FW image file format");
|
|
return 0;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
static gboolean
|
|
compare (const FuThunderboltFwLocation *location,
|
|
const FuThunderboltFwObject *controller_fw,
|
|
const FuThunderboltFwObject *image_fw,
|
|
gboolean *result,
|
|
GError **error)
|
|
{
|
|
g_autoptr(GByteArray) controller_data = NULL;
|
|
g_autoptr(GByteArray) image_data = NULL;
|
|
|
|
controller_data = read_location (location, controller_fw, error);
|
|
if (controller_data == NULL)
|
|
return FALSE;
|
|
|
|
image_data = read_location (location, image_fw, error);
|
|
if (image_data == NULL)
|
|
return FALSE;
|
|
|
|
*result = memcmp (controller_data->data, image_data->data, location->len) == 0;
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
read_bool (const FuThunderboltFwLocation *location,
|
|
const FuThunderboltFwObject *fw,
|
|
gboolean *val,
|
|
GError **error)
|
|
{
|
|
g_autoptr(GByteArray) read = read_location (location, fw, error);
|
|
if (read == NULL)
|
|
return FALSE;
|
|
for (gsize i = 0; i < read->len; i++)
|
|
if (read->data[i] != 0) {
|
|
*val = TRUE;
|
|
return TRUE;
|
|
}
|
|
*val = FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
read_uint16 (const FuThunderboltFwLocation *location,
|
|
const FuThunderboltFwObject *fw,
|
|
guint16 *value,
|
|
GError **error)
|
|
{
|
|
g_autoptr(GByteArray) read = read_location (location, fw, error);
|
|
g_assert_cmpuint (location->len, ==, sizeof (guint16));
|
|
if (read == NULL)
|
|
return FALSE;
|
|
|
|
*value = 0;
|
|
memcpy (value, read->data, read->len);
|
|
*value = GUINT16_FROM_LE (*value);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
read_uint32 (const FuThunderboltFwLocation *location,
|
|
const FuThunderboltFwObject *fw,
|
|
guint32 *value,
|
|
GError **error)
|
|
{
|
|
g_autoptr(GByteArray) read = read_location (location, fw, error);
|
|
g_assert_cmpuint (location->len, ==, sizeof (guint32));
|
|
if (read == NULL)
|
|
return FALSE;
|
|
|
|
*value = 0;
|
|
memcpy (value, read->data, read->len);
|
|
*value = GUINT32_FROM_LE (*value);
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* Size of ucode sections is uint16 value saved at the start of the section,
|
|
* it's in DWORDS (4-bytes) units and it doesn't include itself. We need the
|
|
* offset to the next section, so we translate it to bytes and add 2 for the
|
|
* size field itself.
|
|
*
|
|
* offset parameter must be relative to digital section
|
|
*/
|
|
static gboolean
|
|
read_ucode_section_len (guint32 offset,
|
|
const FuThunderboltFwObject *fw,
|
|
guint16 *value,
|
|
GError **error)
|
|
{
|
|
const FuThunderboltFwLocation section_size = { .offset = offset, .len = 2, .description = "size field" };
|
|
if (!read_uint16 (§ion_size, fw, value, error))
|
|
return FALSE;
|
|
*value *= sizeof (guint32);
|
|
*value += section_size.len;
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* reads generic entries from DROM based on type field and fills
|
|
* location to point to the entry data if found. Returns TRUE if there
|
|
* was no error even if the entry was not found (location->offset is != 0
|
|
* when entry was 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
|
|
*/
|
|
static gboolean
|
|
read_sections (const FuThunderboltFwObject *fw, gboolean is_host, guint gen, GError **error)
|
|
{
|
|
const FuThunderboltFwLocation arc_params_offset = { .offset = 0x75, .len = 4, .description = "arc params offset" };
|
|
const FuThunderboltFwLocation drom_offset = { .offset = 0x10E, .len = 4, .description = "DROM offset" };
|
|
guint32 offset;
|
|
|
|
if (gen >= 3 || gen == 0) {
|
|
if (!read_uint32 (&drom_offset, fw, &offset, error))
|
|
return FALSE;
|
|
fw->sections[DROM_SECTION] = offset + fw->sections[DIGITAL_SECTION];
|
|
|
|
if (!read_uint32 (&arc_params_offset, fw, &offset, error))
|
|
return FALSE;
|
|
fw->sections[ARC_PARAMS_SECTION] = offset + fw->sections[DIGITAL_SECTION];
|
|
}
|
|
|
|
if (is_host && gen > 2) {
|
|
/*
|
|
* Algorithm:
|
|
* To find the DRAM section, we have to jump from section to
|
|
* section in a chain of sections.
|
|
* available_sections location tells what sections exist at all
|
|
* (with a flag per section).
|
|
* ee_ucode_start_addr location tells the offset of the first
|
|
* section in the list relatively to the digital section start.
|
|
* After having the offset of the first section, we have a loop
|
|
* over the section list. If the section exists, we read its
|
|
* length (2 bytes at section start) and add it to current
|
|
* offset to find the start of the next section. Otherwise, we
|
|
* already have the next section offset...
|
|
*/
|
|
const unsigned DRAM_FLAG = 1 << 6;
|
|
const FuThunderboltFwLocation available_sections_loc = { .offset = 0x2, .len = 1, .description = "sections" };
|
|
const FuThunderboltFwLocation ee_ucode_start_addr_loc = { .offset = 0x3, .len = 2, .description = "ucode start" };
|
|
|
|
guint16 ucode_offset;
|
|
|
|
g_autoptr(GByteArray) available_sections =
|
|
read_location (&available_sections_loc, fw, error);
|
|
if (available_sections == NULL)
|
|
return FALSE;
|
|
|
|
if (!read_uint16 (&ee_ucode_start_addr_loc, fw, &ucode_offset, error))
|
|
return FALSE;
|
|
offset = ucode_offset;
|
|
|
|
if ((available_sections->data[0] & DRAM_FLAG) == 0) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE,
|
|
"Can't find needed FW sections in the FW image file");
|
|
return FALSE;
|
|
}
|
|
|
|
for (unsigned u = 1; u < DRAM_FLAG; u <<= 1)
|
|
if (u & available_sections->data[0]) {
|
|
if (!read_ucode_section_len (offset, fw, &ucode_offset, error))
|
|
return FALSE;
|
|
offset += ucode_offset;
|
|
}
|
|
|
|
fw->sections[DRAM_UCODE_SECTION] = offset + fw->sections[DIGITAL_SECTION];
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static inline gboolean
|
|
missing_needed_drom (const FuThunderboltFwObject *fw, gboolean is_host, guint gen)
|
|
{
|
|
if (fw->sections[DROM_SECTION] != 0)
|
|
return FALSE;
|
|
if (is_host && gen < 3)
|
|
return FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* Controllers that can have 1 or 2 ports have additional locations to check in
|
|
* the 2 ports case. To make this as generic as possible, both sets are stored
|
|
* in the same array with an empty entry separating them. The 1 port case should
|
|
* stop comparing at the separator and the 2 ports case should continue
|
|
* iterating the array to compare the rest.
|
|
*/
|
|
static const FuThunderboltFwLocation *
|
|
get_host_locations (guint16 id)
|
|
{
|
|
static const FuThunderboltFwLocation FR[] = {
|
|
{ .offset = 0x10, .len = 4, .description = "PCIe Settings" },
|
|
{ .offset = 0x143, .len = 1, .description = "CIO-Port0_TX" },
|
|
{ .offset = 0x153, .len = 1, .description = "CIO-Port0_RX" },
|
|
{ .offset = 0x147, .len = 1, .description = "CIO-Port1_TX" },
|
|
{ .offset = 0x157, .len = 1, .description = "CIO-Port1_RX" },
|
|
{ .offset = 0x211, .len = 1, .description = "Snk0_0(DP-in)" },
|
|
{ .offset = 0x215, .len = 1, .description = "Snk0_1(DP-in)" },
|
|
{ .offset = 0x219, .len = 1, .description = "Snk0_2(DP-in)" },
|
|
{ .offset = 0x21D, .len = 1, .description = "Snk0_3(DP-in)" },
|
|
{ .offset = 0X2175, .len = 1, .description = "PA(DP-out)" },
|
|
{ .offset = 0X2179, .len = 1, .description = "PB(DP-out)" },
|
|
{ .offset = 0X217D, .len = 1, .description = "Src0(DP-out)", .mask = 0xAA },
|
|
{ 0 },
|
|
|
|
{ .offset = 0x14B, .len = 1, .description = "CIO-Port2_TX" },
|
|
{ .offset = 0x15B, .len = 1, .description = "CIO-Port2_RX" },
|
|
{ .offset = 0x14F, .len = 1, .description = "CIO-Port3_TX" },
|
|
{ .offset = 0x15F, .len = 1, .description = "CIO-Port3_RX" },
|
|
{ .offset = 0X11C3, .len = 1, .description = "Snk1_0(DP-in)" },
|
|
{ .offset = 0X11C7, .len = 1, .description = "Snk1_1(DP-in)" },
|
|
{ .offset = 0X11CB, .len = 1, .description = "Snk1_2(DP-in)" },
|
|
{ .offset = 0X11CF, .len = 1, .description = "Snk1_3(DP-in)" },
|
|
{ 0 }
|
|
};
|
|
|
|
static const FuThunderboltFwLocation WR[] = {
|
|
{ .offset = 0x10, .len = 4, .description = "PCIe Settings" },
|
|
{ .offset = 0x14F, .len = 1, .description = "CIO-Port0_TX" },
|
|
{ .offset = 0x157, .len = 1, .description = "CIO-Port0_RX" },
|
|
{ .offset = 0x153, .len = 1, .description = "CIO-Port1_TX" },
|
|
{ .offset = 0x15B, .len = 1, .description = "CIO-Port1_RX" },
|
|
{ .offset = 0x1F1, .len = 1, .description = "Snk0_0(DP-in)" },
|
|
{ .offset = 0x1F5, .len = 1, .description = "Snk0_1(DP-in)" },
|
|
{ .offset = 0x1F9, .len = 1, .description = "Snk0_2(DP-in)" },
|
|
{ .offset = 0x1FD, .len = 1, .description = "Snk0_3(DP-in)" },
|
|
{ .offset = 0X11A5, .len = 1, .description = "PA(DP-out)" },
|
|
{ 0 }
|
|
};
|
|
|
|
static const FuThunderboltFwLocation AR[] = {
|
|
{ .offset = 0x10, .len = 4, .description = "PCIe Settings" },
|
|
{ .offset = 0x12, .len = 1, .description = "PA", .mask = 0xCC, .section = DRAM_UCODE_SECTION },
|
|
{ .offset = 0x121, .len = 1, .description = "Snk0" },
|
|
{ .offset = 0x129, .len = 1, .description = "Snk1" },
|
|
{ .offset = 0x136, .len = 1, .description = "Src0", .mask = 0xF0 },
|
|
{ .offset = 0xB6, .len = 1, .description = "PA/PB (USB2)", .mask = 0xC0 },
|
|
{ .offset = 0x45, .len = 1, .description = "Flash Size", .mask = 0x07 },
|
|
{ .offset = 0x7B, .len = 1, .description = "Native", .mask = 0x20 },
|
|
{ 0 },
|
|
|
|
{ .offset = 0x13, .len = 1, .description = "PB", .mask = 0xCC, .section = DRAM_UCODE_SECTION },
|
|
{ 0 }
|
|
};
|
|
|
|
static const FuThunderboltFwLocation AR_LP[] = {
|
|
{ .offset = 0x10, .len = 4, .description = "PCIe Settings" },
|
|
{ .offset = 0x12, .len = 1, .description = "PA", .mask = 0xCC, .section = DRAM_UCODE_SECTION },
|
|
{ .offset = 0x13, .len = 1, .description = "PB", .mask = 0x44, .section = DRAM_UCODE_SECTION },
|
|
{ .offset = 0x121, .len = 1, .description = "Snk0" },
|
|
{ .offset = 0xB6, .len = 1, .description = "PA/PB (USB2)", .mask = 0xC0 },
|
|
{ .offset = 0x45, .len = 1, .description = "Flash Size", .mask = 0x07 },
|
|
{ .offset = 0x7B, .len = 1, .description = "Native", .mask = 0x20 },
|
|
{ 0 }
|
|
};
|
|
|
|
static const FuThunderboltFwLocation TR[] = {
|
|
{ .offset = 0x10, .len = 4, .description = "PCIe Settings" },
|
|
{ .offset = 0x12, .len = 1, .description = "PA", .mask = 0xCC, .section = DRAM_UCODE_SECTION },
|
|
{ .offset = 0x121, .len = 1, .description = "Snk0" },
|
|
{ .offset = 0x129, .len = 1, .description = "Snk1" },
|
|
{ .offset = 0x136, .len = 1, .description = "Src0", .mask = 0xF0 },
|
|
{ .offset = 0xB6, .len = 1, .description = "PA/PB (USB2)", .mask = 0xC0 },
|
|
{ .offset = 0x5E, .len = 1, .description = "Aux", .mask = 0x0F },
|
|
{ .offset = 0x45, .len = 1, .description = "Flash Size", .mask = 0x07 },
|
|
{ .offset = 0x7B, .len = 1, .description = "Native", .mask = 0x20 },
|
|
{ 0 },
|
|
|
|
{ .offset = 0x13, .len = 1, .description = "PB", .mask = 0xCC, .section = DRAM_UCODE_SECTION },
|
|
{ .offset = 0x5E, .len = 1, .description = "Aux (PB)", .mask = 0x10 },
|
|
{ 0 }
|
|
};
|
|
|
|
switch (id) {
|
|
case 0x156D:
|
|
case 0x156B:
|
|
return FR;
|
|
case 0x157E:
|
|
return WR;
|
|
case 0x1578:
|
|
case 0x1576:
|
|
case 0x15D3:
|
|
case 0x15DA:
|
|
return AR;
|
|
case 0x15C0:
|
|
return AR_LP;
|
|
case 0x15E7:
|
|
case 0x15EA:
|
|
return TR;
|
|
default:
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Finds optional multi controller (MC) entry from controller DROM.
|
|
* Returns TRUE if the controller did not have MC entry or the
|
|
* controller and image MC entries match. In any other case FALSE is
|
|
* returned and error is set accordingly.
|
|
*/
|
|
static gboolean
|
|
compare_device_mc (const FuThunderboltFwObject *controller,
|
|
const FuThunderboltFwObject *image,
|
|
GError **error)
|
|
{
|
|
FuThunderboltFwLocation image_mc_loc = { .section = DROM_SECTION, .description = "Multi Controller" };
|
|
FuThunderboltFwLocation controller_mc_loc = image_mc_loc;
|
|
g_autoptr(GByteArray) controller_mc = NULL;
|
|
g_autoptr(GByteArray) image_mc = NULL;
|
|
|
|
if (!read_drom_entry_location (controller, DROM_ENTRY_MC,
|
|
&controller_mc_loc, error))
|
|
return FALSE;
|
|
|
|
/* it is fine if the controller does not have MC entry */
|
|
if (controller_mc_loc.offset == 0)
|
|
return TRUE;
|
|
|
|
if (!read_drom_entry_location (image, DROM_ENTRY_MC, &image_mc_loc, error))
|
|
return FALSE;
|
|
|
|
if (image_mc_loc.offset == 0) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE,
|
|
"firmware does not have multi controller entry");
|
|
return FALSE;
|
|
}
|
|
if (controller_mc_loc.len != image_mc_loc.len) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE,
|
|
"firmware multi controller entry length mismatch");
|
|
return FALSE;
|
|
}
|
|
|
|
controller_mc = read_location (&controller_mc_loc, controller, error);
|
|
if (controller_mc == NULL)
|
|
return FALSE;
|
|
image_mc = read_location (&image_mc_loc, image, error);
|
|
if (image_mc == NULL)
|
|
return FALSE;
|
|
|
|
if (memcmp (controller_mc->data, image_mc->data, controller_mc->len) != 0) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE,
|
|
"firmware multi controller entry mismatch");
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static const FuThunderboltFwLocation *
|
|
get_device_locations (guint16 id, const FuThunderboltFwObject *controller,
|
|
const FuThunderboltFwObject *image, GError **error)
|
|
{
|
|
static const FuThunderboltFwLocation AR[] = {
|
|
{ .offset = 0x45, .len = 1, .description = "Flash Size", .mask = 0x07 },
|
|
{ .offset = 0x124, .len = 1, .section = ARC_PARAMS_SECTION, .description = "X of N" },
|
|
{ 0 }
|
|
};
|
|
|
|
static const FuThunderboltFwLocation TR[] = {
|
|
{ .offset = 0x45, .len = 1, .description = "Flash Size", .mask = 0x07 },
|
|
{ 0 }
|
|
};
|
|
|
|
switch (id) {
|
|
case 0x1578:
|
|
case 0x1576:
|
|
case 0x15D3:
|
|
case 0x15DA:
|
|
case 0x15C0:
|
|
return AR;
|
|
case 0x15E7:
|
|
case 0x15EA:
|
|
case 0x15EF:
|
|
/* if the controller has multi controller entry need to
|
|
* compare it against the image first. */
|
|
if (!compare_device_mc (controller, image, error))
|
|
return NULL;
|
|
return TR;
|
|
default:
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Compares the given locations, assuming locations is an array.
|
|
* Returns FALSE and sets error upon failure.
|
|
* locations points to the end of the array (the empty entry) upon
|
|
* successful return.
|
|
*/
|
|
static gboolean
|
|
compare_locations (const FuThunderboltFwLocation **locations,
|
|
const FuThunderboltFwObject *controller,
|
|
const FuThunderboltFwObject *image,
|
|
GError **error)
|
|
{
|
|
gboolean result;
|
|
do {
|
|
if (!compare (*locations, controller, image, &result, error))
|
|
return FALSE;
|
|
if (!result) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE,
|
|
"FW image image not compatible to this controller (%s)",
|
|
(*locations)->description);
|
|
return FALSE;
|
|
}
|
|
} while ((++(*locations))->offset != 0);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
compare_pd_existence (guint16 id,
|
|
const FuThunderboltFwObject *controller,
|
|
const FuThunderboltFwObject *image,
|
|
GError **error)
|
|
{
|
|
const FuThunderboltFwLocation pd_pointer_loc = { .offset = 0x10C, .len = 4, .section = ARC_PARAMS_SECTION, .description = "PD pointer" };
|
|
gboolean controller_has_pd;
|
|
gboolean image_has_pd;
|
|
guint32 pd_pointer;
|
|
|
|
if (controller->sections[ARC_PARAMS_SECTION] == 0)
|
|
return TRUE;
|
|
|
|
if (!read_uint32 (&pd_pointer_loc, controller, &pd_pointer, error))
|
|
return FALSE;
|
|
controller_has_pd = valid_pd_pointer (pd_pointer);
|
|
|
|
if (!read_uint32 (&pd_pointer_loc, image, &pd_pointer, error))
|
|
return FALSE;
|
|
image_has_pd = valid_pd_pointer (pd_pointer);
|
|
|
|
if (controller_has_pd != image_has_pd) {
|
|
g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE,
|
|
"PD section mismatch");
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
FuPluginValidation
|
|
fu_thunderbolt_image_validate (GBytes *controller_fw,
|
|
GBytes *blob_fw,
|
|
GError **error)
|
|
{
|
|
gboolean is_host;
|
|
guint16 device_id;
|
|
gboolean compare_result;
|
|
const FuThunderboltHwInfo *hw_info;
|
|
const FuThunderboltHwInfo unknown = { 0 };
|
|
const FuThunderboltFwLocation *locations;
|
|
|
|
gsize fw_size;
|
|
const guint8 *fw_data = g_bytes_get_data (controller_fw, &fw_size);
|
|
|
|
gsize blob_size;
|
|
const guint8 *blob_data = g_bytes_get_data (blob_fw, &blob_size);
|
|
|
|
guint32 controller_sections[SECTION_COUNT] = { [DIGITAL_SECTION] = 0 };
|
|
guint32 image_sections [SECTION_COUNT] = { 0 };
|
|
|
|
const FuThunderboltFwObject controller = { fw_data, fw_size, controller_sections };
|
|
const FuThunderboltFwObject image = { blob_data, blob_size, image_sections };
|
|
|
|
const FuThunderboltFwLocation is_host_loc = { .offset = 0x10, .len = 1, .mask = 1 << 1, .description = "host flag" };
|
|
const FuThunderboltFwLocation device_id_loc = { .offset = 0x5, .len = 2, .description = "devID" };
|
|
|
|
image_sections[DIGITAL_SECTION] = read_farb_pointer (&image, error);
|
|
if (image_sections[DIGITAL_SECTION] == 0)
|
|
return VALIDATION_FAILED;
|
|
|
|
if (!read_bool (&is_host_loc, &controller, &is_host, error))
|
|
return VALIDATION_FAILED;
|
|
|
|
if (!read_uint16 (&device_id_loc, &controller, &device_id, error))
|
|
return VALIDATION_FAILED;
|
|
|
|
hw_info = get_hw_info (device_id);
|
|
if (hw_info == NULL) {
|
|
if (is_host) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED,
|
|
"Unknown controller");
|
|
return VALIDATION_FAILED;
|
|
}
|
|
hw_info = &unknown;
|
|
}
|
|
|
|
if (!compare (&is_host_loc, &controller, &image, &compare_result, error))
|
|
return VALIDATION_FAILED;
|
|
if (!compare_result) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE,
|
|
"The FW image file is for a %s controller",
|
|
is_host ? "device" : "host");
|
|
return VALIDATION_FAILED;
|
|
}
|
|
|
|
if (!compare (&device_id_loc, &controller, &image, &compare_result, error))
|
|
return VALIDATION_FAILED;
|
|
if (!compare_result) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE,
|
|
"The FW image file is for a different HW type");
|
|
return VALIDATION_FAILED;
|
|
}
|
|
|
|
if (!read_sections (&controller, is_host, hw_info->gen, error))
|
|
return VALIDATION_FAILED;
|
|
if (missing_needed_drom (&controller, is_host, hw_info->gen)) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR, FWUPD_ERROR_READ,
|
|
"Can't find needed FW sections in the controller");
|
|
return VALIDATION_FAILED;
|
|
}
|
|
|
|
if (!read_sections (&image, is_host, hw_info->gen, error))
|
|
return VALIDATION_FAILED;
|
|
if (missing_needed_drom (&image, is_host, hw_info->gen)) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE,
|
|
"Can't find needed FW sections in the FW image file");
|
|
return VALIDATION_FAILED;
|
|
}
|
|
|
|
if (controller.sections[DROM_SECTION] != 0) {
|
|
const FuThunderboltFwLocation drom_locations[] = {
|
|
{ .offset = 0x10, .len = 2, .section = DROM_SECTION, .description = "vendor ID" },
|
|
{ .offset = 0x12, .len = 2, .section = DROM_SECTION, .description = "model ID" },
|
|
{ 0 }
|
|
};
|
|
locations = drom_locations;
|
|
if (!compare_locations (&locations, &controller, &image, error))
|
|
return VALIDATION_FAILED;
|
|
}
|
|
|
|
if (!compare_pd_existence (hw_info->id, &controller, &image, error))
|
|
return VALIDATION_FAILED;
|
|
|
|
/*
|
|
* 0 is for the unknown device case, for being future-compatible with
|
|
* new devices; so we can't know which locations to check besides the
|
|
* vendor and model IDs that were validated already, but those should be
|
|
* good enough validation.
|
|
*/
|
|
if (hw_info->id == 0)
|
|
return UNKNOWN_DEVICE;
|
|
|
|
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,
|
|
&image, error);
|
|
if (locations == NULL) {
|
|
/* error is set already by the above */
|
|
return VALIDATION_FAILED;
|
|
}
|
|
}
|
|
|
|
if (!compare_locations (&locations, &controller, &image, error))
|
|
return VALIDATION_FAILED;
|
|
|
|
if (is_host && hw_info->ports == 2) {
|
|
locations++;
|
|
if (!compare_locations (&locations, &controller, &image, error))
|
|
return VALIDATION_FAILED;
|
|
}
|
|
|
|
return VALIDATION_PASSED;
|
|
}
|
|
|
|
gboolean
|
|
fu_thunderbolt_image_controller_is_native (GBytes *controller_fw,
|
|
gboolean *is_native,
|
|
GError **error)
|
|
{
|
|
guint32 controller_sections[SECTION_COUNT] = { [DIGITAL_SECTION] = 0 };
|
|
gsize fw_size;
|
|
const guint8 *fw_data = g_bytes_get_data (controller_fw, &fw_size);
|
|
const FuThunderboltFwObject controller = { fw_data, fw_size, controller_sections };
|
|
const FuThunderboltFwLocation location = {
|
|
.offset = FU_TBT_OFFSET_NATIVE,
|
|
.len = 1,
|
|
.description = "Native",
|
|
.mask = 0x20 };
|
|
return read_bool (&location, &controller, is_native, error);
|
|
}
|