/* * Copyright (C) 2022 Intel * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-igsc-aux-firmware.h" #include "fu-igsc-heci.h" struct _FuIgscAuxFirmware { FuIfwiFptFirmware parent_instance; guint32 oem_version; guint16 major_version; guint16 major_vcn; GPtrArray *device_infos; /* of igsc_fwdata_device_info */ gboolean has_manifest_ext; }; G_DEFINE_TYPE(FuIgscAuxFirmware, fu_igsc_aux_firmware, FU_TYPE_IFWI_FPT_FIRMWARE) #define MFT_EXT_TYPE_DEVICE_IDS 37 #define MFT_EXT_TYPE_FWDATA_UPDATE 29 struct mft_fwdata_update_ext { guint32 extension_type; guint32 extension_length; guint32 oem_manuf_data_version; guint16 major_vcn; guint16 flags; }; struct igsc_fwdata_device_info { guint16 vendor_id; guint16 device_id; guint16 subsys_vendor_id; guint16 subsys_device_id; }; struct igsc_fwdata_version { guint32 oem_manuf_data_version; guint16 major_version; guint16 major_vcn; }; static void fu_igsc_aux_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuIgscAuxFirmware *self = FU_IGSC_AUX_FIRMWARE(firmware); fu_xmlb_builder_insert_kx(bn, "oem_version", self->oem_version); fu_xmlb_builder_insert_kx(bn, "major_version", self->major_version); fu_xmlb_builder_insert_kx(bn, "major_vcn", self->major_vcn); fu_xmlb_builder_insert_kx(bn, "device_infos", self->device_infos->len); fu_xmlb_builder_insert_kb(bn, "has_manifest_ext", self->has_manifest_ext); } gboolean fu_igsc_aux_firmware_match_device(FuIgscAuxFirmware *self, guint16 vendor_id, guint16 device_id, guint16 subsys_vendor_id, guint16 subsys_device_id, GError **error) { g_return_val_if_fail(FU_IS_IGSC_AUX_FIRMWARE(self), FALSE); for (guint i = 0; i < self->device_infos->len; i++) { struct igsc_fwdata_device_info *info = g_ptr_array_index(self->device_infos, i); if (info->vendor_id == vendor_id && info->device_id == device_id && info->subsys_vendor_id == subsys_vendor_id && info->subsys_device_id == subsys_device_id) return TRUE; } /* not us */ g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "could not find 0x%04x:0x%04x 0x%04x:0x%04x in the image", vendor_id, device_id, subsys_vendor_id, subsys_device_id); return FALSE; } guint32 fu_igsc_aux_firmware_get_oem_version(FuIgscAuxFirmware *self) { g_return_val_if_fail(FU_IS_IGSC_AUX_FIRMWARE(self), G_MAXUINT32); return self->oem_version; } guint16 fu_igsc_aux_firmware_get_major_version(FuIgscAuxFirmware *self) { g_return_val_if_fail(FU_IS_IGSC_AUX_FIRMWARE(self), G_MAXUINT16); return self->major_version; } guint16 fu_igsc_aux_firmware_get_major_vcn(FuIgscAuxFirmware *self) { g_return_val_if_fail(FU_IS_IGSC_AUX_FIRMWARE(self), G_MAXUINT16); return self->major_vcn; } static gboolean fu_igsc_aux_firmware_parse_version(FuIgscAuxFirmware *self, GError **error) { gsize bufsz = 0; const guint8 *buf; struct igsc_fwdata_version version = {0x0}; g_autoptr(GBytes) fw_info = NULL; fw_info = fu_firmware_get_image_by_idx_bytes(FU_FIRMWARE(self), FU_IFWI_FPT_FIRMWARE_IDX_SDTA, error); if (fw_info == NULL) return FALSE; buf = g_bytes_get_data(fw_info, &bufsz); if (!fu_memcpy_safe((guint8 *)&version, sizeof(version), 0x0, /* dst */ buf, bufsz, sizeof(struct gsc_fwu_heci_image_metadata), /* src */ sizeof(version), error)) { g_prefix_error(error, "no version: "); return FALSE; } self->oem_version = version.oem_manuf_data_version; self->major_vcn = version.major_vcn; self->major_version = version.major_version; return TRUE; } static gboolean fu_igsc_aux_firmware_parse_extension(FuIgscAuxFirmware *self, FuFirmware *fw, GError **error) { const guint8 *buf; gsize bufsz = 0; g_autoptr(GBytes) blob = NULL; /* get data */ blob = fu_firmware_get_bytes(fw, error); if (blob == NULL) return FALSE; buf = g_bytes_get_data(blob, &bufsz); if (fu_firmware_get_idx(fw) == MFT_EXT_TYPE_DEVICE_IDS) { for (gsize offset = 0; offset < bufsz; offset += sizeof(struct igsc_fwdata_device_info)) { struct igsc_fwdata_device_info device_info = {0x0}; if (!fu_memcpy_safe((guint8 *)&device_info, sizeof(device_info), 0x0, /* dst */ buf, bufsz, offset, /* src */ sizeof(device_info), error)) { g_prefix_error(error, "no ext header: "); return FALSE; } g_ptr_array_add(self->device_infos, fu_memdup_safe((const guint8 *)&device_info, sizeof(device_info), NULL)); } } else if (fu_firmware_get_idx(fw) == MFT_EXT_TYPE_FWDATA_UPDATE) { if (bufsz != sizeof(struct mft_fwdata_update_ext)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "signed data update manifest ext was 0x%x bytes", (guint)bufsz); return FALSE; } self->has_manifest_ext = TRUE; } /* success */ return TRUE; } static gboolean fu_igsc_aux_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuIgscAuxFirmware *self = FU_IGSC_AUX_FIRMWARE(firmware); g_autoptr(FuFirmware) fw_cpd = fu_ifwi_cpd_firmware_new(); g_autoptr(FuFirmware) fw_manifest = NULL; g_autoptr(GBytes) blob_dataimg = NULL; g_autoptr(GPtrArray) imgs = NULL; /* FuIfwiFptFirmware->parse */ if (!FU_FIRMWARE_CLASS(fu_igsc_aux_firmware_parent_class) ->parse(firmware, fw, offset, flags, error)) return FALSE; /* parse data section */ blob_dataimg = fu_firmware_get_image_by_idx_bytes(firmware, FU_IFWI_FPT_FIRMWARE_IDX_SDTA, error); if (blob_dataimg == NULL) return FALSE; /* parse as CPD */ if (!fu_firmware_parse(fw_cpd, blob_dataimg, flags, error)) return FALSE; /* get manifest */ fw_manifest = fu_firmware_get_image_by_idx(fw_cpd, FU_IFWI_CPD_FIRMWARE_IDX_MANIFEST, error); if (fw_manifest == NULL) return FALSE; /* parse all the manifest extensions */ imgs = fu_firmware_get_images(fw_manifest); for (guint i = 0; i < imgs->len; i++) { FuFirmware *img = g_ptr_array_index(imgs, i); if (!fu_igsc_aux_firmware_parse_extension(self, img, error)) return FALSE; } if (!self->has_manifest_ext || self->device_infos->len == 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "missing extensions"); return FALSE; } /* parse the info block */ if (!fu_igsc_aux_firmware_parse_version(self, error)) return FALSE; /* success */ return TRUE; } static GBytes * fu_igsc_aux_firmware_write(FuFirmware *firmware, GError **error) { g_autoptr(GByteArray) buf = g_byte_array_new(); return g_byte_array_free_to_bytes(g_steal_pointer(&buf)); } static gboolean fu_igsc_aux_firmware_build(FuFirmware *firmware, XbNode *n, GError **error) { FuIgscAuxFirmware *self = FU_IGSC_AUX_FIRMWARE(firmware); const gchar *tmp; /* simple properties */ tmp = xb_node_query_text(n, "oem_version", NULL); if (tmp != NULL) { guint64 val = 0; if (!fu_strtoull(tmp, &val, 0x0, G_MAXUINT32, error)) return FALSE; self->oem_version = val; } tmp = xb_node_query_text(n, "major_version", NULL); if (tmp != NULL) { guint64 val = 0; if (!fu_strtoull(tmp, &val, 0x0, G_MAXUINT16, error)) return FALSE; self->major_version = val; } tmp = xb_node_query_text(n, "major_vcn", NULL); if (tmp != NULL) { guint64 val = 0; if (!fu_strtoull(tmp, &val, 0x0, G_MAXUINT16, error)) return FALSE; self->major_vcn = val; } /* success */ return TRUE; } static void fu_igsc_aux_firmware_init(FuIgscAuxFirmware *self) { self->device_infos = g_ptr_array_new_with_free_func(g_free); } static void fu_igsc_aux_firmware_finalize(GObject *object) { FuIgscAuxFirmware *self = FU_IGSC_AUX_FIRMWARE(object); g_ptr_array_unref(self->device_infos); G_OBJECT_CLASS(fu_igsc_aux_firmware_parent_class)->finalize(object); } static void fu_igsc_aux_firmware_class_init(FuIgscAuxFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_igsc_aux_firmware_finalize; klass_firmware->parse = fu_igsc_aux_firmware_parse; klass_firmware->write = fu_igsc_aux_firmware_write; klass_firmware->build = fu_igsc_aux_firmware_build; klass_firmware->export = fu_igsc_aux_firmware_export; } FuFirmware * fu_igsc_aux_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_IGSC_AUX_FIRMWARE, NULL)); }