mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-03 01:06:06 +00:00
338 lines
11 KiB
C
338 lines
11 KiB
C
/*
|
|
* Copyright (C) 2005 Synaptics Incorporated
|
|
* Copyright (C) 2019 Richard Hughes <richard@hughsie.com>
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1+
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <fwupdplugin.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include "fu-synaptics-cxaudio-firmware.h"
|
|
|
|
struct _FuSynapticsCxaudioFirmware {
|
|
FuSrecFirmwareClass parent_instance;
|
|
FuSynapticsCxaudioFileKind file_kind;
|
|
FuSynapticsCxaudioDeviceKind device_kind;
|
|
FuSynapticsCxaudioEepromCustomInfo cinfo;
|
|
};
|
|
|
|
G_DEFINE_TYPE(FuSynapticsCxaudioFirmware, fu_synaptics_cxaudio_firmware, FU_TYPE_SREC_FIRMWARE)
|
|
|
|
FuSynapticsCxaudioFileKind
|
|
fu_synaptics_cxaudio_firmware_get_file_type(FuSynapticsCxaudioFirmware *self)
|
|
{
|
|
g_return_val_if_fail(FU_IS_SYNAPTICS_CXAUDIO_FIRMWARE(self), 0);
|
|
return self->file_kind;
|
|
}
|
|
|
|
FuSynapticsCxaudioDeviceKind
|
|
fu_synaptics_cxaudio_firmware_get_devtype(FuSynapticsCxaudioFirmware *self)
|
|
{
|
|
g_return_val_if_fail(FU_IS_SYNAPTICS_CXAUDIO_FIRMWARE(self), 0);
|
|
return self->device_kind;
|
|
}
|
|
|
|
guint8
|
|
fu_synaptics_cxaudio_firmware_get_layout_version(FuSynapticsCxaudioFirmware *self)
|
|
{
|
|
g_return_val_if_fail(FU_IS_SYNAPTICS_CXAUDIO_FIRMWARE(self), 0);
|
|
return self->cinfo.LayoutVersion;
|
|
}
|
|
|
|
static void
|
|
fu_synaptics_cxaudio_firmware_export(FuFirmware *firmware,
|
|
FuFirmwareExportFlags flags,
|
|
XbBuilderNode *bn)
|
|
{
|
|
FuSynapticsCxaudioFirmware *self = FU_SYNAPTICS_CXAUDIO_FIRMWARE(firmware);
|
|
fu_xmlb_builder_insert_kx(bn, "file_kind", self->file_kind);
|
|
fu_xmlb_builder_insert_kx(bn, "device_kind", self->device_kind);
|
|
fu_xmlb_builder_insert_kx(bn, "layout_signature", self->cinfo.LayoutSignature);
|
|
fu_xmlb_builder_insert_kx(bn, "layout_version", self->cinfo.LayoutVersion);
|
|
if (self->cinfo.LayoutVersion >= 1) {
|
|
fu_xmlb_builder_insert_kx(bn, "vid", self->cinfo.VendorID);
|
|
fu_xmlb_builder_insert_kx(bn, "pid", self->cinfo.ProductID);
|
|
fu_xmlb_builder_insert_kx(bn, "rev", self->cinfo.RevisionID);
|
|
}
|
|
}
|
|
|
|
typedef struct {
|
|
const gchar *str;
|
|
guint32 addr;
|
|
guint32 len;
|
|
} FuSynapticsCxaudioFirmwareBadblock;
|
|
|
|
static void
|
|
fu_synaptics_cxaudio_firmware_badblock_add(GPtrArray *badblocks,
|
|
const gchar *str,
|
|
guint32 addr,
|
|
guint32 len)
|
|
{
|
|
FuSynapticsCxaudioFirmwareBadblock *bb = g_new0(FuSynapticsCxaudioFirmwareBadblock, 1);
|
|
g_debug("created reserved range @0x%04x len:0x%x: %s", addr, len, str);
|
|
bb->str = str;
|
|
bb->addr = addr;
|
|
bb->len = len;
|
|
g_ptr_array_add(badblocks, bb);
|
|
}
|
|
|
|
static gboolean
|
|
fu_synaptics_cxaudio_firmware_is_addr_valid(GPtrArray *badblocks, guint32 addr, guint32 len)
|
|
{
|
|
for (guint j = 0; j < badblocks->len; j++) {
|
|
FuSynapticsCxaudioFirmwareBadblock *bb = g_ptr_array_index(badblocks, j);
|
|
if (addr <= bb->addr + bb->len - 1 && addr + len - 1 >= bb->addr) {
|
|
g_debug("addr @0x%04x len:0x%x invalid "
|
|
"as 0x%02x->0x%02x protected: %s",
|
|
addr,
|
|
len,
|
|
bb->addr,
|
|
bb->addr + bb->len - 1,
|
|
bb->str);
|
|
return FALSE;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_synaptics_cxaudio_firmware_is_record_valid(GPtrArray *badblocks, FuSrecFirmwareRecord *rcd)
|
|
{
|
|
/* the entire record is not within an ignored range */
|
|
return fu_synaptics_cxaudio_firmware_is_addr_valid(badblocks, rcd->addr, rcd->buf->len);
|
|
}
|
|
|
|
static void
|
|
fu_synaptics_cxaudio_firmware_avoid_badblocks(GPtrArray *badblocks, GPtrArray *records)
|
|
{
|
|
g_autoptr(GPtrArray) records_new = g_ptr_array_new();
|
|
|
|
/* find records that include addresses with blocks we want to avoid */
|
|
for (guint i = 0; i < records->len; i++) {
|
|
FuSrecFirmwareRecord *rcd = g_ptr_array_index(records, i);
|
|
FuSrecFirmwareRecord *rcd1;
|
|
if (rcd->kind != FU_FIRMWARE_SREC_RECORD_KIND_S3_DATA_32)
|
|
continue;
|
|
if (fu_synaptics_cxaudio_firmware_is_record_valid(badblocks, rcd)) {
|
|
rcd1 = fu_srec_firmware_record_new(rcd->ln, rcd->kind, rcd->addr);
|
|
g_byte_array_append(rcd1->buf, rcd->buf->data, rcd->buf->len);
|
|
g_ptr_array_add(records_new, rcd1);
|
|
continue;
|
|
}
|
|
g_debug("splitting record @0x%04x len:0x%x as protected", rcd->addr, rcd->buf->len);
|
|
for (guint j = 0; j < rcd->buf->len; j++) {
|
|
if (!fu_synaptics_cxaudio_firmware_is_addr_valid(badblocks,
|
|
rcd->addr + j,
|
|
0x1))
|
|
continue;
|
|
rcd1 = fu_srec_firmware_record_new(rcd->ln, rcd->kind, rcd->addr + j);
|
|
g_byte_array_append(rcd1->buf, rcd->buf->data + j, 0x1);
|
|
g_ptr_array_add(records_new, rcd1);
|
|
}
|
|
}
|
|
|
|
/* swap the old set of records with the new records */
|
|
g_ptr_array_set_size(records, 0);
|
|
for (guint i = 0; i < records_new->len; i++) {
|
|
FuSrecFirmwareRecord *rcd1 = g_ptr_array_index(records_new, i);
|
|
g_ptr_array_add(records, rcd1);
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
fu_synaptics_cxaudio_firmware_parse(FuFirmware *firmware,
|
|
GBytes *fw,
|
|
guint64 addr_start,
|
|
guint64 addr_end,
|
|
FwupdInstallFlags flags,
|
|
GError **error)
|
|
{
|
|
FuSynapticsCxaudioFirmware *self = FU_SYNAPTICS_CXAUDIO_FIRMWARE(firmware);
|
|
GPtrArray *records = fu_srec_firmware_get_records(FU_SREC_FIRMWARE(firmware));
|
|
guint8 dev_kind_candidate = G_MAXUINT8;
|
|
g_autofree guint8 *shadow = g_malloc0(FU_SYNAPTICS_CXAUDIO_EEPROM_SHADOW_SIZE);
|
|
|
|
/* copy shadow EEPROM */
|
|
for (guint i = 0; i < records->len; i++) {
|
|
FuSrecFirmwareRecord *rcd = g_ptr_array_index(records, i);
|
|
if (rcd->kind != FU_FIRMWARE_SREC_RECORD_KIND_S3_DATA_32)
|
|
continue;
|
|
if (rcd->addr > FU_SYNAPTICS_CXAUDIO_EEPROM_SHADOW_SIZE)
|
|
continue;
|
|
if (rcd->buf->len == 0) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"record 0x%x had zero size",
|
|
i);
|
|
return FALSE;
|
|
}
|
|
if (!fu_memcpy_safe(shadow,
|
|
FU_SYNAPTICS_CXAUDIO_EEPROM_SHADOW_SIZE,
|
|
rcd->addr, /* dst */
|
|
rcd->buf->data,
|
|
rcd->buf->len,
|
|
0x0, /* src */
|
|
rcd->buf->len,
|
|
error))
|
|
return FALSE;
|
|
}
|
|
|
|
/* parse EEPROM map */
|
|
if (!fu_memcpy_safe((guint8 *)&self->cinfo,
|
|
sizeof(FuSynapticsCxaudioEepromCustomInfo),
|
|
0x0, /* dst */
|
|
shadow,
|
|
FU_SYNAPTICS_CXAUDIO_EEPROM_SHADOW_SIZE,
|
|
FU_SYNAPTICS_CXAUDIO_EEPROM_CUSTOM_INFO_OFFSET, /* src */
|
|
sizeof(FuSynapticsCxaudioEepromCustomInfo),
|
|
error))
|
|
return FALSE;
|
|
|
|
/* just layout version byte is not enough in case of old CX20562 patch
|
|
* files that could have non-zero value of the Layout version */
|
|
if (shadow[FU_SYNAPTICS_CXAUDIO_FIRMWARE_SIGNATURE_OFFSET] ==
|
|
FU_SYNAPTICS_CXAUDIO_SIGNATURE_BYTE) {
|
|
self->device_kind = FU_SYNAPTICS_CXAUDIO_DEVICE_KIND_CX2070x;
|
|
self->file_kind = FU_SYNAPTICS_CXAUDIO_FILE_KIND_CX2070X_FW;
|
|
g_debug("FileKind: CX2070x (FW)");
|
|
} else if (shadow[FU_SYNAPTICS_CXAUDIO_EEPROM_PATCH_SIGNATURE_ADDRESS] ==
|
|
FU_SYNAPTICS_CXAUDIO_SIGNATURE_PATCH_BYTE) {
|
|
self->device_kind = FU_SYNAPTICS_CXAUDIO_DEVICE_KIND_CX2070x;
|
|
self->file_kind = FU_SYNAPTICS_CXAUDIO_FILE_KIND_CX2070X_PATCH;
|
|
g_debug("FileKind: CX2070x (Patch)");
|
|
} else {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"CX20562 is not supported");
|
|
return FALSE;
|
|
}
|
|
for (guint i = records->len - 3; i < records->len; i++) {
|
|
FuSrecFirmwareRecord *rcd = g_ptr_array_index(records, i);
|
|
if (rcd->kind == FU_FIRMWARE_SREC_RECORD_KIND_S9_TERMINATION_16)
|
|
continue;
|
|
if (rcd->buf->len < 2)
|
|
continue;
|
|
if (memcmp(rcd->buf->data, "CX", 2) == 0) {
|
|
dev_kind_candidate = rcd->buf->data[2];
|
|
g_debug("DeviceKind signature suspected 0x%0x", dev_kind_candidate);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* check the signature character to see if it defines the device */
|
|
switch (dev_kind_candidate) {
|
|
case '2': /* fallthrough */ /* CX2070x */
|
|
case '4': /* CX2070x-21Z */
|
|
case '6': /* CX2070x-21Z */
|
|
self->device_kind = FU_SYNAPTICS_CXAUDIO_DEVICE_KIND_CX2070x;
|
|
self->file_kind = FU_SYNAPTICS_CXAUDIO_FILE_KIND_CX2070X_PATCH;
|
|
g_debug("FileKind: CX2070x overwritten from signature");
|
|
break;
|
|
case '3':
|
|
self->device_kind = FU_SYNAPTICS_CXAUDIO_DEVICE_KIND_CX2077x;
|
|
self->file_kind = FU_SYNAPTICS_CXAUDIO_FILE_KIND_CX2077X_PATCH;
|
|
g_debug("FileKind: CX2077x overwritten from signature");
|
|
break;
|
|
case '5':
|
|
self->device_kind = FU_SYNAPTICS_CXAUDIO_DEVICE_KIND_CX2076x;
|
|
self->file_kind = FU_SYNAPTICS_CXAUDIO_FILE_KIND_CX2076X_PATCH;
|
|
g_debug("FileKind: CX2076x overwritten from signature");
|
|
break;
|
|
case '7':
|
|
self->device_kind = FU_SYNAPTICS_CXAUDIO_DEVICE_KIND_CX2085x;
|
|
self->file_kind = FU_SYNAPTICS_CXAUDIO_FILE_KIND_CX2085X_PATCH;
|
|
g_debug("FileKind: CX2085x overwritten from signature");
|
|
break;
|
|
case '8':
|
|
self->device_kind = FU_SYNAPTICS_CXAUDIO_DEVICE_KIND_CX2089x;
|
|
self->file_kind = FU_SYNAPTICS_CXAUDIO_FILE_KIND_CX2089X_PATCH;
|
|
g_debug("FileKind: CX2089x overwritten from signature");
|
|
break;
|
|
case '9':
|
|
self->device_kind = FU_SYNAPTICS_CXAUDIO_DEVICE_KIND_CX2098x;
|
|
self->file_kind = FU_SYNAPTICS_CXAUDIO_FILE_KIND_CX2098X_PATCH;
|
|
g_debug("FileKind: CX2098x overwritten from signature");
|
|
break;
|
|
case 'A':
|
|
self->device_kind = FU_SYNAPTICS_CXAUDIO_DEVICE_KIND_CX2198x;
|
|
self->file_kind = FU_SYNAPTICS_CXAUDIO_FILE_KIND_CX2198X_PATCH;
|
|
g_debug("FileKind: CX2198x overwritten from signature");
|
|
break;
|
|
default:
|
|
/* probably future devices */
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"DeviceKind signature invalid 0x%x",
|
|
dev_kind_candidate);
|
|
return FALSE;
|
|
}
|
|
|
|
/* ignore records with protected content */
|
|
if (self->cinfo.LayoutVersion >= 1) {
|
|
g_autoptr(GPtrArray) badblocks = g_ptr_array_new_with_free_func(g_free);
|
|
|
|
/* add standard ranges to ignore */
|
|
fu_synaptics_cxaudio_firmware_badblock_add(badblocks, "test mark", 0x00BC, 0x02);
|
|
fu_synaptics_cxaudio_firmware_badblock_add(
|
|
badblocks,
|
|
"application status",
|
|
FU_SYNAPTICS_CXAUDIO_EEPROM_APP_STATUS_ADDRESS,
|
|
1);
|
|
fu_synaptics_cxaudio_firmware_badblock_add(
|
|
badblocks,
|
|
"boot bytes",
|
|
FU_SYNAPTICS_CXAUDIO_EEPROM_VALIDITY_SIGNATURE_OFFSET,
|
|
sizeof(FuSynapticsCxaudioEepromValiditySignature) + 1);
|
|
|
|
/* serial number address and also string pointer itself if set */
|
|
if (self->cinfo.SerialNumberStringAddress != 0x0) {
|
|
FuSynapticsCxaudioEepromPtr addr_tmp;
|
|
FuSynapticsCxaudioEepromPtr addr_str;
|
|
addr_tmp = FU_SYNAPTICS_CXAUDIO_EEPROM_CUSTOM_INFO_OFFSET +
|
|
G_STRUCT_OFFSET(FuSynapticsCxaudioEepromCustomInfo,
|
|
SerialNumberStringAddress);
|
|
fu_synaptics_cxaudio_firmware_badblock_add(
|
|
badblocks,
|
|
"serial number",
|
|
addr_tmp,
|
|
sizeof(FuSynapticsCxaudioEepromPtr));
|
|
memcpy(&addr_str, shadow + addr_tmp, sizeof(addr_str));
|
|
fu_synaptics_cxaudio_firmware_badblock_add(badblocks,
|
|
"serial number data",
|
|
addr_str,
|
|
shadow[addr_str]);
|
|
}
|
|
fu_synaptics_cxaudio_firmware_avoid_badblocks(badblocks, records);
|
|
}
|
|
|
|
/* this isn't used, but it seems a good thing to add */
|
|
fu_firmware_set_bytes(firmware, fw);
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
fu_synaptics_cxaudio_firmware_init(FuSynapticsCxaudioFirmware *self)
|
|
{
|
|
}
|
|
|
|
static void
|
|
fu_synaptics_cxaudio_firmware_class_init(FuSynapticsCxaudioFirmwareClass *klass)
|
|
{
|
|
FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass);
|
|
klass_firmware->parse = fu_synaptics_cxaudio_firmware_parse;
|
|
klass_firmware->export = fu_synaptics_cxaudio_firmware_export;
|
|
}
|
|
|
|
FuFirmware *
|
|
fu_synaptics_cxaudio_firmware_new(void)
|
|
{
|
|
return FU_FIRMWARE(g_object_new(FU_TYPE_SYNAPTICS_CXAUDIO_FIRMWARE, NULL));
|
|
}
|