/* * Copyright (C) 2017 VIA Corporation * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-vli-pd-common.h" #include "fu-vli-pd-firmware.h" struct _FuVliPdFirmware { FuFirmwareClass parent_instance; FuVliDeviceKind device_kind; FuVliPdHdr hdr; }; G_DEFINE_TYPE(FuVliPdFirmware, fu_vli_pd_firmware, FU_TYPE_FIRMWARE) FuVliDeviceKind fu_vli_pd_firmware_get_kind(FuVliPdFirmware *self) { g_return_val_if_fail(FU_IS_VLI_PD_FIRMWARE(self), 0); return self->device_kind; } guint16 fu_vli_pd_firmware_get_vid(FuVliPdFirmware *self) { g_return_val_if_fail(FU_IS_VLI_PD_FIRMWARE(self), 0); return GUINT16_FROM_LE(self->hdr.vid); } guint16 fu_vli_pd_firmware_get_pid(FuVliPdFirmware *self) { g_return_val_if_fail(FU_IS_VLI_PD_FIRMWARE(self), 0); return GUINT16_FROM_LE(self->hdr.pid); } static gboolean fu_vli_pd_firmware_validate_header(FuVliPdFirmware *self) { if (GUINT16_FROM_LE(self->hdr.vid) == 0x2109) return TRUE; if (GUINT16_FROM_LE(self->hdr.vid) == 0x17EF) return TRUE; if (GUINT16_FROM_LE(self->hdr.vid) == 0x2D01) return TRUE; if (GUINT16_FROM_LE(self->hdr.vid) == 0x06C4) return TRUE; return FALSE; } static void fu_vli_pd_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuVliPdFirmware *self = FU_VLI_PD_FIRMWARE(firmware); fu_xmlb_builder_insert_kv(bn, "device_kind", fu_vli_common_device_kind_to_string(self->device_kind)); fu_xmlb_builder_insert_kx(bn, "vid", fu_vli_pd_firmware_get_vid(self)); fu_xmlb_builder_insert_kx(bn, "pid", fu_vli_pd_firmware_get_pid(self)); } static gboolean fu_vli_pd_firmware_parse(FuFirmware *firmware, GBytes *fw, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error) { FuVliPdFirmware *self = FU_VLI_PD_FIRMWARE(firmware); gsize bufsz = 0; guint32 fwver; const guint8 *buf = g_bytes_get_data(fw, &bufsz); g_autofree gchar *fwver_str = NULL; /* map header from new offset location */ if (!fu_memcpy_safe((guint8 *)&self->hdr, sizeof(self->hdr), 0x0, buf, bufsz, VLI_USBHUB_PD_FLASHMAP_ADDR, sizeof(self->hdr), error)) { g_prefix_error(error, "failed to read header: "); return FALSE; } /* fall back to legacy location */ if (!fu_vli_pd_firmware_validate_header(self)) { if (!fu_memcpy_safe((guint8 *)&self->hdr, sizeof(self->hdr), 0x0, buf, bufsz, VLI_USBHUB_PD_FLASHMAP_ADDR_LEGACY, sizeof(self->hdr), error)) { g_prefix_error(error, "failed to read header: "); return FALSE; } } /* urgh, not found */ if (!fu_vli_pd_firmware_validate_header(self)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "header invalid, VID not supported"); return FALSE; } /* guess device kind from fwver */ fwver = GUINT32_FROM_BE(self->hdr.fwver); self->device_kind = fu_vli_pd_common_guess_device_kind(fwver); if (self->device_kind == FU_VLI_DEVICE_KIND_UNKNOWN) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "version invalid, using 0x%x", fwver); return FALSE; } fwver_str = fu_common_version_from_uint32(fwver, FWUPD_VERSION_FORMAT_QUAD); fu_firmware_set_version(firmware, fwver_str); fu_firmware_set_version_raw(firmware, fwver); /* check size */ if (bufsz != fu_vli_common_device_kind_get_size(self->device_kind)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "size invalid, got 0x%x expected 0x%x", (guint)bufsz, fu_vli_common_device_kind_get_size(self->device_kind)); return FALSE; } /* check CRC */ if ((flags & FWUPD_INSTALL_FLAG_IGNORE_CHECKSUM) == 0) { guint16 crc_actual; guint16 crc_file = 0x0; if (!fu_common_read_uint16_safe(buf, bufsz, bufsz - 2, &crc_file, G_LITTLE_ENDIAN, error)) { g_prefix_error(error, "failed to read file CRC: "); return FALSE; } crc_actual = fu_common_crc16(buf, bufsz - 2); if (crc_actual != crc_file) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "CRC invalid, got 0x%x expected 0x%x", crc_file, crc_actual); return FALSE; } } /* whole image */ fu_firmware_set_bytes(firmware, fw); return TRUE; } static void fu_vli_pd_firmware_init(FuVliPdFirmware *self) { fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_CHECKSUM); } static void fu_vli_pd_firmware_class_init(FuVliPdFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->parse = fu_vli_pd_firmware_parse; klass_firmware->export = fu_vli_pd_firmware_export; } FuFirmware * fu_vli_pd_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_VLI_PD_FIRMWARE, NULL)); }