fwupd/plugins/vli/fu-vli-usbhub-pd-device.c
Richard Hughes fbd8b5d325 Add fu_device_dump_firmware()
Conceptually we were trying to stuff subtly different actions into one vfunc:

 * Read firmware from the device to update the verification checksums

 * Read a firmware blob from the device for debugging

For the first action we might want to mask out the sections of the flash with
serial numbers (so the verification hashes match the ones published on the LVFS)
and for the second we want just a raw ROM file from the hardware with no
pre-processing that we can compare against an external SPI dumper.

Split out ->dump_firmware to get the raw blob, and allow plugins to also
implement ->read_firmware() if they have to mask out specific offsets or remove
specific images from the FuFirmware container.

In the common case when masking is not required, fall back to using a 'binary'
FuFirmware automatically to make most plugins simpler.
2020-09-24 10:54:27 -05:00

232 lines
7.6 KiB
C

/*
* Copyright (C) 2017-2019 VIA Corporation
* Copyright (C) 2019 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include <string.h>
#include "fu-vli-pd-common.h"
#include "fu-vli-pd-firmware.h"
#include "fu-vli-usbhub-common.h"
#include "fu-vli-usbhub-device.h"
#include "fu-vli-usbhub-pd-device.h"
struct _FuVliUsbhubPdDevice
{
FuDevice parent_instance;
FuVliPdHdr hdr;
FuVliDeviceKind device_kind;
};
G_DEFINE_TYPE (FuVliUsbhubPdDevice, fu_vli_usbhub_pd_device, FU_TYPE_DEVICE)
static void
fu_vli_usbhub_pd_device_to_string (FuDevice *device, guint idt, GString *str)
{
FuVliUsbhubPdDevice *self = FU_VLI_USBHUB_PD_DEVICE (device);
fu_common_string_append_kv (str, idt, "DeviceKind",
fu_vli_common_device_kind_to_string (self->device_kind));
fu_common_string_append_kx (str, idt, "FwOffset",
fu_vli_common_device_kind_get_offset (self->device_kind));
fu_common_string_append_kx (str, idt, "FwSize",
fu_vli_common_device_kind_get_size (self->device_kind));
}
static gboolean
fu_vli_usbhub_pd_device_probe (FuDevice *device, GError **error)
{
FuVliUsbhubPdDevice *self = FU_VLI_USBHUB_PD_DEVICE (device);
guint32 fwver;
g_autofree gchar *fwver_str = NULL;
g_autofree gchar *instance_id0 = NULL;
g_autofree gchar *instance_id1 = NULL;
g_autofree gchar *instance_id2 = NULL;
g_autofree gchar *instance_id3 = NULL;
/* get version */
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_NOT_SUPPORTED,
"PD version invalid [0x%x]", fwver);
return FALSE;
}
fu_device_set_name (device, fu_vli_common_device_kind_to_string (self->device_kind));
/* use header to populate device info */
fu_device_set_version_raw (device, fwver);
fwver_str = fu_common_version_from_uint32 (fwver, FWUPD_VERSION_FORMAT_QUAD);
fu_device_set_version (device, fwver_str);
instance_id0 = g_strdup_printf ("USB\\VID_%04X&PID_%04X&APP_%02X",
GUINT16_FROM_LE (self->hdr.vid),
GUINT16_FROM_LE (self->hdr.pid),
fwver & 0xff);
fu_device_add_instance_id (device, instance_id0);
instance_id1 = g_strdup_printf ("USB\\VID_%04X&PID_%04X&DEV_%s",
GUINT16_FROM_LE (self->hdr.vid),
GUINT16_FROM_LE (self->hdr.pid),
fu_vli_common_device_kind_to_string (self->device_kind));
fu_device_add_instance_id (device, instance_id1);
/* add standard GUIDs in order of priority */
instance_id2 = g_strdup_printf ("USB\\VID_%04X&PID_%04X",
GUINT16_FROM_LE (self->hdr.vid),
GUINT16_FROM_LE (self->hdr.pid));
fu_device_add_instance_id (device, instance_id2);
instance_id3 = g_strdup_printf ("USB\\VID_%04X",
GUINT16_FROM_LE (self->hdr.vid));
fu_device_add_instance_id_full (device, instance_id3,
FU_DEVICE_INSTANCE_FLAG_ONLY_QUIRKS);
/* these have a backup section */
if (fu_vli_common_device_kind_get_offset (self->device_kind) == VLI_USBHUB_FLASHMAP_ADDR_PD)
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_SELF_RECOVERY);
/* success */
return TRUE;
}
static FuFirmware *
fu_vli_usbhub_pd_device_prepare_firmware (FuDevice *device,
GBytes *fw,
FwupdInstallFlags flags,
GError **error)
{
FuVliUsbhubPdDevice *self = FU_VLI_USBHUB_PD_DEVICE (device);
FuVliDeviceKind device_kind;
g_autoptr(FuFirmware) firmware = fu_vli_pd_firmware_new ();
/* check is compatible with firmware */
if (!fu_firmware_parse (firmware, fw, flags, error))
return NULL;
device_kind = fu_vli_pd_firmware_get_kind (FU_VLI_PD_FIRMWARE (firmware));
if (self->device_kind != device_kind) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"firmware incompatible, got %s, expected %s",
fu_vli_common_device_kind_to_string (device_kind),
fu_vli_common_device_kind_to_string (self->device_kind));
return NULL;
}
/* we could check this against flags */
g_debug ("parsed version: %s", fu_firmware_get_version (firmware));
return g_steal_pointer (&firmware);
}
static GBytes *
fu_vli_usbhub_pd_device_dump_firmware (FuDevice *device, GError **error)
{
FuVliUsbhubDevice *parent = FU_VLI_USBHUB_DEVICE (fu_device_get_parent (device));
FuVliUsbhubPdDevice *self = FU_VLI_USBHUB_PD_DEVICE (device);
g_autoptr(FuDeviceLocker) locker = NULL;
/* open device */
locker = fu_device_locker_new (parent, error);
if (locker == NULL)
return NULL;
/* read */
fu_device_set_status (FU_DEVICE (device), FWUPD_STATUS_DEVICE_READ);
return fu_vli_device_spi_read (FU_VLI_DEVICE (parent),
fu_vli_common_device_kind_get_offset (self->device_kind),
fu_device_get_firmware_size_max (device),
error);
}
static gboolean
fu_vli_usbhub_pd_device_write_firmware (FuDevice *device,
FuFirmware *firmware,
FwupdInstallFlags flags,
GError **error)
{
FuVliUsbhubPdDevice *self = FU_VLI_USBHUB_PD_DEVICE (device);
FuVliUsbhubDevice *parent = FU_VLI_USBHUB_DEVICE (fu_device_get_parent (device));
gsize bufsz = 0;
const guint8 *buf;
g_autoptr(FuDeviceLocker) locker = NULL;
g_autoptr(GBytes) fw = NULL;
/* simple image */
fw = fu_firmware_get_image_default_bytes (firmware, error);
if (fw == NULL)
return FALSE;
/* open device */
locker = fu_device_locker_new (parent, error);
if (locker == NULL)
return FALSE;
/* erase */
fu_device_set_status (device, FWUPD_STATUS_DEVICE_ERASE);
buf = g_bytes_get_data (fw, &bufsz);
if (!fu_vli_device_spi_erase (FU_VLI_DEVICE (parent),
fu_vli_common_device_kind_get_offset (self->device_kind),
bufsz, error))
return FALSE;
/* write */
fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE);
if (!fu_vli_device_spi_write (FU_VLI_DEVICE (parent),
fu_vli_common_device_kind_get_offset (self->device_kind),
buf, bufsz, error))
return FALSE;
/* success */
return TRUE;
}
/* reboot the parent FuVliUsbhubDevice if we update the FuVliUsbhubPdDevice */
static gboolean
fu_vli_usbhub_pd_device_attach (FuDevice *device, GError **error)
{
FuDevice *parent = fu_device_get_parent (device);
g_autoptr(FuDeviceLocker) locker = fu_device_locker_new (parent, error);
if (locker == NULL)
return FALSE;
return fu_device_attach (parent, error);
}
static void
fu_vli_usbhub_pd_device_init (FuVliUsbhubPdDevice *self)
{
fu_device_add_icon (FU_DEVICE (self), "audio-card");
fu_device_set_protocol (FU_DEVICE (self), "com.vli.usbhub");
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE);
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE);
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_NO_GUID_MATCHING);
fu_device_set_version_format (FU_DEVICE (self), FWUPD_VERSION_FORMAT_QUAD);
fu_device_set_install_duration (FU_DEVICE (self), 15); /* seconds */
fu_device_set_logical_id (FU_DEVICE (self), "PD");
fu_device_set_summary (FU_DEVICE (self), "USB-C Power Delivery Device");
}
static void
fu_vli_usbhub_pd_device_class_init (FuVliUsbhubPdDeviceClass *klass)
{
FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass);
klass_device->to_string = fu_vli_usbhub_pd_device_to_string;
klass_device->probe = fu_vli_usbhub_pd_device_probe;
klass_device->attach = fu_vli_usbhub_pd_device_attach;
klass_device->dump_firmware = fu_vli_usbhub_pd_device_dump_firmware;
klass_device->write_firmware = fu_vli_usbhub_pd_device_write_firmware;
klass_device->prepare_firmware = fu_vli_usbhub_pd_device_prepare_firmware;
}
FuDevice *
fu_vli_usbhub_pd_device_new (FuVliPdHdr *hdr)
{
FuVliUsbhubPdDevice *self = g_object_new (FU_TYPE_VLI_USBHUB_PD_DEVICE, NULL);
memcpy (&self->hdr, hdr, sizeof(self->hdr));
return FU_DEVICE (self);
}