fwupd/plugins/dell-dock/fu-dell-dock-i2c-tbt.c
2022-03-16 09:18:29 +00:00

302 lines
8.7 KiB
C

/*
* Copyright (C) 2019 Intel Corporation.
* Copyright (C) 2019 Dell Inc.
* All rights reserved.
*
* This software and associated documentation (if any) is furnished
* under a license and may only be used or copied in accordance
* with the terms of the license.
*
* This file is provided under a dual MIT/LGPLv2 license. When using or
* redistributing this file, you may do so under either license.
* Dell Chooses the MIT license part of Dual MIT/LGPLv2 license agreement.
*
* SPDX-License-Identifier: LGPL-2.1+ OR MIT
*/
#include "config.h"
#include <fwupdplugin.h>
#include <string.h>
#include "fu-dell-dock-common.h"
#define I2C_TBT_ADDRESS 0xa2
const FuHIDI2CParameters tbt_base_settings = {
.i2ctargetaddr = I2C_TBT_ADDRESS,
.regaddrlen = 1,
.i2cspeed = I2C_SPEED_400K,
};
/* TR Device ID */
#define PID_OFFSET 0x05
#define INTEL_PID 0x15ef
/* earlier versions have bugs */
#define MIN_NVM "36.01"
struct _FuDellDockTbt {
FuDevice parent_instance;
guint8 unlock_target;
guint64 blob_major_offset;
guint64 blob_minor_offset;
gchar *hub_minimum_version;
};
G_DEFINE_TYPE(FuDellDockTbt, fu_dell_dock_tbt, FU_TYPE_DEVICE)
static gboolean
fu_dell_dock_tbt_write_fw(FuDevice *device,
FuFirmware *firmware,
FuProgress *progress,
FwupdInstallFlags flags,
GError **error)
{
FuDellDockTbt *self = FU_DELL_DOCK_TBT(device);
guint32 start_offset = 0;
gsize image_size = 0;
const guint8 *buffer;
guint16 target_system = 0;
g_autoptr(GTimer) timer = g_timer_new();
g_autofree gchar *dynamic_version = NULL;
g_autoptr(GBytes) fw = NULL;
g_return_val_if_fail(device != NULL, FALSE);
g_return_val_if_fail(FU_IS_FIRMWARE(firmware), FALSE);
/* get default image */
fw = fu_firmware_get_bytes(firmware, error);
if (fw == NULL)
return FALSE;
buffer = g_bytes_get_data(fw, &image_size);
dynamic_version = g_strdup_printf("%02x.%02x",
buffer[self->blob_major_offset],
buffer[self->blob_minor_offset]);
g_debug("writing Thunderbolt firmware version %s", dynamic_version);
g_debug("Total Image size: %" G_GSIZE_FORMAT, image_size);
memcpy(&start_offset, buffer, sizeof(guint32));
g_debug("Header size 0x%x", start_offset);
if (start_offset > image_size) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"Image header is too big (0x%x)",
start_offset);
return FALSE;
}
memcpy(&target_system, buffer + start_offset + PID_OFFSET, sizeof(guint16));
if (target_system != INTEL_PID) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"Image is not intended for this system (0x%x)",
target_system);
return FALSE;
}
buffer += start_offset;
image_size -= start_offset;
g_debug("waking Thunderbolt controller");
if (!fu_dell_dock_hid_tbt_wake(fu_device_get_proxy(device), &tbt_base_settings, error))
return FALSE;
g_usleep(2000000);
fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_WRITE);
for (guint i = 0; i < image_size; i += HIDI2C_MAX_WRITE, buffer += HIDI2C_MAX_WRITE) {
guint8 write_size = (image_size - i) > HIDI2C_MAX_WRITE ? HIDI2C_MAX_WRITE
: (image_size - i);
if (!fu_dell_dock_hid_tbt_write(fu_device_get_proxy(device),
i,
buffer,
write_size,
&tbt_base_settings,
error))
return FALSE;
fu_progress_set_percentage_full(progress, i, image_size);
}
g_debug("writing took %f seconds", g_timer_elapsed(timer, NULL));
fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_BUSY);
if (fu_dell_dock_ec_tbt_passive(fu_device_get_parent(device))) {
g_debug("using passive flow for Thunderbolt");
} else if (!fu_dell_dock_hid_tbt_authenticate(fu_device_get_proxy(device),
&tbt_base_settings,
error)) {
g_prefix_error(error, "failed to authenticate: ");
return FALSE;
}
/* dock will reboot to re-read; this is to appease the daemon */
fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_PAIR);
fu_device_set_version(device, dynamic_version);
return TRUE;
}
static gboolean
fu_dell_dock_tbt_set_quirk_kv(FuDevice *device,
const gchar *key,
const gchar *value,
GError **error)
{
FuDellDockTbt *self = FU_DELL_DOCK_TBT(device);
guint64 tmp = 0;
if (g_strcmp0(key, "DellDockUnlockTarget") == 0) {
if (!fu_common_strtoull_full(value, &tmp, 0, G_MAXUINT8, error))
return FALSE;
self->unlock_target = tmp;
return TRUE;
} else if (g_strcmp0(key, "DellDockInstallDurationI2C") == 0) {
if (!fu_common_strtoull_full(value, &tmp, 0, 60 * 60 * 24, error))
return FALSE;
fu_device_set_install_duration(device, tmp);
return TRUE;
} else if (g_strcmp0(key, "DellDockHubVersionLowest") == 0) {
self->hub_minimum_version = g_strdup(value);
return TRUE;
} else if (g_strcmp0(key, "DellDockBlobMajorOffset") == 0) {
if (!fu_common_strtoull_full(value, &tmp, 0, G_MAXUINT32, error))
return FALSE;
self->blob_major_offset = tmp;
return TRUE;
} else if (g_strcmp0(key, "DellDockBlobMinorOffset") == 0) {
if (!fu_common_strtoull_full(value, &tmp, 0, G_MAXUINT32, error))
return FALSE;
self->blob_minor_offset = tmp;
return TRUE;
}
/* failed */
g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "quirk key not supported");
return FALSE;
}
static gboolean
fu_dell_dock_tbt_setup(FuDevice *device, GError **error)
{
FuDellDockTbt *self = FU_DELL_DOCK_TBT(device);
FuDevice *parent;
const gchar *version;
const gchar *hub_version;
/* set version from EC if we know it */
parent = fu_device_get_parent(device);
version = fu_dell_dock_ec_get_tbt_version(parent);
if (version != NULL) {
fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_PAIR);
fu_device_set_version(device, version);
}
/* minimum version of NVM that supports this feature */
if (version == NULL ||
fu_common_vercmp_full(version, MIN_NVM, FWUPD_VERSION_FORMAT_PAIR) < 0) {
fu_device_set_update_error(
device,
"Updates over I2C are disabled due to insuffient NVM version");
return TRUE;
}
/* minimum Hub2 version that supports this feature */
hub_version = fu_device_get_version(fu_device_get_proxy(device));
if (fu_common_vercmp_full(hub_version,
self->hub_minimum_version,
FWUPD_VERSION_FORMAT_PAIR) < 0) {
fu_device_set_update_error(
device,
"Updates over I2C are disabled due to insufficient USB 3.1 G2 hub version");
return TRUE;
}
return TRUE;
}
static gboolean
fu_dell_dock_tbt_probe(FuDevice *device, GError **error)
{
FuDevice *parent = fu_device_get_parent(device);
fu_device_set_physical_id(device, fu_device_get_physical_id(parent));
fu_device_set_logical_id(FU_DEVICE(device), "tbt");
fu_device_add_instance_id(device, DELL_DOCK_TBT_INSTANCE_ID);
/* this is true only when connected to non-thunderbolt port */
fu_device_add_flag(device, FWUPD_DEVICE_FLAG_USABLE_DURING_UPDATE);
return TRUE;
}
static gboolean
fu_dell_dock_tbt_open(FuDevice *device, GError **error)
{
FuDellDockTbt *self = FU_DELL_DOCK_TBT(device);
g_return_val_if_fail(self->unlock_target != 0, FALSE);
if (!fu_device_open(fu_device_get_proxy(device), error))
return FALSE;
/* adjust to access controller */
if (!fu_dell_dock_set_power(device, self->unlock_target, TRUE, error))
return FALSE;
return TRUE;
}
static gboolean
fu_dell_dock_tbt_close(FuDevice *device, GError **error)
{
FuDellDockTbt *self = FU_DELL_DOCK_TBT(device);
/* adjust to access controller */
if (!fu_dell_dock_set_power(device, self->unlock_target, FALSE, error))
return FALSE;
return fu_device_close(fu_device_get_proxy(device), error);
}
static void
fu_dell_dock_tbt_finalize(GObject *object)
{
FuDellDockTbt *self = FU_DELL_DOCK_TBT(object);
g_free(self->hub_minimum_version);
G_OBJECT_CLASS(fu_dell_dock_tbt_parent_class)->finalize(object);
}
static void
fu_dell_dock_tbt_init(FuDellDockTbt *self)
{
fu_device_add_protocol(FU_DEVICE(self), "com.intel.thunderbolt");
fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE);
fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD);
}
static void
fu_dell_dock_tbt_class_init(FuDellDockTbtClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS(klass);
FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass);
object_class->finalize = fu_dell_dock_tbt_finalize;
klass_device->probe = fu_dell_dock_tbt_probe;
klass_device->setup = fu_dell_dock_tbt_setup;
klass_device->open = fu_dell_dock_tbt_open;
klass_device->close = fu_dell_dock_tbt_close;
klass_device->write_firmware = fu_dell_dock_tbt_write_fw;
klass_device->set_quirk_kv = fu_dell_dock_tbt_set_quirk_kv;
}
FuDellDockTbt *
fu_dell_dock_tbt_new(FuDevice *proxy)
{
FuContext *ctx = fu_device_get_context(proxy);
FuDellDockTbt *self = g_object_new(FU_TYPE_DELL_DOCK_TBT, "context", ctx, NULL);
fu_device_set_proxy(FU_DEVICE(self), proxy);
return self;
}