fwupd/plugins/dell-dock/fu-dell-dock-i2c-ec.c
Mario Limonciello a42daefb9e dell-dock: Capture the dock SKU in metadata
Should be helpful in reproducing failure reports.
2020-05-26 10:22:58 -05:00

1026 lines
30 KiB
C

/*
* Copyright (C) 2018 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 <string.h>
#include "fu-common-version.h"
#include "fu-usb-device.h"
#include "fwupd-error.h"
#include "fu-dell-dock-common.h"
#define I2C_EC_ADDRESS 0xec
#define EC_CMD_SET_DOCK_PKG 0x01
#define EC_CMD_GET_DOCK_INFO 0x02
#define EC_CMD_GET_DOCK_DATA 0x03
#define EC_CMD_GET_DOCK_TYPE 0x05
#define EC_CMD_MODIFY_LOCK 0x0a
#define EC_CMD_RESET 0x0b
#define EC_CMD_REBOOT 0x0c
#define EC_CMD_PASSIVE 0x0d
#define EC_GET_FW_UPDATE_STATUS 0x0f
#define EXPECTED_DOCK_INFO_SIZE 0xb7
#define EXPECTED_DOCK_TYPE 0x04
#define TBT_MODE_MASK 0x01
#define BIT_SET(x,y) (x |= (1<<y))
#define BIT_CLEAR(x,y) (x &= (~(1<<y)))
#define PASSIVE_RESET_MASK 0x01
#define PASSIVE_REBOOT_MASK 0x02
#define PASSIVE_TBT_MASK 0x04
typedef enum {
FW_UPDATE_IN_PROGRESS,
FW_UPDATE_COMPLETE,
FW_UPDATE_AUTHENTICATION_FAILED,
} FuDellDockECFWUpdateStatus;
const FuHIDI2CParameters ec_base_settings = {
.i2cslaveaddr = I2C_EC_ADDRESS,
.regaddrlen = 1,
.i2cspeed = I2C_SPEED_250K,
};
typedef enum {
LOCATION_BASE,
LOCATION_MODULE,
} FuDellDockLocationEnum;
typedef enum {
FU_DELL_DOCK_DEVICETYPE_MAIN_EC = 0,
FU_DELL_DOCK_DEVICETYPE_PD = 1,
FU_DELL_DOCK_DEVICETYPE_HUB = 3,
FU_DELL_DOCK_DEVICETYPE_MST = 4,
FU_DELL_DOCK_DEVICETYPE_TBT = 5,
} FuDellDockDeviceTypeEnum;
typedef enum {
SUBTYPE_GEN2,
SUBTYPE_GEN1,
} FuDellDockHubSubTypeEnum;
typedef struct __attribute__ ((packed)) {
guint8 total_devices;
guint8 first_index;
guint8 last_index;
} FuDellDockDockInfoHeader;
typedef struct __attribute__ ((packed)) {
guint8 location;
guint8 device_type;
guint8 sub_type;
guint8 arg;
guint8 instance;
} FuDellDockEcAddrMap;
typedef struct __attribute__ ((packed)) {
FuDellDockEcAddrMap ec_addr_map;
union {
guint32 version_32;
guint8 version_8[4];
} version;
} FuDellDockEcQueryEntry;
typedef enum {
MODULE_TYPE_SINGLE = 1,
MODULE_TYPE_DUAL,
MODULE_TYPE_TBT,
} FuDellDockDockModule;
typedef struct __attribute__ ((packed)) {
guint8 dock_configuration;
guint8 dock_type;
guint16 power_supply_wattage;
guint16 module_type;
guint16 board_id;
guint16 port0_dock_status;
guint16 port1_dock_status;
guint32 dock_firmware_pkg_ver;
guint64 module_serial;
guint64 original_module_serial;
gchar service_tag[7];
gchar marketing_name[64];
} FuDellDockDockDataStructure;
typedef struct __attribute__ ((packed)) {
guint32 ec_version;
guint32 mst_version;
guint32 hub1_version;
guint32 hub2_version;
guint32 tbt_version;
guint32 pkg_version;
} FuDellDockDockPackageFWVersion;
struct _FuDellDockEc {
FuDevice parent_instance;
FuDellDockDockDataStructure *data;
FuDellDockDockPackageFWVersion *raw_versions;
gchar *ec_version;
gchar *mst_version;
gchar *tbt_version;
guint8 unlock_target;
guint8 board_min;
gchar *ec_minimum_version;
guint64 blob_version_offset;
guint8 passive_flow;
guint32 dock_unlock_status;
};
static gboolean fu_dell_dock_get_ec_status (FuDevice *device,
FuDellDockECFWUpdateStatus *status_out,
GError **error);
G_DEFINE_TYPE (FuDellDockEc, fu_dell_dock_ec, FU_TYPE_DEVICE)
/* Used to root out I2C communication problems */
static gboolean
fu_dell_dock_test_valid_byte (const guint8 *str, gint index)
{
if (str[index] == 0x00 || str[index] == 0xff)
return FALSE;
return TRUE;
}
static void
fu_dell_dock_ec_set_board (FuDevice *device)
{
FuDellDockEc *self = FU_DELL_DOCK_EC (device);
const gchar *summary = NULL;
g_autofree gchar *board_type_str = NULL;
board_type_str = g_strdup_printf ("DellDockBoard%u", self->data->board_id);
summary = fu_device_get_metadata (device, board_type_str);
if (summary != NULL)
fu_device_set_summary (device, summary);
}
const gchar *
fu_dell_dock_ec_get_module_type (FuDevice *device)
{
FuDellDockEc *self = FU_DELL_DOCK_EC (device);
switch (self->data->module_type) {
case MODULE_TYPE_SINGLE:
return "WD19";
case MODULE_TYPE_DUAL:
return "WD19DC";
case MODULE_TYPE_TBT:
return "WD19TB";
default:
return NULL;
}
}
gboolean
fu_dell_dock_ec_needs_tbt (FuDevice *device)
{
FuDellDockEc *self = FU_DELL_DOCK_EC (device);
gboolean port0_tbt_mode = self->data->port0_dock_status & TBT_MODE_MASK;
/* check for TBT module type */
if (self->data->module_type != MODULE_TYPE_TBT)
return FALSE;
g_debug ("found thunderbolt dock, port mode: %d", port0_tbt_mode);
return !port0_tbt_mode;
}
gboolean
fu_dell_dock_ec_tbt_passive (FuDevice *device)
{
FuDellDockEc *self = FU_DELL_DOCK_EC (device);
if (self->passive_flow > 0) {
self->passive_flow |= PASSIVE_TBT_MASK;
return TRUE;
}
return FALSE;
}
static const gchar*
fu_dell_dock_devicetype_to_str (guint device_type, guint sub_type)
{
switch (device_type) {
case FU_DELL_DOCK_DEVICETYPE_MAIN_EC:
return "EC";
case FU_DELL_DOCK_DEVICETYPE_MST:
return "MST";
case FU_DELL_DOCK_DEVICETYPE_TBT:
return "Thunderbolt";
case FU_DELL_DOCK_DEVICETYPE_HUB:
if (sub_type == SUBTYPE_GEN2)
return "USB 3.1 Gen2";
else if (sub_type == SUBTYPE_GEN1)
return "USB 3.1 Gen1";
return NULL;
case FU_DELL_DOCK_DEVICETYPE_PD:
return "PD";
default:
return NULL;
}
}
static gboolean
fu_dell_dock_ec_read (FuDevice *device, guint32 cmd, gsize length,
GBytes **bytes, GError **error)
{
/* The first byte of result data will be the size of the return,
hide this from callers */
guint8 result_length = length + 1;
g_autoptr(GBytes) bytes_local = NULL;
const guint8 *result;
g_return_val_if_fail (device != NULL, FALSE);
g_return_val_if_fail (fu_device_get_proxy (device) != NULL, FALSE);
g_return_val_if_fail (bytes != NULL, FALSE);
if (!fu_dell_dock_hid_i2c_read (fu_device_get_proxy (device), cmd, result_length,
&bytes_local, &ec_base_settings, error)) {
g_prefix_error (error, "read over HID-I2C failed: ");
return FALSE;
}
result = g_bytes_get_data (bytes_local, NULL);
/* first byte of result should be size of our data */
if (result[0] != length) {
g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL,
"Invalid result data: %d expected %" G_GSIZE_FORMAT,
result[0], length);
return FALSE;
}
*bytes = g_bytes_new (result + 1, length);
return TRUE;
}
static gboolean
fu_dell_dock_ec_write (FuDevice *device, gsize length, guint8 *data, GError **error)
{
g_return_val_if_fail (device != NULL, FALSE);
g_return_val_if_fail (fu_device_get_proxy (device) != NULL, FALSE);
g_return_val_if_fail (length > 1, FALSE);
if (!fu_dell_dock_hid_i2c_write (fu_device_get_proxy (device), data, length,
&ec_base_settings, error)) {
g_prefix_error (error, "write over HID-I2C failed: ");
return FALSE;
}
return TRUE;
}
static gboolean
fu_dell_dock_is_valid_dock (FuDevice *device, GError **error)
{
g_autoptr(GBytes) data = NULL;
const guint8 *result = NULL;
g_return_val_if_fail (device != NULL, FALSE);
if (!fu_dell_dock_ec_read (device, EC_CMD_GET_DOCK_TYPE, 1, &data, error)) {
g_prefix_error (error, "Failed to query dock type: ");
return FALSE;
}
result = g_bytes_get_data (data, NULL);
if (result == NULL || *result != EXPECTED_DOCK_TYPE) {
g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND,
"No valid dock was found");
return FALSE;
}
return TRUE;
}
static gboolean
fu_dell_dock_ec_get_dock_info (FuDevice *device,
GError **error)
{
FuDellDockEc *self = FU_DELL_DOCK_EC (device);
const FuDellDockDockInfoHeader *header = NULL;
const FuDellDockEcQueryEntry *device_entry = NULL;
const FuDellDockEcAddrMap *map = NULL;
const gchar *hub_version;
guint32 oldest_base_pd = 0;
g_autoptr(GBytes) data = NULL;
g_return_val_if_fail (device != NULL, FALSE);
if (!fu_dell_dock_ec_read (device, EC_CMD_GET_DOCK_INFO,
EXPECTED_DOCK_INFO_SIZE,
&data, error)) {
g_prefix_error (error, "Failed to query dock info: ");
return FALSE;
}
if (!g_bytes_get_data (data, NULL)) {
g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND,
"Failed to read dock info");
return FALSE;
}
header = (FuDellDockDockInfoHeader *) g_bytes_get_data (data, NULL);
if (!header) {
g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND,
"Failed to parse dock info");
return FALSE;
}
/* guard against EC not yet ready and fail init */
if (header->total_devices == 0) {
g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID,
"No bridge devices detected, dock may be booting up");
return FALSE;
}
g_debug ("%u devices [%u->%u]",
header->total_devices,
header->first_index,
header->last_index);
device_entry =
(FuDellDockEcQueryEntry *) ((guint8 *) header + sizeof(FuDellDockDockInfoHeader));
for (guint i = 0; i < header->total_devices; i++) {
const gchar *type_str;
map = &(device_entry[i].ec_addr_map);
type_str = fu_dell_dock_devicetype_to_str (map->device_type, map->sub_type);
if (type_str == NULL)
continue;
g_debug ("#%u: %s in %s (A: %u I: %u)", i, type_str,
(map->location == LOCATION_BASE) ? "Base" : "Module",
map->arg, map->instance);
g_debug ("\tVersion32: %08x\tVersion8: %x %x %x %x",
device_entry[i].version.version_32,
device_entry[i].version.version_8[0],
device_entry[i].version.version_8[1],
device_entry[i].version.version_8[2],
device_entry[i].version.version_8[3]);
/* BCD but guint32 */
if (map->device_type == FU_DELL_DOCK_DEVICETYPE_MAIN_EC) {
self->raw_versions->ec_version = device_entry[i].version.version_32;
self->ec_version = g_strdup_printf (
"%02x.%02x.%02x.%02x", device_entry[i].version.version_8[0],
device_entry[i].version.version_8[1],
device_entry[i].version.version_8[2],
device_entry[i].version.version_8[3]);
g_debug ("\tParsed version %s", self->ec_version);
fu_device_set_version_format (FU_DEVICE (self), FWUPD_VERSION_FORMAT_QUAD);
fu_device_set_version (FU_DEVICE (self), self->ec_version);
} else if (map->device_type == FU_DELL_DOCK_DEVICETYPE_MST) {
self->raw_versions->mst_version = device_entry[i].version.version_32;
/* guard against invalid MST version read from EC */
if (!fu_dell_dock_test_valid_byte (device_entry[i].version.version_8, 1)) {
g_warning ("[EC Bug] EC read invalid MST version %08x",
device_entry[i].version.version_32);
continue;
}
self->mst_version = g_strdup_printf ("%02x.%02x.%02x",
device_entry[i].version.version_8[1],
device_entry[i].version.version_8[2],
device_entry[i].version.version_8[3]);
g_debug ("\tParsed version %s", self->mst_version);
} else if (map->device_type == FU_DELL_DOCK_DEVICETYPE_TBT &&
self->data->module_type == MODULE_TYPE_TBT) {
/* guard against invalid Thunderbolt version read from EC */
if (!fu_dell_dock_test_valid_byte (device_entry[i].version.version_8, 2)) {
g_warning ("[EC bug] EC read invalid Thunderbolt version %08x",
device_entry[i].version.version_32);
continue;
}
self->raw_versions->tbt_version = device_entry[i].version.version_32;
self->tbt_version = g_strdup_printf ("%02x.%02x",
device_entry[i].version.version_8[2],
device_entry[i].version.version_8[3]);
g_debug ("\tParsed version %s", self->tbt_version);
} else if (map->device_type == FU_DELL_DOCK_DEVICETYPE_HUB) {
g_debug ("\thub subtype: %u", map->sub_type);
if (map->sub_type == SUBTYPE_GEN2)
self->raw_versions->hub2_version = device_entry[i].version.version_32;
else if (map->sub_type == SUBTYPE_GEN1)
self->raw_versions->hub1_version = device_entry[i].version.version_32;
} else if (map->device_type == FU_DELL_DOCK_DEVICETYPE_PD &&
map->location == LOCATION_BASE &&
map->sub_type == 0) {
if (oldest_base_pd == 0 ||
device_entry[i].version.version_32 < oldest_base_pd)
oldest_base_pd = GUINT32_TO_BE (device_entry[i].version.version_32);
g_debug ("\tParsed version: %02x.%02x.%02x.%02x",
device_entry[i].version.version_8[0],
device_entry[i].version.version_8[1],
device_entry[i].version.version_8[2],
device_entry[i].version.version_8[3]);
}
}
/* Thunderbolt SKU takes a little longer */
if (self->data->module_type == MODULE_TYPE_TBT) {
guint64 tmp = fu_device_get_install_duration (device);
fu_device_set_install_duration (device, tmp + 20);
}
/* minimum EC version this code will support */
if (fu_common_vercmp_full (self->ec_version,
self->ec_minimum_version,
FWUPD_VERSION_FORMAT_QUAD) < 0) {
g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED,
"dock containing EC version %s is not supported",
self->ec_version);
return FALSE;
}
fu_device_set_version_lowest (device, self->ec_minimum_version);
/* Determine if the passive flow should be used when flashing */
hub_version = fu_device_get_version (fu_device_get_proxy (device));
if (fu_common_vercmp_full (hub_version, "1.42", FWUPD_VERSION_FORMAT_PAIR) >= 0) {
g_debug ("using passive flow");
self->passive_flow = PASSIVE_REBOOT_MASK;
fu_device_add_flag (device, FWUPD_DEVICE_FLAG_SKIPS_RESTART);
} else {
g_debug ("not using passive flow (EC: %s Hub2: %s)",
self->ec_version, hub_version);
}
return TRUE;
}
static gboolean
fu_dell_dock_ec_get_dock_data (FuDevice *device,
GError **error)
{
FuDellDockEc *self = FU_DELL_DOCK_EC (device);
g_autoptr(GBytes) data = NULL;
g_autoptr(GString) name = NULL;
gchar service_tag[8] = {0x00};
const guint8 *result;
gsize length = sizeof(FuDellDockDockDataStructure);
g_autofree gchar *bundled_serial = NULL;
FuDellDockECFWUpdateStatus status;
g_return_val_if_fail (device != NULL, FALSE);
if (!fu_dell_dock_ec_read (device, EC_CMD_GET_DOCK_DATA, length,
&data, error)) {
g_prefix_error (error, "Failed to query dock info: ");
return FALSE;
}
result = g_bytes_get_data (data, NULL);
if (result == NULL) {
g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND,
"Failed to read dock data");
return FALSE;
}
if (g_bytes_get_size (data) != length) {
g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE,
"Unexpected dock data size %" G_GSIZE_FORMAT,
g_bytes_get_size (data));
return FALSE;
}
memcpy (self->data, result, length);
/* guard against EC not yet ready and fail init */
name = g_string_new (self->data->marketing_name);
if (name->len > 0)
fu_device_set_name (device, name->str);
else
g_warning ("[EC bug] Invalid dock name detected");
if (self->data->module_type >= 0xfe)
g_warning ("[EC bug] Invalid module type 0x%02x",
self->data->module_type);
/* set serial number */
memcpy (service_tag, self->data->service_tag, 7);
bundled_serial = g_strdup_printf ("%s/%08" G_GUINT64_FORMAT,
service_tag,
self->data->module_serial);
fu_device_set_serial (device, bundled_serial);
/* copy this for being able to send in next commit transaction */
self->raw_versions->pkg_version = self->data->dock_firmware_pkg_ver;
/* read if passive update pending */
if (!fu_dell_dock_get_ec_status (device, &status, error))
return FALSE;
/* make sure this hardware spin matches our expecations */
if (self->data->board_id >= self->board_min) {
if (status != FW_UPDATE_IN_PROGRESS) {
fu_dell_dock_ec_set_board (device);
fu_device_add_flag (device, FWUPD_DEVICE_FLAG_UPDATABLE);
} else {
fu_device_add_flag (device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION);
fu_device_set_update_error (device, "An update is pending "
"next time the dock is "
"unplugged");
}
} else {
g_warning ("This utility does not support this board, disabling updates for %s",
fu_device_get_name (device));
}
return TRUE;
}
static void
fu_dell_dock_ec_to_string (FuDevice *device, guint idt, GString *str)
{
FuDellDockEc *self = FU_DELL_DOCK_EC (device);
gchar service_tag[8] = {0x00};
fu_common_string_append_ku (str, idt, "BoardId", self->data->board_id);
fu_common_string_append_ku (str, idt, "PowerSupply", self->data->power_supply_wattage);
fu_common_string_append_kx (str, idt, "StatusPort0", self->data->port0_dock_status);
fu_common_string_append_kx (str, idt, "StatusPort1", self->data->port1_dock_status);
memcpy (service_tag, self->data->service_tag, 7);
fu_common_string_append_kv (str, idt, "ServiceTag", service_tag);
fu_common_string_append_ku (str, idt, "Configuration", self->data->dock_configuration);
fu_common_string_append_kx (str, idt, "PackageFirmwareVersion", self->data->dock_firmware_pkg_ver);
fu_common_string_append_ku (str, idt, "ModuleSerial", self->data->module_serial);
fu_common_string_append_ku (str, idt, "OriginalModuleSerial", self->data->original_module_serial);
fu_common_string_append_ku (str, idt, "Type", self->data->dock_type);
fu_common_string_append_kx (str, idt, "ModuleType", self->data->module_type);
fu_common_string_append_kv (str, idt, "MinimumEc", self->ec_minimum_version);
fu_common_string_append_ku (str, idt, "PassiveFlow", self->passive_flow);
}
gboolean
fu_dell_dock_ec_modify_lock (FuDevice *device,
guint8 target,
gboolean unlocked,
GError **error)
{
FuDellDockEc *self = FU_DELL_DOCK_EC (device);
guint32 cmd;
g_return_val_if_fail (device != NULL, FALSE);
g_return_val_if_fail (target != 0, FALSE);
cmd = EC_CMD_MODIFY_LOCK | /* cmd */
2 << 8 | /* length of data arguments */
target << 16 | /* device to operate on */
unlocked << 24; /* unlock/lock */
if (!fu_dell_dock_ec_write (device, 4, (guint8 *) &cmd, error)) {
g_prefix_error (error, "Failed to unlock device %d: ", target);
return FALSE;
}
g_debug ("Modified lock for %d to %d through %s (%s)",
target, unlocked,
fu_device_get_name (device),
fu_device_get_id (device));
if (unlocked)
BIT_SET (self->dock_unlock_status, target);
else
BIT_CLEAR (self->dock_unlock_status, target);
g_debug ("current overall unlock status: 0x%08x", self->dock_unlock_status);
return TRUE;
}
static gboolean
fu_dell_dock_ec_reset (FuDevice *device, GError **error)
{
guint16 cmd = EC_CMD_RESET;
g_return_val_if_fail (device != NULL, FALSE);
return fu_dell_dock_ec_write (device, 2, (guint8 *) &cmd, error);
}
static gboolean
fu_dell_dock_ec_activate (FuDevice *device, GError **error)
{
FuDellDockECFWUpdateStatus status;
/* read if passive update pending */
if (!fu_dell_dock_get_ec_status (device, &status, error))
return FALSE;
if (status != FW_UPDATE_IN_PROGRESS) {
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
"No firmware update pending for %s",
fu_device_get_name (device));
return FALSE;
}
return fu_dell_dock_ec_reset (device, error);
}
gboolean
fu_dell_dock_ec_reboot_dock (FuDevice *device, GError **error)
{
FuDellDockEc *self = FU_DELL_DOCK_EC (device);
g_return_val_if_fail (device != NULL, FALSE);
if (self->passive_flow > 0) {
guint32 cmd = EC_CMD_PASSIVE | /* cmd */
1 << 8 | /* length of data arguments */
self->passive_flow << 16;
g_debug ("activating passive flow (%x) for %s",
self->passive_flow,
fu_device_get_name (device));
return fu_dell_dock_ec_write (device, 3, (guint8 *) &cmd, error);
} else {
guint16 cmd = EC_CMD_REBOOT;
g_debug ("rebooting %s", fu_device_get_name (device));
return fu_dell_dock_ec_write (device, 2, (guint8 *) &cmd, error);
}
return TRUE;
}
static gboolean
fu_dell_dock_get_ec_status (FuDevice *device,
FuDellDockECFWUpdateStatus *status_out,
GError **error)
{
g_autoptr(GBytes) data = NULL;
const guint8 *result = NULL;
g_return_val_if_fail (device != NULL, FALSE);
g_return_val_if_fail (status_out != NULL, FALSE);
if (!fu_dell_dock_ec_read (device, EC_GET_FW_UPDATE_STATUS, 1,
&data, error)) {
g_prefix_error (error, "Failed to read FW update status: ");
return FALSE;
}
result = g_bytes_get_data (data, NULL);
if (!result) {
g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND,
"Failed to read FW update status");
return FALSE;
}
*status_out = *result;
return TRUE;
}
const gchar*
fu_dell_dock_ec_get_tbt_version (FuDevice *device)
{
FuDellDockEc *self = FU_DELL_DOCK_EC (device);
return self->tbt_version;
}
const gchar*
fu_dell_dock_ec_get_mst_version (FuDevice *device)
{
FuDellDockEc *self = FU_DELL_DOCK_EC (device);
return self->mst_version;
}
guint32
fu_dell_dock_ec_get_status_version (FuDevice *device)
{
FuDellDockEc *self = FU_DELL_DOCK_EC (device);
return self->raw_versions->pkg_version;
}
gboolean
fu_dell_dock_ec_commit_package (FuDevice *device, GBytes *blob_fw,
GError **error)
{
FuDellDockEc *self = FU_DELL_DOCK_EC (device);
gsize length = 0;
const guint8 *data = g_bytes_get_data (blob_fw, &length);
g_autofree guint8 *payload = g_malloc0 (length + 2);
g_return_val_if_fail (device != NULL, FALSE);
g_return_val_if_fail (blob_fw != NULL, FALSE);
if (length != sizeof(FuDellDockDockPackageFWVersion)) {
g_set_error (error, G_IO_ERR, G_IO_ERROR_INVALID_DATA,
"Invalid package size %" G_GSIZE_FORMAT,
length);
return FALSE;
}
memcpy (self->raw_versions, data, length);
g_debug ("Committing (%zu) bytes ", sizeof(FuDellDockDockPackageFWVersion));
g_debug ("\tec_version: %x", self->raw_versions->ec_version);
g_debug ("\tmst_version: %x", self->raw_versions->mst_version);
g_debug ("\thub1_version: %x", self->raw_versions->hub1_version);
g_debug ("\thub2_version: %x", self->raw_versions->hub2_version);
g_debug ("\ttbt_version: %x", self->raw_versions->tbt_version);
g_debug ("\tpkg_version: %x", self->raw_versions->pkg_version);
payload [0] = EC_CMD_SET_DOCK_PKG;
payload [1] = length;
memcpy (payload + 2, data, length);
if (!fu_dell_dock_ec_write (device, length + 2, payload, error)) {
g_prefix_error (error, "Failed to query dock info: ");
return FALSE;
}
return TRUE;
}
static gboolean
fu_dell_dock_ec_write_fw (FuDevice *device,
FuFirmware *firmware,
FwupdInstallFlags flags,
GError **error)
{
FuDellDockEc *self = FU_DELL_DOCK_EC (device);
FuDellDockECFWUpdateStatus status = FW_UPDATE_IN_PROGRESS;
guint8 progress1 = 0, progress0 = 0;
gsize fw_size = 0;
const guint8 *data;
gsize write_size = 0;
gsize nwritten = 0;
guint32 address = 0 | 0xff << 24;
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_image_default_bytes (firmware, error);
if (fw == NULL)
return FALSE;
data = g_bytes_get_data (fw, &fw_size);
write_size = (fw_size / HIDI2C_MAX_WRITE) >= 1 ? HIDI2C_MAX_WRITE : fw_size;
dynamic_version = g_strndup ((gchar *) data + self->blob_version_offset, 11);
g_debug ("writing EC firmware version %s", dynamic_version);
if (!fu_dell_dock_ec_modify_lock (device, self->unlock_target, TRUE, error))
return FALSE;
if (!fu_dell_dock_hid_raise_mcu_clock (fu_device_get_proxy (device), TRUE, error))
return FALSE;
fu_device_set_status (device, FWUPD_STATUS_DEVICE_ERASE);
if (!fu_dell_dock_hid_erase_bank (fu_device_get_proxy (device), 0xff, error))
return FALSE;
fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE);
do {
/* last packet */
if (fw_size - nwritten < write_size)
write_size = fw_size - nwritten;
if (!fu_dell_dock_hid_write_flash (fu_device_get_proxy (device), address, data,
write_size, error)) {
g_prefix_error (error, "write over HID failed: ");
return FALSE;
}
fu_device_set_progress_full (device, nwritten, fw_size);
nwritten += write_size;
data += write_size;
address += write_size;
} while (nwritten < fw_size);
if (!fu_dell_dock_hid_raise_mcu_clock (fu_device_get_proxy (device), FALSE, error))
return FALSE;
/* dock will reboot to re-read; this is to appease the daemon */
fu_device_set_version_format (device, FWUPD_VERSION_FORMAT_QUAD);
fu_device_set_version (device, dynamic_version);
/* activate passive behavior */
if (self->passive_flow)
self->passive_flow |= PASSIVE_RESET_MASK;
if (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_SKIPS_RESTART)) {
g_debug ("Skipping EC reset per quirk request");
fu_device_add_flag (device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION);
return TRUE;
}
if (!fu_dell_dock_ec_reset (device, error))
return FALSE;
/* notify daemon that this device will need to replug */
fu_dell_dock_will_replug (device);
/* poll for completion status */
fu_device_set_status (device, FWUPD_STATUS_DEVICE_BUSY);
while (status != FW_UPDATE_COMPLETE) {
g_autoptr(GError) error_local = NULL;
if (!fu_dell_dock_hid_get_ec_status (fu_device_get_proxy (device), &progress1,
&progress0, error)) {
g_prefix_error (error, "Failed to read scratch: ");
return FALSE;
}
g_debug ("Read %u and %u from scratch", progress1, progress0);
if (progress0 > 100)
progress0 = 100;
fu_device_set_progress_full (device, progress0, 100);
/* This is expected to fail until update is done */
if (!fu_dell_dock_get_ec_status (device, &status,
&error_local)) {
g_debug ("Flash EC Received result: %s (status %u)",
error_local->message, status);
return TRUE;
}
if (status == FW_UPDATE_AUTHENTICATION_FAILED) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"invalid EC firmware image");
return FALSE;
}
}
fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART);
return TRUE;
}
static gboolean
fu_dell_dock_ec_set_quirk_kv (FuDevice *device,
const gchar *key,
const gchar *value,
GError **error)
{
FuDellDockEc *self = FU_DELL_DOCK_EC (device);
if (g_strcmp0 (key, "DellDockUnlockTarget") == 0) {
guint64 tmp = fu_common_strtoull (value);
if (tmp < G_MAXUINT8) {
self->unlock_target = tmp;
return TRUE;
}
g_set_error_literal (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"invalid DellDockUnlockTarget");
return FALSE;
}
if (g_strcmp0 (key, "DellDockBoardMin") == 0) {
guint64 tmp = fu_common_strtoull (value);
if (tmp < G_MAXUINT8) {
self->board_min = tmp;
return TRUE;
}
g_set_error_literal (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"invalid DellDockBoardMin");
return FALSE;
}
if (g_strcmp0 (key, "DellDockVersionLowest") == 0) {
self->ec_minimum_version = g_strdup (value);
return TRUE;
}
if (g_str_has_prefix (key, "DellDockBoard")) {
fu_device_set_metadata (device, key, value);
return TRUE;
}
if (g_strcmp0 (key, "DellDockBlobVersionOffset") == 0) {
self->blob_version_offset = fu_common_strtoull (value);
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_ec_probe (FuDevice *device, GError **error)
{
/* this will trigger setting up all the quirks */
fu_device_add_instance_id (device, DELL_DOCK_EC_INSTANCE_ID);
return TRUE;
}
static gboolean
fu_dell_dock_ec_query (FuDevice *device, GError **error)
{
if (!fu_dell_dock_ec_get_dock_data (device, error))
return FALSE;
return fu_dell_dock_ec_get_dock_info (device, error);
}
static gboolean
fu_dell_dock_ec_setup (FuDevice *device, GError **error)
{
g_autoptr(GError) error_local = NULL;
GPtrArray *children;
/* if query looks bad, wait a few seconds and retry */
if (!fu_dell_dock_ec_query (device, &error_local)) {
if (g_error_matches (error_local,
FWUPD_ERROR,
FWUPD_ERROR_SIGNATURE_INVALID)) {
g_warning ("%s", error_local->message);
g_usleep (2 * G_USEC_PER_SEC);
if (!fu_dell_dock_ec_query (device, error))
return FALSE;
} else {
g_propagate_error (error, g_steal_pointer (&error_local));
return FALSE;
}
}
/* call setup on all the children we produced */
children = fu_device_get_children (device);
for (guint i=0 ; i < children->len; i++) {
FuDevice *child = g_ptr_array_index (children, i);
g_autoptr(FuDeviceLocker) locker = NULL;
g_debug ("setup %s",
fu_device_get_name (child));
locker = fu_device_locker_new (child, error);
if (locker == NULL)
return FALSE;
}
return TRUE;
}
static gboolean
fu_dell_dock_ec_open (FuDevice *device, GError **error)
{
if (!fu_device_open (fu_device_get_proxy (device), error))
return FALSE;
return fu_dell_dock_is_valid_dock (device, error);
}
static gboolean
fu_dell_dock_ec_close (FuDevice *device, GError **error)
{
return fu_device_close (fu_device_get_proxy (device), error);
}
static void
fu_dell_dock_ec_finalize (GObject *object)
{
FuDellDockEc *self = FU_DELL_DOCK_EC (object);
g_free (self->ec_version);
g_free (self->mst_version);
g_free (self->tbt_version);
g_free (self->data);
g_free (self->raw_versions);
g_free (self->ec_minimum_version);
G_OBJECT_CLASS (fu_dell_dock_ec_parent_class)->finalize (object);
}
static void
fu_dell_dock_ec_init (FuDellDockEc *self)
{
self->data = g_new0 (FuDellDockDockDataStructure, 1);
self->raw_versions = g_new0 (FuDellDockDockPackageFWVersion, 1);
fu_device_set_protocol (FU_DEVICE (self), "com.dell.dock");
}
static void
fu_dell_dock_ec_class_init (FuDellDockEcClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass);
object_class->finalize = fu_dell_dock_ec_finalize;
klass_device->activate = fu_dell_dock_ec_activate;
klass_device->to_string = fu_dell_dock_ec_to_string;
klass_device->probe = fu_dell_dock_ec_probe;
klass_device->setup = fu_dell_dock_ec_setup;
klass_device->open = fu_dell_dock_ec_open;
klass_device->close = fu_dell_dock_ec_close;
klass_device->write_firmware = fu_dell_dock_ec_write_fw;
klass_device->set_quirk_kv = fu_dell_dock_ec_set_quirk_kv;
}
FuDellDockEc *
fu_dell_dock_ec_new (FuDevice *proxy)
{
FuDellDockEc *self = NULL;
self = g_object_new (FU_TYPE_DELL_DOCK_EC, NULL);
fu_device_set_proxy (FU_DEVICE (self), proxy);
fu_device_set_physical_id (FU_DEVICE (self),
fu_device_get_physical_id (proxy));
fu_device_set_logical_id (FU_DEVICE (self), "ec");
return self;
}