From 16b463334e0119374a5bd3e31e8dc6432221072d Mon Sep 17 00:00:00 2001 From: Richard Hughes Date: Mon, 25 Jul 2022 12:13:07 +0100 Subject: [PATCH] Export the generic Intel Thunderbolt firmware format This is being used for other products, e.g. USB4 docks. If non-Intel firmware is being used (e.g. ASMedia) then the explicit calls to `fu_intel_thunderbolt_nvm_new()` can be changed to something like `fu_firmware_new_from_gtypes()` with all the formats listed. --- contrib/ci/oss-fuzz.py | 1 + .../fu-intel-thunderbolt-firmware.c | 129 +++ .../fu-intel-thunderbolt-firmware.h | 26 + libfwupdplugin/fu-intel-thunderbolt-nvm.c | 967 ++++++++++++++++++ libfwupdplugin/fu-intel-thunderbolt-nvm.h | 41 + libfwupdplugin/fu-self-test.c | 3 + libfwupdplugin/fwupdplugin.h | 2 + libfwupdplugin/fwupdplugin.map | 16 + libfwupdplugin/meson.build | 4 + libfwupdplugin/tests/intel-thunderbolt.bin | Bin 0 -> 624 bytes .../tests/intel-thunderbolt.builder.xml | 11 + plugins/thunderbolt/fu-plugin-thunderbolt.c | 4 - plugins/thunderbolt/fu-self-test.c | 92 +- .../thunderbolt/fu-thunderbolt-controller.c | 23 +- plugins/thunderbolt/fu-thunderbolt-device.c | 87 +- .../fu-thunderbolt-firmware-update.c | 114 --- .../fu-thunderbolt-firmware-update.h | 21 - plugins/thunderbolt/fu-thunderbolt-firmware.c | 569 ----------- plugins/thunderbolt/fu-thunderbolt-firmware.h | 77 -- plugins/thunderbolt/meson.build | 4 - src/fu-engine.c | 3 + 21 files changed, 1247 insertions(+), 947 deletions(-) create mode 100644 libfwupdplugin/fu-intel-thunderbolt-firmware.c create mode 100644 libfwupdplugin/fu-intel-thunderbolt-firmware.h create mode 100644 libfwupdplugin/fu-intel-thunderbolt-nvm.c create mode 100644 libfwupdplugin/fu-intel-thunderbolt-nvm.h create mode 100644 libfwupdplugin/tests/intel-thunderbolt.bin create mode 100644 libfwupdplugin/tests/intel-thunderbolt.builder.xml delete mode 100644 plugins/thunderbolt/fu-thunderbolt-firmware-update.c delete mode 100644 plugins/thunderbolt/fu-thunderbolt-firmware-update.h delete mode 100644 plugins/thunderbolt/fu-thunderbolt-firmware.c delete mode 100644 plugins/thunderbolt/fu-thunderbolt-firmware.h diff --git a/contrib/ci/oss-fuzz.py b/contrib/ci/oss-fuzz.py index 34f307acb..506481b8b 100755 --- a/contrib/ci/oss-fuzz.py +++ b/contrib/ci/oss-fuzz.py @@ -352,6 +352,7 @@ def _build(bld: Builder) -> None: Fuzzer("fmap"), Fuzzer("ihex"), Fuzzer("srec"), + Fuzzer("intel-thunderbolt"), Fuzzer("ifwi-cpd"), Fuzzer("ifwi-fpt"), Fuzzer("oprom"), diff --git a/libfwupdplugin/fu-intel-thunderbolt-firmware.c b/libfwupdplugin/fu-intel-thunderbolt-firmware.c new file mode 100644 index 000000000..a9d920f49 --- /dev/null +++ b/libfwupdplugin/fu-intel-thunderbolt-firmware.c @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2021 Dell Inc. + * Copyright (C) 2020 Richard Hughes + * Copyright (C) 2020 Mario Limonciello + * Copyright (C) 2017 Intel Corporation. + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#define G_LOG_DOMAIN "FuFirmware" + +#include "config.h" + +#include "fu-byte-array.h" +#include "fu-intel-thunderbolt-firmware.h" +#include "fu-mem.h" + +/** + * FuIntelThunderboltFirmware: + * + * The Non-Volatile-Memory file-format specification. This is what you would find as the update + * payload. + * + * See also: [class@FuFirmware] + */ + +G_DEFINE_TYPE(FuIntelThunderboltFirmware, + fu_intel_thunderbolt_firmware, + FU_TYPE_INTEL_THUNDERBOLT_NVM) + +static gboolean +fu_intel_thunderbolt_nvm_valid_farb_pointer(guint32 pointer) +{ + return pointer != 0 && pointer != 0xFFFFFF; +} + +static gboolean +fu_intel_thunderbolt_firmware_parse(FuFirmware *firmware, + GBytes *fw, + gsize offset, + FwupdInstallFlags flags, + GError **error) +{ + const guint32 farb_offsets[] = {0x0, 0x1000}; + gboolean valid = FALSE; + guint32 farb_pointer = 0x0; + + /* get header offset */ + for (guint i = 0; i < G_N_ELEMENTS(farb_offsets); i++) { + if (!fu_memread_uint24_safe(g_bytes_get_data(fw, NULL), + g_bytes_get_size(fw), + offset + farb_offsets[i], + &farb_pointer, + G_LITTLE_ENDIAN, + error)) + return FALSE; + if (fu_intel_thunderbolt_nvm_valid_farb_pointer(farb_pointer)) { + valid = TRUE; + break; + } + } + if (!valid) { + g_set_error_literal(error, + FWUPD_ERROR, + FWUPD_ERROR_INVALID_FILE, + "no valid farb pointer found"); + return FALSE; + } + g_debug("detected digital section begins at 0x%x", farb_pointer); + fu_firmware_set_offset(firmware, farb_pointer); + + /* FuIntelThunderboltNvm->parse */ + return FU_FIRMWARE_CLASS(fu_intel_thunderbolt_firmware_parent_class) + ->parse(firmware, fw, offset + farb_pointer, flags, error); +} + +static GBytes * +fu_intel_thunderbolt_firmware_write(FuFirmware *firmware, GError **error) +{ + g_autoptr(GByteArray) buf = g_byte_array_new(); + g_autoptr(GBytes) blob = NULL; + + /* sanity check */ + if (fu_firmware_get_offset(firmware) < 0x08) { + g_set_error_literal(error, + FWUPD_ERROR, + FWUPD_ERROR_INVALID_FILE, + "not valid offset"); + return NULL; + } + + /* offset */ + fu_byte_array_append_uint32(buf, fu_firmware_get_offset(firmware), G_LITTLE_ENDIAN); + fu_byte_array_set_size(buf, fu_firmware_get_offset(firmware), 0x00); + + /* FuIntelThunderboltNvm->write */ + blob = + FU_FIRMWARE_CLASS(fu_intel_thunderbolt_firmware_parent_class)->write(firmware, error); + fu_byte_array_append_bytes(buf, blob); + + /* success */ + return g_byte_array_free_to_bytes(g_steal_pointer(&buf)); +} + +static void +fu_intel_thunderbolt_firmware_init(FuIntelThunderboltFirmware *self) +{ +} + +static void +fu_intel_thunderbolt_firmware_class_init(FuIntelThunderboltFirmwareClass *klass) +{ + FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); + klass_firmware->parse = fu_intel_thunderbolt_firmware_parse; + klass_firmware->write = fu_intel_thunderbolt_firmware_write; +} + +/** + * fu_intel_thunderbolt_firmware_new: + * + * Creates a new #FuFirmware of Intel NVM format + * + * Since: 1.8.5 + **/ +FuFirmware * +fu_intel_thunderbolt_firmware_new(void) +{ + return FU_FIRMWARE(g_object_new(FU_TYPE_INTEL_THUNDERBOLT_FIRMWARE, NULL)); +} diff --git a/libfwupdplugin/fu-intel-thunderbolt-firmware.h b/libfwupdplugin/fu-intel-thunderbolt-firmware.h new file mode 100644 index 000000000..f01d19efa --- /dev/null +++ b/libfwupdplugin/fu-intel-thunderbolt-firmware.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2021 Dell Inc. + * Copyright (C) 2020 Richard Hughes + * Copyright (C) 2020 Mario Limonciello + * Copyright (C) 2017 Intel Corporation. + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include "fu-intel-thunderbolt-nvm.h" + +#define FU_TYPE_INTEL_THUNDERBOLT_FIRMWARE (fu_intel_thunderbolt_firmware_get_type()) +G_DECLARE_DERIVABLE_TYPE(FuIntelThunderboltFirmware, + fu_intel_thunderbolt_firmware, + FU, + INTEL_THUNDERBOLT_FIRMWARE, + FuIntelThunderboltNvm) + +struct _FuIntelThunderboltFirmwareClass { + FuIntelThunderboltNvmClass parent_class; +}; + +FuFirmware * +fu_intel_thunderbolt_firmware_new(void); diff --git a/libfwupdplugin/fu-intel-thunderbolt-nvm.c b/libfwupdplugin/fu-intel-thunderbolt-nvm.c new file mode 100644 index 000000000..aca0304d3 --- /dev/null +++ b/libfwupdplugin/fu-intel-thunderbolt-nvm.c @@ -0,0 +1,967 @@ +/* + * Copyright (C) 2021 Dell Inc. + * Copyright (C) 2020 Richard Hughes + * Copyright (C) 2020 Mario Limonciello + * Copyright (C) 2017 Intel Corporation. + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#define G_LOG_DOMAIN "FuFirmware" + +#include "config.h" + +#include "fu-byte-array.h" +#include "fu-bytes.h" +#include "fu-intel-thunderbolt-nvm.h" +#include "fu-mem.h" +#include "fu-string.h" +#include "fu-version-common.h" + +/** + * FuIntelThunderboltNvm: + * + * The Non-Volatile-Memory device specification. This is what you would find on the device SPI chip. + * + * See also: [class@FuFirmware] + */ + +typedef enum { + FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL, + FU_INTEL_THUNDERBOLT_NVM_SECTION_DROM, + FU_INTEL_THUNDERBOLT_NVM_SECTION_ARC_PARAMS, + FU_INTEL_THUNDERBOLT_NVM_SECTION_DRAM_UCODE, + FU_INTEL_THUNDERBOLT_NVM_SECTION_LAST +} FuIntelThunderboltNvmSection; + +typedef enum { + FU_INTEL_THUNDERBOLT_NVM_FAMILY_UNKNOWN, + FU_INTEL_THUNDERBOLT_NVM_FAMILY_FR, + FU_INTEL_THUNDERBOLT_NVM_FAMILY_WR, + FU_INTEL_THUNDERBOLT_NVM_FAMILY_AR, + FU_INTEL_THUNDERBOLT_NVM_FAMILY_AR_C, + FU_INTEL_THUNDERBOLT_NVM_FAMILY_TR, + FU_INTEL_THUNDERBOLT_NVM_FAMILY_BB, + FU_INTEL_THUNDERBOLT_NVM_FAMILY_MR, + FU_INTEL_THUNDERBOLT_NVM_FAMILY_LAST, +} FuIntelThunderboltNvmFamily; + +typedef struct { + guint32 sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_LAST]; + FuIntelThunderboltNvmFamily family; + gboolean is_host; + gboolean is_native; + gboolean has_pd; + guint16 vendor_id; + guint16 device_id; + guint16 model_id; + guint gen; + guint ports; + guint8 flash_size; +} FuIntelThunderboltNvmPrivate; + +G_DEFINE_TYPE_WITH_PRIVATE(FuIntelThunderboltNvm, fu_intel_thunderbolt_nvm, FU_TYPE_FIRMWARE) +#define GET_PRIVATE(o) (fu_intel_thunderbolt_nvm_get_instance_private(o)) + +#define FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_AVAILABLE_SECTIONS 0x0002 +#define FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_UCODE 0x0003 +#define FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_DEVICE_ID 0x0005 +#define FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_VERSION 0x0009 +#define FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_FLAGS_HOST 0x0010 +#define FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_FLASH_SIZE 0x0045 +#define FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_ARC_PARAMS 0x0075 +#define FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_FLAGS_IS_NATIVE 0x007B +#define FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_DROM 0x010E + +#define FU_INTEL_THUNDERBOLT_NVM_DROM_OFFSET_VENDOR_ID 0x0010 +#define FU_INTEL_THUNDERBOLT_NVM_DROM_OFFSET_MODEL_ID 0x0012 + +#define FU_INTEL_THUNDERBOLT_NVM_ARC_PARAMS_OFFSET_PD_POINTER 0x010C + +static const gchar * +fu_intel_thunderbolt_nvm_family_to_string(FuIntelThunderboltNvmFamily family) +{ + if (family == FU_INTEL_THUNDERBOLT_NVM_FAMILY_FR) + return "falcon-ridge"; + if (family == FU_INTEL_THUNDERBOLT_NVM_FAMILY_WR) + return "win-ridge"; + if (family == FU_INTEL_THUNDERBOLT_NVM_FAMILY_AR) + return "alpine-ridge"; + if (family == FU_INTEL_THUNDERBOLT_NVM_FAMILY_AR_C) + return "alpine-ridge-c"; + if (family == FU_INTEL_THUNDERBOLT_NVM_FAMILY_TR) + return "titan-ridge"; + if (family == FU_INTEL_THUNDERBOLT_NVM_FAMILY_BB) + return "bb"; + if (family == FU_INTEL_THUNDERBOLT_NVM_FAMILY_MR) + return "maple-ridge"; + return "unknown"; +} + +static FuIntelThunderboltNvmFamily +fu_intel_thunderbolt_nvm_family_from_string(const gchar *family) +{ + if (g_strcmp0(family, "falcon-ridge") == 0) + return FU_INTEL_THUNDERBOLT_NVM_FAMILY_FR; + if (g_strcmp0(family, "win-ridge") == 0) + return FU_INTEL_THUNDERBOLT_NVM_FAMILY_WR; + if (g_strcmp0(family, "alpine-ridge") == 0) + return FU_INTEL_THUNDERBOLT_NVM_FAMILY_AR; + if (g_strcmp0(family, "alpine-ridge-c") == 0) + return FU_INTEL_THUNDERBOLT_NVM_FAMILY_AR_C; + if (g_strcmp0(family, "titan-ridge") == 0) + return FU_INTEL_THUNDERBOLT_NVM_FAMILY_TR; + if (g_strcmp0(family, "bb") == 0) + return FU_INTEL_THUNDERBOLT_NVM_FAMILY_BB; + if (g_strcmp0(family, "maple-ridge") == 0) + return FU_INTEL_THUNDERBOLT_NVM_FAMILY_MR; + return FU_INTEL_THUNDERBOLT_NVM_FAMILY_UNKNOWN; +} + +static const gchar * +fu_intel_thunderbolt_nvm_section_to_string(FuIntelThunderboltNvmSection section) +{ + if (section == FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL) + return "digital"; + if (section == FU_INTEL_THUNDERBOLT_NVM_SECTION_DROM) + return "drom"; + if (section == FU_INTEL_THUNDERBOLT_NVM_SECTION_ARC_PARAMS) + return "arc-params"; + if (section == FU_INTEL_THUNDERBOLT_NVM_SECTION_DRAM_UCODE) + return "dram-ucode"; + return "unknown"; +} + +/** + * fu_intel_thunderbolt_nvm_get_vendor_id: + * @self: a #FuFirmware + * + * Gets the vendor ID. + * + * Returns: an integer, or 0x0 for unset + * + * Since: 1.8.5 + **/ +guint16 +fu_intel_thunderbolt_nvm_get_vendor_id(FuIntelThunderboltNvm *self) +{ + FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self); + g_return_val_if_fail(FU_IS_INTEL_THUNDERBOLT_NVM(self), G_MAXUINT16); + return priv->vendor_id; +} + +/** + * fu_intel_thunderbolt_nvm_get_device_id: + * @self: a #FuFirmware + * + * Gets the device ID. + * + * Returns: an integer, or 0x0 for unset + * + * Since: 1.8.5 + **/ +guint16 +fu_intel_thunderbolt_nvm_get_device_id(FuIntelThunderboltNvm *self) +{ + FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self); + return priv->device_id; +} + +/** + * fu_intel_thunderbolt_nvm_is_host: + * @self: a #FuFirmware + * + * Gets if the firmware is designed for a host controller rather than a device. + * + * Returns: %TRUE for controller, %FALSE for device + * + * Since: 1.8.5 + **/ +gboolean +fu_intel_thunderbolt_nvm_is_host(FuIntelThunderboltNvm *self) +{ + FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self); + g_return_val_if_fail(FU_IS_INTEL_THUNDERBOLT_NVM(self), FALSE); + return priv->is_host; +} + +/** + * fu_intel_thunderbolt_nvm_is_native: + * @self: a #FuFirmware + * + * Gets if the device is native, i.e. not in recovery mode. + * + * Returns: %TRUE if set + * + * Since: 1.8.5 + **/ +gboolean +fu_intel_thunderbolt_nvm_is_native(FuIntelThunderboltNvm *self) +{ + FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self); + g_return_val_if_fail(FU_IS_INTEL_THUNDERBOLT_NVM(self), FALSE); + return priv->is_native; +} + +/** + * fu_intel_thunderbolt_nvm_has_pd: + * @self: a #FuFirmware + * + * Gets if the device has power delivery capability. + * + * Returns: %TRUE if set + * + * Since: 1.8.5 + **/ +gboolean +fu_intel_thunderbolt_nvm_has_pd(FuIntelThunderboltNvm *self) +{ + FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self); + g_return_val_if_fail(FU_IS_INTEL_THUNDERBOLT_NVM(self), FALSE); + return priv->has_pd; +} + +/** + * fu_intel_thunderbolt_nvm_get_model_id: + * @self: a #FuFirmware + * + * Gets the model ID. + * + * Returns: an integer, or 0x0 for unset + * + * Since: 1.8.5 + **/ +guint16 +fu_intel_thunderbolt_nvm_get_model_id(FuIntelThunderboltNvm *self) +{ + FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self); + g_return_val_if_fail(FU_IS_INTEL_THUNDERBOLT_NVM(self), 0x0); + return priv->model_id; +} + +/** + * fu_intel_thunderbolt_nvm_get_flash_size: + * @self: a #FuFirmware + * + * Gets the flash size. + * + * NOTE: This does not correspond to a size in bytes, or a power of 2 and is only useful for + * comparison between firmware and device. + * + * Returns: an integer, or 0x0 for unset + * + * Since: 1.8.5 + **/ +guint8 +fu_intel_thunderbolt_nvm_get_flash_size(FuIntelThunderboltNvm *self) +{ + FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self); + g_return_val_if_fail(FU_IS_INTEL_THUNDERBOLT_NVM(self), 0x0); + return priv->flash_size; +} + +static void +fu_intel_thunderbolt_nvm_export(FuFirmware *firmware, + FuFirmwareExportFlags flags, + XbBuilderNode *bn) +{ + FuIntelThunderboltNvm *self = FU_INTEL_THUNDERBOLT_NVM(firmware); + FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self); + fu_xmlb_builder_insert_kx(bn, "vendor_id", priv->vendor_id); + fu_xmlb_builder_insert_kx(bn, "device_id", priv->device_id); + fu_xmlb_builder_insert_kx(bn, "model_id", priv->model_id); + fu_xmlb_builder_insert_kv(bn, + "family", + fu_intel_thunderbolt_nvm_family_to_string(priv->family)); + fu_xmlb_builder_insert_kb(bn, "is_host", priv->is_host); + fu_xmlb_builder_insert_kb(bn, "is_native", priv->is_native); + fu_xmlb_builder_insert_kx(bn, "flash_size", priv->flash_size); + fu_xmlb_builder_insert_kx(bn, "generation", priv->gen); + fu_xmlb_builder_insert_kx(bn, "ports", priv->ports); + fu_xmlb_builder_insert_kb(bn, "has_pd", priv->has_pd); + for (guint i = 0; i < FU_INTEL_THUNDERBOLT_NVM_SECTION_LAST; i++) { + if (priv->sections[i] != 0x0) { + g_autofree gchar *tmp = g_strdup_printf("0x%x", priv->sections[i]); + g_autoptr(XbBuilderNode) bc = + xb_builder_node_insert(bn, + "section", + "type", + fu_intel_thunderbolt_nvm_section_to_string(i), + "offset", + tmp, + NULL); + } + } +} + +static inline gboolean +fu_intel_thunderbolt_nvm_valid_pd_pointer(guint32 pointer) +{ + return pointer != 0 && pointer != 0xFFFFFFFF; +} + +static gboolean +fu_intel_thunderbolt_nvm_read_uint8(FuIntelThunderboltNvm *self, + FuIntelThunderboltNvmSection section, + guint32 offset, + guint8 *value, + GError **error) +{ + FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self); + g_autoptr(GBytes) fw = NULL; + + /* get blob and read */ + fw = fu_firmware_get_bytes(FU_FIRMWARE(self), error); + if (fw == NULL) + return FALSE; + return fu_memread_uint8_safe(g_bytes_get_data(fw, NULL), + g_bytes_get_size(fw), + priv->sections[section] + offset, + value, + error); +} + +static gboolean +fu_intel_thunderbolt_nvm_read_uint16(FuIntelThunderboltNvm *self, + FuIntelThunderboltNvmSection section, + guint32 offset, + guint16 *value, + GError **error) +{ + FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self); + g_autoptr(GBytes) fw = NULL; + + /* get blob and read */ + fw = fu_firmware_get_bytes(FU_FIRMWARE(self), error); + if (fw == NULL) + return FALSE; + return fu_memread_uint16_safe(g_bytes_get_data(fw, NULL), + g_bytes_get_size(fw), + priv->sections[section] + offset, + value, + G_LITTLE_ENDIAN, + error); +} + +static gboolean +fu_intel_thunderbolt_nvm_read_uint32(FuIntelThunderboltNvm *self, + FuIntelThunderboltNvmSection section, + guint32 offset, + guint32 *value, + GError **error) +{ + FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self); + g_autoptr(GBytes) fw = NULL; + + /* get blob and read */ + fw = fu_firmware_get_bytes(FU_FIRMWARE(self), error); + if (fw == NULL) + return FALSE; + return fu_memread_uint32_safe(g_bytes_get_data(fw, NULL), + g_bytes_get_size(fw), + priv->sections[section] + offset, + value, + G_LITTLE_ENDIAN, + error); +} + +/* + * 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 +fu_intel_thunderbolt_nvm_read_ucode_section_len(FuIntelThunderboltNvm *self, + guint32 offset, + guint16 *value, + GError **error) +{ + if (!fu_intel_thunderbolt_nvm_read_uint16(self, + FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL, + offset, + value, + error)) { + g_prefix_error(error, "failed to read ucode section len: "); + return FALSE; + } + *value *= sizeof(guint32); + *value += sizeof(guint16); + return TRUE; +} + +/* assumes sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL].offset is already set */ +static gboolean +fu_intel_thunderbolt_nvm_read_sections(FuIntelThunderboltNvm *self, GError **error) +{ + guint32 offset; + FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self); + + if (priv->gen >= 3 || priv->gen == 0) { + if (!fu_intel_thunderbolt_nvm_read_uint32( + self, + FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL, + FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_DROM, + &offset, + error)) + return FALSE; + priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DROM] = + offset + priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL]; + + if (!fu_intel_thunderbolt_nvm_read_uint32( + self, + FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL, + FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_ARC_PARAMS, + &offset, + error)) + return FALSE; + priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_ARC_PARAMS] = + offset + priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL]; + } + + if (priv->is_host && priv->gen > 2) { + /* + * 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 guint8 DRAM_FLAG = 1 << 6; + guint16 ucode_offset; + guint8 available_sections = 0; + + if (!fu_intel_thunderbolt_nvm_read_uint8( + self, + FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL, + FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_AVAILABLE_SECTIONS, + &available_sections, + error)) { + g_prefix_error(error, "failed to read available sections: "); + return FALSE; + } + if (!fu_intel_thunderbolt_nvm_read_uint16( + self, + FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL, + FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_UCODE, + &ucode_offset, + error)) { + g_prefix_error(error, "failed to read ucode offset: "); + return FALSE; + } + offset = ucode_offset; + if ((available_sections & DRAM_FLAG) == 0) { + g_set_error_literal(error, + FWUPD_ERROR, + FWUPD_ERROR_INVALID_FILE, + "cannot find needed FW sections in the FW image file"); + return FALSE; + } + + for (guint8 i = 1; i < DRAM_FLAG; i <<= 1) { + if (available_sections & i) { + if (!fu_intel_thunderbolt_nvm_read_ucode_section_len(self, + offset, + &ucode_offset, + error)) + return FALSE; + offset += ucode_offset; + } + } + priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DRAM_UCODE] = + offset + priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL]; + } + + return TRUE; +} + +static gboolean +fu_intel_thunderbolt_nvm_missing_needed_drom(FuIntelThunderboltNvm *self) +{ + FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self); + if (priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DROM] != 0) + return FALSE; + if (priv->is_host && priv->gen < 3) + return FALSE; + return TRUE; +} + +static gboolean +fu_intel_thunderbolt_nvm_parse(FuFirmware *firmware, + GBytes *fw, + gsize offset, + FwupdInstallFlags flags, + GError **error) +{ + FuIntelThunderboltNvm *self = FU_INTEL_THUNDERBOLT_NVM(firmware); + FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self); + guint8 tmp = 0; + guint16 version_raw = 0; + struct { + guint16 device_id; + guint gen; + FuIntelThunderboltNvmFamily family; + guint ports; + } hw_info_arr[] = {{0x156D, 2, FU_INTEL_THUNDERBOLT_NVM_FAMILY_FR, 2}, /* FR 4C */ + {0x156B, 2, FU_INTEL_THUNDERBOLT_NVM_FAMILY_FR, 1}, /* FR 2C */ + {0x157E, 2, FU_INTEL_THUNDERBOLT_NVM_FAMILY_WR, 1}, /* WR */ + {0x1578, 3, FU_INTEL_THUNDERBOLT_NVM_FAMILY_AR, 2}, /* AR 4C */ + {0x1576, 3, FU_INTEL_THUNDERBOLT_NVM_FAMILY_AR, 1}, /* AR 2C */ + {0x15C0, 3, FU_INTEL_THUNDERBOLT_NVM_FAMILY_AR, 1}, /* AR LP */ + {0x15D3, 3, FU_INTEL_THUNDERBOLT_NVM_FAMILY_AR_C, 2}, /* AR-C 4C */ + {0x15DA, 3, FU_INTEL_THUNDERBOLT_NVM_FAMILY_AR_C, 1}, /* AR-C 2C */ + {0x15E7, 3, FU_INTEL_THUNDERBOLT_NVM_FAMILY_TR, 1}, /* TR 2C */ + {0x15EA, 3, FU_INTEL_THUNDERBOLT_NVM_FAMILY_TR, 2}, /* TR 4C */ + {0x15EF, 3, FU_INTEL_THUNDERBOLT_NVM_FAMILY_TR, 2}, /* TR 4C device */ + {0x15EE, 3, FU_INTEL_THUNDERBOLT_NVM_FAMILY_BB, 0}, /* BB device */ + /* Maple ridge devices + * NOTE: These are expected to be flashed via UEFI capsules *not* + * Thunderbolt plugin Flashing via fwupd will require matching kernel + * work. They're left here only for parsing the binaries + */ + {0x1136, 4, FU_INTEL_THUNDERBOLT_NVM_FAMILY_MR, 2}, + {0x1137, 4, FU_INTEL_THUNDERBOLT_NVM_FAMILY_MR, 2}, + {0}}; + g_autofree gchar *version = NULL; + g_autoptr(FuFirmware) img_payload = NULL; + g_autoptr(GBytes) fw_payload = NULL; + + /* add this straight away */ + priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL] = offset; + fu_firmware_set_bytes(firmware, fw); + + /* is native */ + if (!fu_intel_thunderbolt_nvm_read_uint8( + self, + FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL, + FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_FLAGS_IS_NATIVE, + &tmp, + error)) { + g_prefix_error(error, "failed to read native: "); + return FALSE; + } + priv->is_native = tmp & 0x20; + + /* we're only reading the first chunk */ + if (g_bytes_get_size(fw) == 0x80) + return TRUE; + + /* host or device */ + if (!fu_intel_thunderbolt_nvm_read_uint8(self, + FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL, + FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_FLAGS_HOST, + &tmp, + error)) { + g_prefix_error(error, "failed to read is-host: "); + return FALSE; + } + priv->is_host = tmp & (1 << 1); + + /* device ID */ + if (!fu_intel_thunderbolt_nvm_read_uint16(self, + FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL, + FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_DEVICE_ID, + &priv->device_id, + error)) { + g_prefix_error(error, "failed to read device-id: "); + return FALSE; + } + + /* this is best-effort */ + for (guint i = 0; hw_info_arr[i].device_id != 0; i++) { + if (hw_info_arr[i].device_id == priv->device_id) { + priv->family = hw_info_arr[i].family; + priv->gen = hw_info_arr[i].gen; + priv->ports = hw_info_arr[i].ports; + break; + } + } + if (priv->ports == 0 && priv->is_host) { + g_set_error(error, + FWUPD_ERROR, + FWUPD_ERROR_NOT_SUPPORTED, + "unknown controller: %x", + priv->device_id); + return FALSE; + } + + /* read sections from file */ + if (!fu_intel_thunderbolt_nvm_read_sections(self, error)) + return FALSE; + if (fu_intel_thunderbolt_nvm_missing_needed_drom(self)) { + g_set_error_literal(error, + FWUPD_ERROR, + FWUPD_ERROR_READ, + "cannot find required drom section"); + return FALSE; + } + + /* vendor:model */ + if (priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DROM] != 0x0) { + if (!fu_intel_thunderbolt_nvm_read_uint16( + self, + FU_INTEL_THUNDERBOLT_NVM_SECTION_DROM, + FU_INTEL_THUNDERBOLT_NVM_DROM_OFFSET_VENDOR_ID, + &priv->vendor_id, + error)) { + g_prefix_error(error, "failed to read vendor-id: "); + return FALSE; + } + if (!fu_intel_thunderbolt_nvm_read_uint16( + self, + FU_INTEL_THUNDERBOLT_NVM_SECTION_DROM, + FU_INTEL_THUNDERBOLT_NVM_DROM_OFFSET_MODEL_ID, + &priv->model_id, + error)) { + g_prefix_error(error, "failed to read model-id: "); + return FALSE; + } + } + + /* has PD */ + if (priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_ARC_PARAMS] != 0x0) { + guint32 pd_pointer = 0x0; + if (!fu_intel_thunderbolt_nvm_read_uint32( + self, + FU_INTEL_THUNDERBOLT_NVM_SECTION_ARC_PARAMS, + FU_INTEL_THUNDERBOLT_NVM_ARC_PARAMS_OFFSET_PD_POINTER, + &pd_pointer, + error)) { + g_prefix_error(error, "failed to read pd-pointer: "); + return FALSE; + } + priv->has_pd = fu_intel_thunderbolt_nvm_valid_pd_pointer(pd_pointer); + } + + /* versions */ + switch (priv->family) { + case FU_INTEL_THUNDERBOLT_NVM_FAMILY_TR: // FIXME and GR + if (!fu_intel_thunderbolt_nvm_read_uint16( + self, + FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL, + FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_VERSION, + &version_raw, + error)) { + g_prefix_error(error, "failed to read version: "); + return FALSE; + } + fu_firmware_set_version_raw(FU_FIRMWARE(self), version_raw); + version = fu_version_from_uint16(version_raw, FWUPD_VERSION_FORMAT_BCD); + fu_firmware_set_version(FU_FIRMWARE(self), version); + break; + default: + break; + } + + if (priv->is_host) { + switch (priv->family) { + case FU_INTEL_THUNDERBOLT_NVM_FAMILY_AR: + case FU_INTEL_THUNDERBOLT_NVM_FAMILY_AR_C: + case FU_INTEL_THUNDERBOLT_NVM_FAMILY_TR: + /* used for comparison between old and new image, not a raw number */ + if (!fu_intel_thunderbolt_nvm_read_uint8( + self, + FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL, + FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_FLASH_SIZE, + &tmp, + error)) { + g_prefix_error(error, "failed to read flash size: "); + return FALSE; + } + priv->flash_size = tmp & 0x07; + break; + default: + break; + } + } + + /* as as easy-to-grab payload blob */ + if (offset > 0) { + fw_payload = fu_bytes_new_offset(fw, offset, g_bytes_get_size(fw) - offset, error); + if (fw_payload == NULL) + return FALSE; + } else { + fw_payload = g_bytes_ref(fw); + } + img_payload = fu_firmware_new_from_bytes(fw_payload); + fu_firmware_set_id(img_payload, FU_FIRMWARE_ID_PAYLOAD); + fu_firmware_add_image(firmware, img_payload); + + /* success */ + return TRUE; +} + +/* can only write version 3 NVM */ +static GBytes * +fu_intel_thunderbolt_nvm_write(FuFirmware *firmware, GError **error) +{ + FuIntelThunderboltNvm *self = FU_INTEL_THUNDERBOLT_NVM(firmware); + FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self); + guint32 digital_size = 0x120; + guint32 drom_offset = 0 + digital_size; + guint32 drom_size = 0x20; + guint32 arc_param_offset = drom_offset + drom_size; + guint32 arc_param_size = 0x120; + g_autoptr(GByteArray) buf = g_byte_array_new(); + + /* minimal size */ + fu_byte_array_set_size(buf, arc_param_offset + arc_param_size, 0x0); + + /* digital section */ + if (!fu_memwrite_uint8_safe(buf->data, + buf->len, + FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_AVAILABLE_SECTIONS, + 0x0, + error)) + return NULL; + if (!fu_memwrite_uint16_safe(buf->data, + buf->len, + FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_UCODE, + 0x0, + G_LITTLE_ENDIAN, + error)) + return NULL; + if (!fu_memwrite_uint8_safe(buf->data, + buf->len, + FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_FLAGS_IS_NATIVE, + priv->is_native ? 0x20 : 0x0, + error)) + return NULL; + if (!fu_memwrite_uint8_safe(buf->data, + buf->len, + FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_FLAGS_HOST, + priv->is_host ? 0x2 : 0x0, + error)) + return NULL; + if (!fu_memwrite_uint32_safe(buf->data, + buf->len, + FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_DEVICE_ID, + priv->device_id, + G_LITTLE_ENDIAN, + error)) + return NULL; + if (!fu_memwrite_uint16_safe(buf->data, + buf->len, + FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_VERSION, + fu_firmware_get_version_raw(firmware), + G_LITTLE_ENDIAN, + error)) + return NULL; + if (!fu_memwrite_uint8_safe(buf->data, + buf->len, + FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_FLASH_SIZE, + priv->flash_size, + error)) + return NULL; + + /* drom section */ + if (!fu_memwrite_uint32_safe(buf->data, + buf->len, + FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_DROM, + drom_offset, + G_LITTLE_ENDIAN, + error)) + return NULL; + if (!fu_memwrite_uint16_safe(buf->data, + buf->len, + drom_offset + FU_INTEL_THUNDERBOLT_NVM_DROM_OFFSET_VENDOR_ID, + priv->vendor_id, + G_LITTLE_ENDIAN, + error)) + return NULL; + if (!fu_memwrite_uint16_safe(buf->data, + buf->len, + drom_offset + FU_INTEL_THUNDERBOLT_NVM_DROM_OFFSET_MODEL_ID, + priv->model_id, + G_LITTLE_ENDIAN, + error)) + return NULL; + + /* ARC param section */ + if (!fu_memwrite_uint32_safe(buf->data, + buf->len, + FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_ARC_PARAMS, + arc_param_offset, + G_LITTLE_ENDIAN, + error)) + return NULL; + if (!fu_memwrite_uint32_safe(buf->data, + buf->len, + arc_param_offset + + FU_INTEL_THUNDERBOLT_NVM_ARC_PARAMS_OFFSET_PD_POINTER, + priv->has_pd ? 0x1 : 0x0, + G_LITTLE_ENDIAN, + error)) + return NULL; + + /* success */ + return g_byte_array_free_to_bytes(g_steal_pointer(&buf)); +} + +static gboolean +fu_intel_thunderbolt_nvm_check_compatible(FuFirmware *firmware, + FuFirmware *firmware_other, + FwupdInstallFlags flags, + GError **error) +{ + FuIntelThunderboltNvm *self = FU_INTEL_THUNDERBOLT_NVM(firmware); + FuIntelThunderboltNvm *other = FU_INTEL_THUNDERBOLT_NVM(firmware_other); + FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self); + FuIntelThunderboltNvmPrivate *priv_other = GET_PRIVATE(other); + + if (priv->is_host != priv_other->is_host) { + g_set_error(error, + FWUPD_ERROR, + FWUPD_ERROR_INVALID_FILE, + "incorrect firmware mode, got %s, expected %s", + priv->is_host ? "host" : "device", + priv_other->is_host ? "host" : "device"); + return FALSE; + } + if (priv->vendor_id != priv_other->vendor_id) { + g_set_error(error, + FWUPD_ERROR, + FWUPD_ERROR_INVALID_FILE, + "incorrect device vendor, got 0x%04x, expected 0x%04x", + priv->vendor_id, + priv_other->vendor_id); + return FALSE; + } + if (priv->device_id != priv_other->device_id) { + g_set_error(error, + FWUPD_ERROR, + FWUPD_ERROR_INVALID_FILE, + "incorrect device type, got 0x%04x, expected 0x%04x", + priv->device_id, + priv_other->device_id); + return FALSE; + } + if ((flags & FWUPD_INSTALL_FLAG_IGNORE_VID_PID) == 0) { + if (priv->model_id != priv_other->model_id) { + g_set_error(error, + FWUPD_ERROR, + FWUPD_ERROR_INVALID_FILE, + "incorrect device model, got 0x%04x, expected 0x%04x", + priv->model_id, + priv_other->model_id); + return FALSE; + } + /* old firmware has PD but new doesn't (we don't care about other way around) */ + if (priv->has_pd && !priv_other->has_pd) { + g_set_error_literal(error, + FWUPD_ERROR, + FWUPD_ERROR_INVALID_FILE, + "incorrect PD section"); + return FALSE; + } + if (priv->flash_size != priv_other->flash_size) { + g_set_error_literal(error, + FWUPD_ERROR, + FWUPD_ERROR_INVALID_FILE, + "incorrect flash size"); + return FALSE; + } + } + return TRUE; +} + +static gboolean +fu_intel_thunderbolt_nvm_build(FuFirmware *firmware, XbNode *n, GError **error) +{ + FuIntelThunderboltNvm *self = FU_INTEL_THUNDERBOLT_NVM(firmware); + FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self); + const gchar *tmp; + + /* simple properties */ + tmp = xb_node_query_text(n, "vendor_id", NULL); + if (tmp != NULL) { + guint64 val = 0; + if (!fu_strtoull(tmp, &val, 0x0, G_MAXUINT16, error)) + return FALSE; + priv->vendor_id = val; + } + tmp = xb_node_query_text(n, "device_id", NULL); + if (tmp != NULL) { + guint64 val = 0; + if (!fu_strtoull(tmp, &val, 0x0, G_MAXUINT16, error)) + return FALSE; + priv->device_id = val; + } + tmp = xb_node_query_text(n, "model_id", NULL); + if (tmp != NULL) { + guint64 val = 0; + if (!fu_strtoull(tmp, &val, 0x0, G_MAXUINT16, error)) + return FALSE; + priv->model_id = val; + } + tmp = xb_node_query_text(n, "family", NULL); + if (tmp != NULL) { + priv->family = fu_intel_thunderbolt_nvm_family_from_string(tmp); + if (priv->family == FU_INTEL_THUNDERBOLT_NVM_FAMILY_UNKNOWN) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "unknown family: %s", + tmp); + return FALSE; + } + } + tmp = xb_node_query_text(n, "flash_size", NULL); + if (tmp != NULL) { + guint64 val = 0; + if (!fu_strtoull(tmp, &val, 0x0, 0x07, error)) + return FALSE; + priv->flash_size = val; + } + tmp = xb_node_query_text(n, "is_host", NULL); + if (tmp != NULL) { + if (!fu_strtobool(tmp, &priv->is_host, error)) + return FALSE; + } + tmp = xb_node_query_text(n, "is_native", NULL); + if (tmp != NULL) { + if (!fu_strtobool(tmp, &priv->is_native, error)) + return FALSE; + } + + /* success */ + return TRUE; +} + +static void +fu_intel_thunderbolt_nvm_init(FuIntelThunderboltNvm *self) +{ + fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_VID_PID); +} + +static void +fu_intel_thunderbolt_nvm_class_init(FuIntelThunderboltNvmClass *klass) +{ + FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); + klass_firmware->export = fu_intel_thunderbolt_nvm_export; + klass_firmware->parse = fu_intel_thunderbolt_nvm_parse; + klass_firmware->write = fu_intel_thunderbolt_nvm_write; + klass_firmware->build = fu_intel_thunderbolt_nvm_build; + klass_firmware->check_compatible = fu_intel_thunderbolt_nvm_check_compatible; +} + +/** + * fu_intel_thunderbolt_nvm_new: + * + * Creates a new #FuFirmware of Intel NVM format + * + * Since: 1.8.5 + **/ +FuFirmware * +fu_intel_thunderbolt_nvm_new(void) +{ + return FU_FIRMWARE(g_object_new(FU_TYPE_INTEL_THUNDERBOLT_NVM, NULL)); +} diff --git a/libfwupdplugin/fu-intel-thunderbolt-nvm.h b/libfwupdplugin/fu-intel-thunderbolt-nvm.h new file mode 100644 index 000000000..fd5f23b1d --- /dev/null +++ b/libfwupdplugin/fu-intel-thunderbolt-nvm.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2021 Dell Inc. + * Copyright (C) 2020 Richard Hughes + * Copyright (C) 2020 Mario Limonciello + * Copyright (C) 2017 Intel Corporation. + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include "fu-firmware.h" + +#define FU_TYPE_INTEL_THUNDERBOLT_NVM (fu_intel_thunderbolt_nvm_get_type()) +G_DECLARE_DERIVABLE_TYPE(FuIntelThunderboltNvm, + fu_intel_thunderbolt_nvm, + FU, + INTEL_THUNDERBOLT_NVM, + FuFirmware) + +struct _FuIntelThunderboltNvmClass { + FuFirmwareClass parent_class; +}; + +guint16 +fu_intel_thunderbolt_nvm_get_vendor_id(FuIntelThunderboltNvm *self); +guint16 +fu_intel_thunderbolt_nvm_get_device_id(FuIntelThunderboltNvm *self); +guint16 +fu_intel_thunderbolt_nvm_get_model_id(FuIntelThunderboltNvm *self); +gboolean +fu_intel_thunderbolt_nvm_is_host(FuIntelThunderboltNvm *self); +gboolean +fu_intel_thunderbolt_nvm_is_native(FuIntelThunderboltNvm *self); +gboolean +fu_intel_thunderbolt_nvm_has_pd(FuIntelThunderboltNvm *self); +guint8 +fu_intel_thunderbolt_nvm_get_flash_size(FuIntelThunderboltNvm *self); + +FuFirmware * +fu_intel_thunderbolt_nvm_new(void); diff --git a/libfwupdplugin/fu-self-test.c b/libfwupdplugin/fu-self-test.c index 771656df8..3d052e034 100644 --- a/libfwupdplugin/fu-self-test.c +++ b/libfwupdplugin/fu-self-test.c @@ -3249,6 +3249,9 @@ fu_firmware_builder_round_trip_func(void) {FU_TYPE_OPROM_FIRMWARE, "oprom.builder.xml", "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed"}, + {FU_TYPE_INTEL_THUNDERBOLT_NVM, + "intel-thunderbolt.builder.xml", + "e858000646fecb5223b41df57647c005b495749b"}, #ifdef HAVE_CBOR {FU_TYPE_USWID_FIRMWARE, "uswid.builder.xml", diff --git a/libfwupdplugin/fwupdplugin.h b/libfwupdplugin/fwupdplugin.h index 6f8320b8c..9a8074853 100644 --- a/libfwupdplugin/fwupdplugin.h +++ b/libfwupdplugin/fwupdplugin.h @@ -52,6 +52,8 @@ #include #include #include +#include +#include #include #include #include diff --git a/libfwupdplugin/fwupdplugin.map b/libfwupdplugin/fwupdplugin.map index 0b68056ee..f506e1f0e 100644 --- a/libfwupdplugin/fwupdplugin.map +++ b/libfwupdplugin/fwupdplugin.map @@ -1095,3 +1095,19 @@ LIBFWUPDPLUGIN_1.8.4 { fu_security_attr_new; local: *; } LIBFWUPDPLUGIN_1.8.3; + +LIBFWUPDPLUGIN_1.8.5 { + global: + fu_intel_thunderbolt_firmware_get_type; + fu_intel_thunderbolt_firmware_new; + fu_intel_thunderbolt_nvm_get_device_id; + fu_intel_thunderbolt_nvm_get_flash_size; + fu_intel_thunderbolt_nvm_get_model_id; + fu_intel_thunderbolt_nvm_get_type; + fu_intel_thunderbolt_nvm_get_vendor_id; + fu_intel_thunderbolt_nvm_has_pd; + fu_intel_thunderbolt_nvm_is_host; + fu_intel_thunderbolt_nvm_is_native; + fu_intel_thunderbolt_nvm_new; + local: *; +} LIBFWUPDPLUGIN_1.8.4; diff --git a/libfwupdplugin/meson.build b/libfwupdplugin/meson.build index 97b54690c..a0b3b9fa6 100644 --- a/libfwupdplugin/meson.build +++ b/libfwupdplugin/meson.build @@ -36,6 +36,8 @@ fwupdplugin_src = [ 'fu-ifwi-cpd-firmware.c', # fuzzing 'fu-ifwi-fpt-firmware.c', # fuzzing 'fu-oprom-firmware.c', # fuzzing + 'fu-intel-thunderbolt-nvm.c', # fuzzing + 'fu-intel-thunderbolt-firmware.c', # fuzzing 'fu-cfu-common.c', # fuzzing 'fu-cfu-offer.c', # fuzzing 'fu-cfu-payload.c', # fuzzing @@ -128,6 +130,8 @@ fwupdplugin_headers = [ 'fu-ifwi-cpd-firmware.h', 'fu-ifwi-fpt-firmware.h', 'fu-oprom-firmware.h', + 'fu-intel-thunderbolt-firmware.h', + 'fu-intel-thunderbolt-nvm.h', 'fu-cfu-common.h', 'fu-cfu-offer.h', 'fu-cfu-payload.h', diff --git a/libfwupdplugin/tests/intel-thunderbolt.bin b/libfwupdplugin/tests/intel-thunderbolt.bin new file mode 100644 index 0000000000000000000000000000000000000000..356e40c9785687f8b33f1e16742f3a0074c06a8a GIT binary patch literal 624 tcmWe&zyjWj0tFMWNDwK+Or$!PG6zN&Ymm_j1RZvTpd literal 0 HcmV?d00001 diff --git a/libfwupdplugin/tests/intel-thunderbolt.builder.xml b/libfwupdplugin/tests/intel-thunderbolt.builder.xml new file mode 100644 index 000000000..ac8bddb57 --- /dev/null +++ b/libfwupdplugin/tests/intel-thunderbolt.builder.xml @@ -0,0 +1,11 @@ + + 0x10 + 0xd4 + 0x15ef + 0xb070 + titan-ridge + 0x3 + false + false + 0x6000 + diff --git a/plugins/thunderbolt/fu-plugin-thunderbolt.c b/plugins/thunderbolt/fu-plugin-thunderbolt.c index de5a8f66d..9b4f7a2ca 100644 --- a/plugins/thunderbolt/fu-plugin-thunderbolt.c +++ b/plugins/thunderbolt/fu-plugin-thunderbolt.c @@ -10,8 +10,6 @@ #include "fu-thunderbolt-common.h" #include "fu-thunderbolt-controller.h" -#include "fu-thunderbolt-firmware-update.h" -#include "fu-thunderbolt-firmware.h" #include "fu-thunderbolt-retimer.h" /*5 seconds sleep until retimer is available \ @@ -64,8 +62,6 @@ fu_plugin_thunderbolt_init(FuPlugin *plugin) fu_plugin_add_udev_subsystem(plugin, "thunderbolt"); fu_plugin_add_device_gtype(plugin, FU_TYPE_THUNDERBOLT_CONTROLLER); fu_plugin_add_device_gtype(plugin, FU_TYPE_THUNDERBOLT_RETIMER); - fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_THUNDERBOLT_FIRMWARE); - fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_THUNDERBOLT_FIRMWARE_UPDATE); } static gboolean diff --git a/plugins/thunderbolt/fu-self-test.c b/plugins/thunderbolt/fu-self-test.c index b0e12dc7d..cc1ad1696 100644 --- a/plugins/thunderbolt/fu-self-test.c +++ b/plugins/thunderbolt/fu-self-test.c @@ -22,8 +22,6 @@ #include "fu-context-private.h" #include "fu-plugin-private.h" -#include "fu-thunderbolt-firmware-update.h" -#include "fu-thunderbolt-firmware.h" #include "fu-udev-device-private.h" static gchar * @@ -1051,70 +1049,6 @@ test_tree(ThunderboltTest *tt, gconstpointer user_data) g_assert_true(ret); } -static gboolean -_compare_images(FuThunderboltFirmware *firmware_old, - FuThunderboltFirmware *firmware, - GError **error) -{ - if (fu_thunderbolt_firmware_is_host(firmware) != - fu_thunderbolt_firmware_is_host(firmware_old)) { - g_set_error(error, - FWUPD_ERROR, - FWUPD_ERROR_INVALID_FILE, - "incorrect firmware mode, got %s, expected %s", - fu_thunderbolt_firmware_is_host(firmware) ? "host" : "device", - fu_thunderbolt_firmware_is_host(firmware_old) ? "host" : "device"); - return FALSE; - } - if (fu_thunderbolt_firmware_get_vendor_id(firmware) != - fu_thunderbolt_firmware_get_vendor_id(firmware_old)) { - g_set_error(error, - FWUPD_ERROR, - FWUPD_ERROR_INVALID_FILE, - "incorrect device vendor, got 0x%04x, expected 0x%04x", - fu_thunderbolt_firmware_get_vendor_id(firmware), - fu_thunderbolt_firmware_get_vendor_id(firmware_old)); - return FALSE; - } - if (fu_thunderbolt_firmware_get_device_id(firmware) != - fu_thunderbolt_firmware_get_device_id(firmware_old)) { - g_set_error(error, - FWUPD_ERROR, - FWUPD_ERROR_INVALID_FILE, - "incorrect device type, got 0x%04x, expected 0x%04x", - fu_thunderbolt_firmware_get_device_id(firmware), - fu_thunderbolt_firmware_get_device_id(firmware_old)); - return FALSE; - } - if (fu_thunderbolt_firmware_get_model_id(firmware) != - fu_thunderbolt_firmware_get_model_id(firmware_old)) { - g_set_error(error, - FWUPD_ERROR, - FWUPD_ERROR_INVALID_FILE, - "incorrect device model, got 0x%04x, expected 0x%04x", - fu_thunderbolt_firmware_get_model_id(firmware), - fu_thunderbolt_firmware_get_model_id(firmware_old)); - return FALSE; - } - if (fu_thunderbolt_firmware_get_has_pd(firmware_old) && - !fu_thunderbolt_firmware_get_has_pd(firmware)) { - g_set_error_literal(error, - FWUPD_ERROR, - FWUPD_ERROR_INVALID_FILE, - "new firmware is missing PD"); - return FALSE; - } - if (fu_thunderbolt_firmware_get_flash_size(firmware) != - fu_thunderbolt_firmware_get_flash_size(firmware_old)) { - g_set_error_literal(error, - FWUPD_ERROR, - FWUPD_ERROR_INVALID_FILE, - "incorrect flash size"); - return FALSE; - } - return TRUE; -} - static void test_image_validation(ThunderboltTest *tt, gconstpointer user_data) { @@ -1129,9 +1063,9 @@ test_image_validation(ThunderboltTest *tt, gconstpointer user_data) g_autoptr(GBytes) ctl_data = NULL; g_autoptr(GBytes) bad_data = NULL; g_autoptr(GError) error = NULL; - g_autoptr(FuThunderboltFirmwareUpdate) firmware_fwi = fu_thunderbolt_firmware_update_new(); - g_autoptr(FuThunderboltFirmware) firmware_ctl = fu_thunderbolt_firmware_new(); - g_autoptr(FuThunderboltFirmware) firmware_bad = fu_thunderbolt_firmware_new(); + g_autoptr(FuFirmware) firmware_fwi = fu_intel_thunderbolt_firmware_new(); + g_autoptr(FuFirmware) firmware_ctl = fu_intel_thunderbolt_nvm_new(); + g_autoptr(FuFirmware) firmware_bad = fu_intel_thunderbolt_nvm_new(); /* image as if read from the controller (i.e. no headers) */ ctl_path = g_test_build_filename(G_TEST_DIST, "tests", "minimal-fw-controller.bin", NULL); @@ -1143,10 +1077,7 @@ test_image_validation(ThunderboltTest *tt, gconstpointer user_data) g_assert_nonnull(ctl_data); /* parse controller image */ - ret = fu_firmware_parse(FU_FIRMWARE(firmware_ctl), - ctl_data, - FWUPD_INSTALL_FLAG_NO_SEARCH, - &error); + ret = fu_firmware_parse(firmware_ctl, ctl_data, FWUPD_INSTALL_FLAG_NO_SEARCH, &error); g_assert_true(ret); g_assert_no_error(error); @@ -1160,10 +1091,7 @@ test_image_validation(ThunderboltTest *tt, gconstpointer user_data) g_assert_nonnull(fwi_data); /* parse */ - ret = fu_firmware_parse(FU_FIRMWARE(firmware_fwi), - fwi_data, - FWUPD_INSTALL_FLAG_NO_SEARCH, - &error); + ret = fu_firmware_parse(firmware_fwi, fwi_data, FWUPD_INSTALL_FLAG_NO_SEARCH, &error); g_assert_true(ret); g_assert_no_error(error); @@ -1177,17 +1105,17 @@ test_image_validation(ThunderboltTest *tt, gconstpointer user_data) g_assert_nonnull(bad_data); /* parse; should fail, bad image */ - ret = fu_firmware_parse(FU_FIRMWARE(firmware_bad), - bad_data, - FWUPD_INSTALL_FLAG_NO_SEARCH, - &error); + ret = fu_firmware_parse(firmware_bad, bad_data, FWUPD_INSTALL_FLAG_NO_SEARCH, &error); g_assert_false(ret); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_READ); g_debug("expected image validation error [ctl]: %s", error->message); g_clear_error(&error); /* now for some testing ... this should work */ - ret = _compare_images(firmware_ctl, FU_THUNDERBOLT_FIRMWARE(firmware_fwi), &error); + ret = fu_firmware_check_compatible(firmware_ctl, + firmware_fwi, + FWUPD_INSTALL_FLAG_NONE, + &error); g_assert_no_error(error); g_assert_true(ret); } diff --git a/plugins/thunderbolt/fu-thunderbolt-controller.c b/plugins/thunderbolt/fu-thunderbolt-controller.c index cecd03a49..19039a727 100644 --- a/plugins/thunderbolt/fu-thunderbolt-controller.c +++ b/plugins/thunderbolt/fu-thunderbolt-controller.c @@ -12,7 +12,6 @@ #include "fu-thunderbolt-common.h" #include "fu-thunderbolt-controller.h" -#include "fu-thunderbolt-firmware.h" typedef enum { FU_THUNDERBOLT_CONTROLLER_KIND_DEVICE, @@ -30,6 +29,10 @@ struct _FuThunderboltController { G_DEFINE_TYPE(FuThunderboltController, fu_thunderbolt_controller, FU_TYPE_THUNDERBOLT_DEVICE) +/* byte offsets in firmware image */ +#define FU_TBT_OFFSET_NATIVE 0x7B +#define FU_TBT_CHUNK_SZ 0x40 + static void fu_thunderbolt_controller_check_safe_mode(FuThunderboltController *self) { @@ -108,7 +111,7 @@ fu_thunderbolt_controller_read_status_block(FuThunderboltController *self, GErro g_autoptr(GFile) nvmem = NULL; g_autoptr(GBytes) controller_fw = NULL; g_autoptr(GInputStream) istr = NULL; - g_autoptr(FuThunderboltFirmware) firmware = fu_thunderbolt_firmware_new(); + g_autoptr(FuFirmware) firmware = NULL; nvmem = fu_thunderbolt_device_find_nvmem(FU_THUNDERBOLT_DEVICE(self), TRUE, error); if (nvmem == NULL) @@ -122,12 +125,18 @@ fu_thunderbolt_controller_read_status_block(FuThunderboltController *self, GErro controller_fw = g_input_stream_read_bytes(istr, nr_chunks * FU_TBT_CHUNK_SZ, NULL, error); if (controller_fw == NULL) return FALSE; - if (!fu_firmware_parse(FU_FIRMWARE(firmware), - controller_fw, - FWUPD_INSTALL_FLAG_NO_SEARCH, - error)) + firmware = fu_firmware_new_from_gtypes(controller_fw, + FWUPD_INSTALL_FLAG_NO_SEARCH, + error, + FU_TYPE_INTEL_THUNDERBOLT_NVM, + FU_TYPE_FIRMWARE, + G_TYPE_INVALID); + if (firmware == NULL) return FALSE; - self->is_native = fu_thunderbolt_firmware_is_native(firmware); + if (FU_IS_INTEL_THUNDERBOLT_FIRMWARE(firmware)) { + self->is_native = + fu_intel_thunderbolt_nvm_is_native(FU_INTEL_THUNDERBOLT_NVM(firmware)); + } return TRUE; } diff --git a/plugins/thunderbolt/fu-thunderbolt-device.c b/plugins/thunderbolt/fu-thunderbolt-device.c index 14018755d..c14e632cf 100644 --- a/plugins/thunderbolt/fu-thunderbolt-device.c +++ b/plugins/thunderbolt/fu-thunderbolt-device.c @@ -17,7 +17,6 @@ #include #include "fu-thunderbolt-device.h" -#include "fu-thunderbolt-firmware-update.h" typedef struct { const gchar *auth_method; @@ -305,13 +304,19 @@ fu_thunderbolt_device_prepare_firmware(FuDevice *device, GError **error) { FuThunderboltDevice *self = FU_THUNDERBOLT_DEVICE(device); - g_autoptr(FuThunderboltFirmwareUpdate) firmware = fu_thunderbolt_firmware_update_new(); - g_autoptr(FuThunderboltFirmware) firmware_old = fu_thunderbolt_firmware_new(); + g_autoptr(FuFirmware) firmware = NULL; + g_autoptr(FuFirmware) firmware_old = NULL; g_autoptr(GBytes) controller_fw = NULL; g_autoptr(GFile) nvmem = NULL; /* parse */ - if (!fu_firmware_parse(FU_FIRMWARE(firmware), fw, flags, error)) + firmware = fu_firmware_new_from_gtypes(fw, + flags, + error, + FU_TYPE_INTEL_THUNDERBOLT_FIRMWARE, + FU_TYPE_FIRMWARE, + G_TYPE_INVALID); + if (firmware == NULL) return NULL; /* get current NVMEM */ @@ -321,75 +326,19 @@ fu_thunderbolt_device_prepare_firmware(FuDevice *device, controller_fw = g_file_load_bytes(nvmem, NULL, NULL, error); if (controller_fw == NULL) return NULL; - if (!fu_firmware_parse(FU_FIRMWARE(firmware_old), controller_fw, flags, error)) + firmware_old = fu_firmware_new_from_gtypes(controller_fw, + flags, + error, + FU_TYPE_INTEL_THUNDERBOLT_NVM, + FU_TYPE_FIRMWARE, + G_TYPE_INVALID); + if (firmware_old == NULL) return NULL; - if (fu_thunderbolt_firmware_is_host(FU_THUNDERBOLT_FIRMWARE(firmware)) != - fu_thunderbolt_firmware_is_host(firmware_old)) { - g_set_error(error, - FWUPD_ERROR, - FWUPD_ERROR_INVALID_FILE, - "incorrect firmware mode, got %s, expected %s", - fu_thunderbolt_firmware_is_host(FU_THUNDERBOLT_FIRMWARE(firmware)) - ? "host" - : "device", - fu_thunderbolt_firmware_is_host(firmware_old) ? "host" : "device"); + if (!fu_firmware_check_compatible(firmware_old, firmware, flags, error)) return NULL; - } - if (fu_thunderbolt_firmware_get_vendor_id(FU_THUNDERBOLT_FIRMWARE(firmware)) != - fu_thunderbolt_firmware_get_vendor_id(firmware_old)) { - g_set_error( - error, - FWUPD_ERROR, - FWUPD_ERROR_INVALID_FILE, - "incorrect device vendor, got 0x%04x, expected 0x%04x", - fu_thunderbolt_firmware_get_vendor_id(FU_THUNDERBOLT_FIRMWARE(firmware)), - fu_thunderbolt_firmware_get_vendor_id(firmware_old)); - return NULL; - } - if (fu_thunderbolt_firmware_get_device_id(FU_THUNDERBOLT_FIRMWARE(firmware)) != - fu_thunderbolt_firmware_get_device_id(firmware_old)) { - g_set_error( - error, - FWUPD_ERROR, - FWUPD_ERROR_INVALID_FILE, - "incorrect device type, got 0x%04x, expected 0x%04x", - fu_thunderbolt_firmware_get_device_id(FU_THUNDERBOLT_FIRMWARE(firmware)), - fu_thunderbolt_firmware_get_device_id(firmware_old)); - return NULL; - } - if ((flags & FWUPD_INSTALL_FLAG_IGNORE_VID_PID) == 0) { - if (fu_thunderbolt_firmware_get_model_id(FU_THUNDERBOLT_FIRMWARE(firmware)) != - fu_thunderbolt_firmware_get_model_id(firmware_old)) { - g_set_error( - error, - FWUPD_ERROR, - FWUPD_ERROR_INVALID_FILE, - "incorrect device model, got 0x%04x, expected 0x%04x", - fu_thunderbolt_firmware_get_model_id(FU_THUNDERBOLT_FIRMWARE(firmware)), - fu_thunderbolt_firmware_get_model_id(firmware_old)); - return NULL; - } - /* old firmware has PD but new doesn't (we don't care about other way around) */ - if (fu_thunderbolt_firmware_get_has_pd(firmware_old) && - !fu_thunderbolt_firmware_get_has_pd(FU_THUNDERBOLT_FIRMWARE(firmware))) { - g_set_error_literal(error, - FWUPD_ERROR, - FWUPD_ERROR_INVALID_FILE, - "incorrect PD section"); - return NULL; - } - if (fu_thunderbolt_firmware_get_flash_size(FU_THUNDERBOLT_FIRMWARE(firmware)) != - fu_thunderbolt_firmware_get_flash_size(firmware_old)) { - g_set_error_literal(error, - FWUPD_ERROR, - FWUPD_ERROR_INVALID_FILE, - "incorrect flash size"); - return NULL; - } - } /* success */ - return FU_FIRMWARE(g_steal_pointer(&firmware)); + return g_steal_pointer(&firmware); } static gboolean diff --git a/plugins/thunderbolt/fu-thunderbolt-firmware-update.c b/plugins/thunderbolt/fu-thunderbolt-firmware-update.c deleted file mode 100644 index e7ce0995c..000000000 --- a/plugins/thunderbolt/fu-thunderbolt-firmware-update.c +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2020 Mario Limonciello - * - * SPDX-License-Identifier: LGPL-2.1+ - */ - -#include "config.h" - -#include - -#include "fu-thunderbolt-firmware-update.h" -#include "fu-thunderbolt-firmware.h" - -struct _FuThunderboltFirmwareUpdate { - FuThunderboltFirmwareClass parent_instance; -}; - -G_DEFINE_TYPE(FuThunderboltFirmwareUpdate, - fu_thunderbolt_firmware_update, - FU_TYPE_THUNDERBOLT_FIRMWARE) - -static inline gboolean -fu_thunderbolt_firmware_valid_farb_pointer(guint32 pointer) -{ - return pointer != 0 && pointer != 0xFFFFFF; -} - -static gboolean -fu_thunderbolt_firmware_read_farb_pointer_impl(FuThunderboltFirmwareUpdate *self, - FuThunderboltSection section, - guint32 offset, - guint32 *value, - GError **error) -{ - FuThunderboltFirmware *tbt = FU_THUNDERBOLT_FIRMWARE(self); - guint32 tmp = 0; - if (!fu_thunderbolt_firmware_read_location(tbt, - section, - offset, - (guint8 *)&tmp, - 3, /* 24 bits */ - error)) { - g_prefix_error(error, "failed to read farb pointer: "); - return FALSE; - } - *value = GUINT32_FROM_LE(tmp); - return TRUE; -} - -/* returns invalid FARB pointer on error */ -static guint32 -fu_thunderbolt_firmware_read_farb_pointer(FuThunderboltFirmwareUpdate *self, GError **error) -{ - guint32 value; - if (!fu_thunderbolt_firmware_read_farb_pointer_impl(self, - _SECTION_DIGITAL, - 0x0, - &value, - error)) - return 0; - if (fu_thunderbolt_firmware_valid_farb_pointer(value)) - return value; - - if (!fu_thunderbolt_firmware_read_farb_pointer_impl(self, - _SECTION_DIGITAL, - 0x1000, - &value, - error)) - return 0; - if (!fu_thunderbolt_firmware_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 -fu_thunderbolt_firmware_update_parse(FuFirmware *firmware, - GBytes *fw, - gsize offset_ignored, - FwupdInstallFlags flags, - GError **error) -{ - FuThunderboltFirmwareUpdate *self = FU_THUNDERBOLT_FIRMWARE_UPDATE(firmware); - guint32 offset = fu_thunderbolt_firmware_read_farb_pointer(self, error); - if (offset == 0) - return FALSE; - g_debug("detected digital section begins at 0x%x", offset); - fu_thunderbolt_firmware_set_digital(FU_THUNDERBOLT_FIRMWARE(firmware), offset); - - return TRUE; -} - -static void -fu_thunderbolt_firmware_update_init(FuThunderboltFirmwareUpdate *self) -{ -} - -static void -fu_thunderbolt_firmware_update_class_init(FuThunderboltFirmwareUpdateClass *klass) -{ - FuThunderboltFirmwareClass *klass_firmware = FU_THUNDERBOLT_FIRMWARE_CLASS(klass); - klass_firmware->parse = fu_thunderbolt_firmware_update_parse; -} - -FuThunderboltFirmwareUpdate * -fu_thunderbolt_firmware_update_new(void) -{ - return g_object_new(FU_TYPE_THUNDERBOLT_FIRMWARE_UPDATE, NULL); -} diff --git a/plugins/thunderbolt/fu-thunderbolt-firmware-update.h b/plugins/thunderbolt/fu-thunderbolt-firmware-update.h deleted file mode 100644 index 76e866ef3..000000000 --- a/plugins/thunderbolt/fu-thunderbolt-firmware-update.h +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (C) 2020 Mario Limonciello - * - * SPDX-License-Identifier: LGPL-2.1+ - */ - -#pragma once - -#include - -#include "fu-thunderbolt-firmware.h" - -#define FU_TYPE_THUNDERBOLT_FIRMWARE_UPDATE (fu_thunderbolt_firmware_update_get_type()) -G_DECLARE_FINAL_TYPE(FuThunderboltFirmwareUpdate, - fu_thunderbolt_firmware_update, - FU, - THUNDERBOLT_FIRMWARE_UPDATE, - FuThunderboltFirmware) - -FuThunderboltFirmwareUpdate * -fu_thunderbolt_firmware_update_new(void); diff --git a/plugins/thunderbolt/fu-thunderbolt-firmware.c b/plugins/thunderbolt/fu-thunderbolt-firmware.c deleted file mode 100644 index 33ea69013..000000000 --- a/plugins/thunderbolt/fu-thunderbolt-firmware.c +++ /dev/null @@ -1,569 +0,0 @@ -/* - * Copyright (C) 2017 Intel Corporation. - * Copyright (C) 2020 Richard Hughes - * - * SPDX-License-Identifier: LGPL-2.1+ - */ - -#include "config.h" - -#include - -#include "fu-thunderbolt-firmware.h" - -typedef struct { - guint32 sections[_SECTION_LAST]; - FuThunderboltFamily family; - gboolean is_host; - gboolean is_native; - gboolean has_pd; - guint16 device_id; - guint16 vendor_id; - guint16 model_id; - guint gen; - guint ports; - guint8 flash_size; -} FuThunderboltFirmwarePrivate; - -G_DEFINE_TYPE_WITH_PRIVATE(FuThunderboltFirmware, fu_thunderbolt_firmware, FU_TYPE_FIRMWARE) - -#define GET_PRIVATE(o) (fu_thunderbolt_firmware_get_instance_private(o)) - -typedef struct { - guint16 id; - guint gen; - FuThunderboltFamily family; - guint ports; -} FuThunderboltHwInfo; - -enum { - DROM_ENTRY_MC = 0x6, -}; - -gboolean -fu_thunderbolt_firmware_is_host(FuThunderboltFirmware *self) -{ - FuThunderboltFirmwarePrivate *priv; - g_return_val_if_fail(FU_IS_THUNDERBOLT_FIRMWARE(self), FALSE); - priv = GET_PRIVATE(self); - return priv->is_host; -} - -gboolean -fu_thunderbolt_firmware_is_native(FuThunderboltFirmware *self) -{ - FuThunderboltFirmwarePrivate *priv; - g_return_val_if_fail(FU_IS_THUNDERBOLT_FIRMWARE(self), FALSE); - priv = GET_PRIVATE(self); - return priv->is_native; -} - -gboolean -fu_thunderbolt_firmware_get_has_pd(FuThunderboltFirmware *self) -{ - FuThunderboltFirmwarePrivate *priv; - g_return_val_if_fail(FU_IS_THUNDERBOLT_FIRMWARE(self), FALSE); - priv = GET_PRIVATE(self); - return priv->has_pd; -} - -guint16 -fu_thunderbolt_firmware_get_device_id(FuThunderboltFirmware *self) -{ - FuThunderboltFirmwarePrivate *priv; - g_return_val_if_fail(FU_IS_THUNDERBOLT_FIRMWARE(self), 0x0); - priv = GET_PRIVATE(self); - return priv->device_id; -} - -guint16 -fu_thunderbolt_firmware_get_vendor_id(FuThunderboltFirmware *self) -{ - FuThunderboltFirmwarePrivate *priv; - g_return_val_if_fail(FU_IS_THUNDERBOLT_FIRMWARE(self), 0x0); - priv = GET_PRIVATE(self); - return priv->vendor_id; -} - -guint16 -fu_thunderbolt_firmware_get_model_id(FuThunderboltFirmware *self) -{ - FuThunderboltFirmwarePrivate *priv; - g_return_val_if_fail(FU_IS_THUNDERBOLT_FIRMWARE(self), 0x0); - priv = GET_PRIVATE(self); - return priv->model_id; -} - -guint8 -fu_thunderbolt_firmware_get_flash_size(FuThunderboltFirmware *self) -{ - FuThunderboltFirmwarePrivate *priv; - g_return_val_if_fail(FU_IS_THUNDERBOLT_FIRMWARE(self), 0x0); - priv = GET_PRIVATE(self); - return priv->flash_size; -} - -static const gchar * -fu_thunderbolt_firmware_family_to_string(FuThunderboltFamily family) -{ - if (family == _FAMILY_FR) - return "Falcon Ridge"; - if (family == _FAMILY_WR) - return "Win Ridge"; - if (family == _FAMILY_AR) - return "Alpine Ridge"; - if (family == _FAMILY_AR_C) - return "Alpine Ridge C"; - if (family == _FAMILY_TR) - return "Titan Ridge"; - if (family == _FAMILY_BB) - return "BB"; - if (family == _FAMILY_MR) - return "Maple Ridge"; - return "Unknown"; -} - -static void -fu_thunderbolt_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) -{ - FuThunderboltFirmware *self = FU_THUNDERBOLT_FIRMWARE(firmware); - FuThunderboltFirmwarePrivate *priv = GET_PRIVATE(self); - - fu_xmlb_builder_insert_kv(bn, - "family", - fu_thunderbolt_firmware_family_to_string(priv->family)); - fu_xmlb_builder_insert_kb(bn, "is_host", priv->is_host); - fu_xmlb_builder_insert_kb(bn, "is_native", priv->is_native); - fu_xmlb_builder_insert_kx(bn, "device_id", priv->device_id); - fu_xmlb_builder_insert_kx(bn, "vendor_id", priv->vendor_id); - fu_xmlb_builder_insert_kx(bn, "model_id", priv->model_id); - fu_xmlb_builder_insert_kx(bn, "flash_size", priv->flash_size); - fu_xmlb_builder_insert_kx(bn, "generation", priv->gen); - fu_xmlb_builder_insert_kx(bn, "ports", priv->ports); - fu_xmlb_builder_insert_kb(bn, "has_pd", priv->has_pd); - for (guint i = 0; i < _SECTION_LAST; i++) { - g_autofree gchar *tmp = g_strdup_printf("%x", priv->sections[i]); - xb_builder_node_insert_text(bn, "section", tmp, NULL); - } -} - -static inline gboolean -fu_thunderbolt_firmware_valid_pd_pointer(guint32 pointer) -{ - return pointer != 0 && pointer != 0xFFFFFFFF; -} - -gboolean -fu_thunderbolt_firmware_read_location(FuThunderboltFirmware *self, - FuThunderboltSection section, - guint32 offset, - guint8 *buf, - guint32 len, - GError **error) -{ - const guint8 *srcbuf; - gsize srcbufsz = 0; - FuThunderboltFirmwarePrivate *priv = GET_PRIVATE(self); - guint32 location_start = priv->sections[section] + offset; - g_autoptr(GBytes) fw = NULL; - - /* get blob */ - fw = fu_firmware_get_bytes(FU_FIRMWARE(self), error); - if (fw == NULL) - return FALSE; - srcbuf = g_bytes_get_data(fw, &srcbufsz); - - if (!fu_memcpy_safe(buf, - len, - 0x0, /* dst */ - srcbuf, - srcbufsz, - location_start, /* src */ - len, - error)) { - g_prefix_error(error, "location is outside of the given image: "); - return FALSE; - } - return TRUE; -} - -static gboolean -fu_thunderbolt_firmware_read_uint8(FuThunderboltFirmware *self, - FuThunderboltSection section, - guint32 offset, - guint8 *value, - GError **error) -{ - return fu_thunderbolt_firmware_read_location(self, section, offset, value, 1, error); -} - -static gboolean -fu_thunderbolt_firmware_read_uint16(FuThunderboltFirmware *self, - FuThunderboltSection section, - guint32 offset, - guint16 *value, - GError **error) -{ - guint16 tmp = 0; - if (!fu_thunderbolt_firmware_read_location(self, - section, - offset, - (guint8 *)&tmp, - sizeof(tmp), - error)) { - g_prefix_error(error, "failed to read uint16: "); - return FALSE; - } - *value = GUINT16_FROM_LE(tmp); - return TRUE; -} - -static gboolean -fu_thunderbolt_firmware_read_uint32(FuThunderboltFirmware *self, - FuThunderboltSection section, - guint32 offset, - guint32 *value, - GError **error) -{ - guint32 tmp = 0; - if (!fu_thunderbolt_firmware_read_location(self, - section, - offset, - (guint8 *)&tmp, - sizeof(tmp), - error)) { - g_prefix_error(error, "failed to read uint32: "); - return FALSE; - } - *value = GUINT32_FROM_LE(tmp); - 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 -fu_thunderbolt_firmware_read_ucode_section_len(FuThunderboltFirmware *self, - guint32 offset, - guint16 *value, - GError **error) -{ - if (!fu_thunderbolt_firmware_read_uint16(self, _SECTION_DIGITAL, offset, value, error)) { - g_prefix_error(error, "failed to read ucode section len: "); - return FALSE; - } - *value *= sizeof(guint32); - *value += sizeof(guint16); - return TRUE; -} - -/* assumes sections[_SECTION_DIGITAL].offset is already set */ -static gboolean -fu_thunderbolt_firmware_read_sections(FuThunderboltFirmware *self, GError **error) -{ - guint32 offset; - FuThunderboltFirmwarePrivate *priv = GET_PRIVATE(self); - - if (priv->gen >= 3 || priv->gen == 0) { - if (!fu_thunderbolt_firmware_read_uint32(self, - _SECTION_DIGITAL, - 0x10e, - &offset, - error)) - return FALSE; - priv->sections[_SECTION_DROM] = offset + priv->sections[_SECTION_DIGITAL]; - - if (!fu_thunderbolt_firmware_read_uint32(self, - _SECTION_DIGITAL, - 0x75, - &offset, - error)) - return FALSE; - priv->sections[_SECTION_ARC_PARAMS] = offset + priv->sections[_SECTION_DIGITAL]; - } - - if (priv->is_host && priv->gen > 2) { - /* - * 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 guint8 DRAM_FLAG = 1 << 6; - guint16 ucode_offset; - guint8 available_sections = 0; - - if (!fu_thunderbolt_firmware_read_uint8(self, - _SECTION_DIGITAL, - 0x2, - &available_sections, - error)) { - g_prefix_error(error, "failed to read available sections: "); - return FALSE; - } - if (!fu_thunderbolt_firmware_read_uint16(self, - _SECTION_DIGITAL, - 0x3, - &ucode_offset, - error)) { - g_prefix_error(error, "failed to read ucode offset: "); - return FALSE; - } - offset = ucode_offset; - if ((available_sections & 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 (guint8 i = 1; i < DRAM_FLAG; i <<= 1) { - if (available_sections & i) { - if (!fu_thunderbolt_firmware_read_ucode_section_len(self, - offset, - &ucode_offset, - error)) - return FALSE; - offset += ucode_offset; - } - } - priv->sections[_SECTION_DRAM_UCODE] = offset + priv->sections[_SECTION_DIGITAL]; - } - - return TRUE; -} - -static gboolean -fu_thunderbolt_firmware_missing_needed_drom(FuThunderboltFirmware *self) -{ - FuThunderboltFirmwarePrivate *priv = GET_PRIVATE(self); - if (priv->sections[_SECTION_DROM] != 0) - return FALSE; - if (priv->is_host && priv->gen < 3) - return FALSE; - return TRUE; -} - -void -fu_thunderbolt_firmware_set_digital(FuThunderboltFirmware *self, guint32 offset) -{ - FuThunderboltFirmwarePrivate *priv = GET_PRIVATE(self); - priv->sections[_SECTION_DIGITAL] = offset; -} - -static gboolean -fu_thunderbolt_firmware_parse(FuFirmware *firmware, - GBytes *fw, - gsize offset, - FwupdInstallFlags flags, - GError **error) -{ - FuThunderboltFirmware *self = FU_THUNDERBOLT_FIRMWARE(firmware); - FuThunderboltFirmwarePrivate *priv = GET_PRIVATE(self); - FuThunderboltFirmwareClass *klass_firmware = FU_THUNDERBOLT_FIRMWARE_GET_CLASS(firmware); - - guint8 tmp = 0; - guint16 version = 0; - static const FuThunderboltHwInfo hw_info_arr[] = { - {0x156D, 2, _FAMILY_FR, 2}, /* FR 4C */ - {0x156B, 2, _FAMILY_FR, 1}, /* FR 2C */ - {0x157E, 2, _FAMILY_WR, 1}, /* WR */ - {0x1578, 3, _FAMILY_AR, 2}, /* AR 4C */ - {0x1576, 3, _FAMILY_AR, 1}, /* AR 2C */ - {0x15C0, 3, _FAMILY_AR, 1}, /* AR LP */ - {0x15D3, 3, _FAMILY_AR_C, 2}, /* AR-C 4C */ - {0x15DA, 3, _FAMILY_AR_C, 1}, /* AR-C 2C */ - {0x15E7, 3, _FAMILY_TR, 1}, /* TR 2C */ - {0x15EA, 3, _FAMILY_TR, 2}, /* TR 4C */ - {0x15EF, 3, _FAMILY_TR, 2}, /* TR 4C device */ - {0x15EE, 3, _FAMILY_BB, 0}, /* BB device */ - /* Maple ridge devices - * NOTE: These are expected to be flashed via UEFI capsules *not* Thunderbolt plugin - * Flashing via fwupd will require matching kernel work. - * They're left here only for parsing the binaries - */ - {0x1136, 4, _FAMILY_MR, 2}, - {0x1137, 4, _FAMILY_MR, 2}, - {0}}; - g_autofree gchar *version_str = NULL; - - /* add this straight away so we can read it without a self */ - fu_firmware_set_bytes(firmware, fw); - - /* subclassed */ - if (klass_firmware->parse != NULL) { - if (!klass_firmware->parse(firmware, fw, offset, flags, error)) - return FALSE; - } - - /* is native */ - if (!fu_thunderbolt_firmware_read_uint8(self, - _SECTION_DIGITAL, - FU_TBT_OFFSET_NATIVE, - &tmp, - error)) { - g_prefix_error(error, "failed to read native: "); - return FALSE; - } - priv->is_native = tmp & 0x20; - - /* we're only reading the first chunk */ - if (g_bytes_get_size(fw) == 0x80) - return TRUE; - - /* host or device */ - if (!fu_thunderbolt_firmware_read_uint8(self, _SECTION_DIGITAL, 0x10, &tmp, error)) { - g_prefix_error(error, "failed to read is-host: "); - return FALSE; - } - priv->is_host = tmp & (1 << 1); - - /* device ID */ - if (!fu_thunderbolt_firmware_read_uint16(self, - _SECTION_DIGITAL, - 0x5, - &priv->device_id, - error)) { - g_prefix_error(error, "failed to read device-id: "); - return FALSE; - } - - /* this is best-effort */ - for (guint i = 0; hw_info_arr[i].id != 0; i++) { - if (hw_info_arr[i].id == priv->device_id) { - priv->family = hw_info_arr[i].family; - priv->gen = hw_info_arr[i].gen; - priv->ports = hw_info_arr[i].ports; - break; - } - } - if (priv->ports == 0 && priv->is_host) { - g_set_error(error, - FWUPD_ERROR, - FWUPD_ERROR_NOT_SUPPORTED, - "Unknown controller: %x", - priv->device_id); - return FALSE; - } - - /* read sections from file */ - if (!fu_thunderbolt_firmware_read_sections(self, error)) - return FALSE; - if (fu_thunderbolt_firmware_missing_needed_drom(self)) { - g_set_error_literal(error, - FWUPD_ERROR, - FWUPD_ERROR_READ, - "Can't find required FW sections"); - return FALSE; - } - - /* vendor:model */ - if (priv->sections[_SECTION_DROM] != 0) { - if (!fu_thunderbolt_firmware_read_uint16(self, - _SECTION_DROM, - 0x10, - &priv->vendor_id, - error)) { - g_prefix_error(error, "failed to read vendor-id: "); - return FALSE; - } - if (!fu_thunderbolt_firmware_read_uint16(self, - _SECTION_DROM, - 0x12, - &priv->model_id, - error)) { - g_prefix_error(error, "failed to read model-id: "); - return FALSE; - } - } - - /* has PD */ - if (priv->sections[_SECTION_ARC_PARAMS] != 0) { - guint32 pd_pointer = 0x0; - if (!fu_thunderbolt_firmware_read_uint32(self, - _SECTION_ARC_PARAMS, - 0x10C, - &pd_pointer, - error)) { - g_prefix_error(error, "failed to read pd-pointer: "); - return FALSE; - } - priv->has_pd = fu_thunderbolt_firmware_valid_pd_pointer(pd_pointer); - } - - /* versions */ - switch (priv->family) { - case _FAMILY_TR: - if (!fu_thunderbolt_firmware_read_uint16(self, - _SECTION_DIGITAL, - 0x09, - &version, - error)) { - g_prefix_error(error, "failed to read version: "); - return FALSE; - } - version_str = fu_version_from_uint16(version, FWUPD_VERSION_FORMAT_BCD); - fu_firmware_set_version(FU_FIRMWARE(self), version_str); - break; - default: - break; - } - - if (priv->is_host) { - switch (priv->family) { - case _FAMILY_AR: - case _FAMILY_AR_C: - case _FAMILY_TR: - /* This is used for comparison between old and new image, not a raw number - */ - if (!fu_thunderbolt_firmware_read_uint8(self, - _SECTION_DIGITAL, - 0x45, - &tmp, - error)) { - g_prefix_error(error, "failed to read flash size: "); - return FALSE; - } - priv->flash_size = tmp & 0x07; - break; - default: - break; - } - } - - /* success */ - return TRUE; -} - -static void -fu_thunderbolt_firmware_init(FuThunderboltFirmware *self) -{ - fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_VID_PID); -} - -static void -fu_thunderbolt_firmware_class_init(FuThunderboltFirmwareClass *klass) -{ - FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); - klass_firmware->parse = fu_thunderbolt_firmware_parse; - klass_firmware->export = fu_thunderbolt_firmware_export; -} - -FuThunderboltFirmware * -fu_thunderbolt_firmware_new(void) -{ - return g_object_new(FU_TYPE_THUNDERBOLT_FIRMWARE, NULL); -} diff --git a/plugins/thunderbolt/fu-thunderbolt-firmware.h b/plugins/thunderbolt/fu-thunderbolt-firmware.h deleted file mode 100644 index 80bd770d3..000000000 --- a/plugins/thunderbolt/fu-thunderbolt-firmware.h +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2017 Intel Corporation. - * Copyright (C) 2020 Richard Hughes - * - * SPDX-License-Identifier: LGPL-2.1+ - */ - -#pragma once - -#include - -#define FU_TYPE_THUNDERBOLT_FIRMWARE (fu_thunderbolt_firmware_get_type()) -G_DECLARE_DERIVABLE_TYPE(FuThunderboltFirmware, - fu_thunderbolt_firmware, - FU, - THUNDERBOLT_FIRMWARE, - FuFirmware) - -typedef enum { - _SECTION_DIGITAL, - _SECTION_DROM, - _SECTION_ARC_PARAMS, - _SECTION_DRAM_UCODE, - _SECTION_LAST -} FuThunderboltSection; - -typedef enum { - _FAMILY_UNKNOWN, - _FAMILY_FR, - _FAMILY_WR, - _FAMILY_AR, - _FAMILY_AR_C, - _FAMILY_TR, - _FAMILY_BB, - _FAMILY_MR, -} FuThunderboltFamily; - -struct _FuThunderboltFirmwareClass { - FuFirmwareClass parent_class; - gboolean (*parse)(FuFirmware *self, - GBytes *fw, - gsize offset, - FwupdInstallFlags flags, - GError **error); - /*< private >*/ - gpointer padding[28]; -}; - -/* byte offsets in firmware image */ -#define FU_TBT_OFFSET_NATIVE 0x7B -#define FU_TBT_CHUNK_SZ 0x40 - -FuThunderboltFirmware * -fu_thunderbolt_firmware_new(void); -gboolean -fu_thunderbolt_firmware_is_host(FuThunderboltFirmware *self); -gboolean -fu_thunderbolt_firmware_is_native(FuThunderboltFirmware *self); -gboolean -fu_thunderbolt_firmware_get_has_pd(FuThunderboltFirmware *self); -guint16 -fu_thunderbolt_firmware_get_device_id(FuThunderboltFirmware *self); -guint16 -fu_thunderbolt_firmware_get_vendor_id(FuThunderboltFirmware *self); -guint16 -fu_thunderbolt_firmware_get_model_id(FuThunderboltFirmware *self); -guint8 -fu_thunderbolt_firmware_get_flash_size(FuThunderboltFirmware *self); -void -fu_thunderbolt_firmware_set_digital(FuThunderboltFirmware *self, guint32 offset); -gboolean -fu_thunderbolt_firmware_read_location(FuThunderboltFirmware *self, - FuThunderboltSection section, - guint32 offset, - guint8 *buf, - guint32 len, - GError **error); diff --git a/plugins/thunderbolt/meson.build b/plugins/thunderbolt/meson.build index 5f8ffbf90..42b4a5cd7 100644 --- a/plugins/thunderbolt/meson.build +++ b/plugins/thunderbolt/meson.build @@ -11,8 +11,6 @@ fu_plugin_thunderbolt = shared_module('fu_plugin_thunderbolt', 'fu-thunderbolt-device.c', 'fu-thunderbolt-retimer.c', 'fu-thunderbolt-controller.c', - 'fu-thunderbolt-firmware.c', - 'fu-thunderbolt-firmware-update.c', ], include_directories: [ root_incdir, @@ -51,8 +49,6 @@ if get_option('tests') and run_sanitize_unsafe_tests and umockdev.found() and gi 'fu-thunderbolt-device.c', 'fu-thunderbolt-retimer.c', 'fu-thunderbolt-controller.c', - 'fu-thunderbolt-firmware.c', - 'fu-thunderbolt-firmware-update.c', ], include_directories: [ root_incdir, diff --git a/src/fu-engine.c b/src/fu-engine.c index 74c87216d..f2fd2c348 100644 --- a/src/fu-engine.c +++ b/src/fu-engine.c @@ -7746,6 +7746,9 @@ fu_engine_load(FuEngine *self, FuEngineLoadFlags flags, FuProgress *progress, GE fu_context_add_firmware_gtype(self->ctx, "cfu-payload", FU_TYPE_CFU_PAYLOAD); fu_context_add_firmware_gtype(self->ctx, "uswid", FU_TYPE_USWID_FIRMWARE); fu_context_add_firmware_gtype(self->ctx, "coswid", FU_TYPE_COSWID_FIRMWARE); + fu_context_add_firmware_gtype(self->ctx, + "intel-thunderbolt", + FU_TYPE_INTEL_THUNDERBOLT_FIRMWARE); /* we are emulating a different host */ if (host_emulate != NULL) {