mirror of
https://git.proxmox.com/git/fwupd
synced 2025-08-03 06:09:51 +00:00
1056 lines
30 KiB
C
1056 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 <fwupdplugin.h>
|
|
|
|
#include <string.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_PASSIVE 0x0d
|
|
#define EC_GET_FW_UPDATE_STATUS 0x0f
|
|
|
|
#define EXPECTED_DOCK_INFO_SIZE 0xb7
|
|
|
|
#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 = {
|
|
.i2ctargetaddr = 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_45_TBT = 1,
|
|
MODULE_TYPE_45,
|
|
MODULE_TYPE_130_TBT,
|
|
MODULE_TYPE_130_DP,
|
|
MODULE_TYPE_130_UNIVERSAL,
|
|
MODULE_TYPE_240_TRIN,
|
|
MODULE_TYPE_210_DUAL,
|
|
MODULE_TYPE_130_USB4,
|
|
} 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;
|
|
guint8 base_type;
|
|
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);
|
|
}
|
|
|
|
gboolean
|
|
fu_dell_dock_module_is_usb4(FuDevice *device)
|
|
{
|
|
FuDellDockEc *self = FU_DELL_DOCK_EC(device);
|
|
return self->data->module_type == MODULE_TYPE_130_USB4;
|
|
}
|
|
|
|
guint8
|
|
fu_dell_dock_get_ec_type(FuDevice *device)
|
|
{
|
|
FuDellDockEc *self = FU_DELL_DOCK_EC(device);
|
|
return self->base_type;
|
|
}
|
|
|
|
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_45_TBT:
|
|
return "45 (TBT)";
|
|
case MODULE_TYPE_45:
|
|
return "45";
|
|
case MODULE_TYPE_130_TBT:
|
|
return "130 (TBT)";
|
|
case MODULE_TYPE_130_DP:
|
|
return "130 (DP)";
|
|
case MODULE_TYPE_130_UNIVERSAL:
|
|
return "130 (Universal)";
|
|
case MODULE_TYPE_240_TRIN:
|
|
return "240 (Trinity)";
|
|
case MODULE_TYPE_210_DUAL:
|
|
return "210 (Dual)";
|
|
case MODULE_TYPE_130_USB4:
|
|
return "130 (TBT4)";
|
|
default:
|
|
return "unknown";
|
|
}
|
|
}
|
|
|
|
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_130_TBT &&
|
|
self->data->module_type != MODULE_TYPE_45_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)
|
|
{
|
|
FuDellDockEc *self = FU_DELL_DOCK_EC(device);
|
|
const guint8 *result = NULL;
|
|
gsize sz = 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_TYPE, 1, &data, error)) {
|
|
g_prefix_error(error, "Failed to query dock type: ");
|
|
return FALSE;
|
|
}
|
|
result = g_bytes_get_data(data, &sz);
|
|
if (sz != 1) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"No valid dock was found");
|
|
return FALSE;
|
|
}
|
|
self->base_type = result[0];
|
|
|
|
/* this will trigger setting up all the quirks */
|
|
if (self->base_type == WD19_BASE) {
|
|
fu_device_add_instance_id(device, DELL_DOCK_EC_INSTANCE_ID);
|
|
return TRUE;
|
|
} else if (self->base_type == ATOMIC_BASE) {
|
|
fu_device_add_instance_id(device, DELL_DOCK_ATOMIC_EC_INSTANCE_ID);
|
|
return TRUE;
|
|
}
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"Invalid dock type: %x",
|
|
self->base_type);
|
|
return FALSE;
|
|
}
|
|
|
|
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_130_TBT ||
|
|
self->data->module_type == MODULE_TYPE_45_TBT ||
|
|
self->data->module_type == MODULE_TYPE_130_USB4)) {
|
|
/* 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_130_TBT ||
|
|
self->data->module_type == MODULE_TYPE_45_TBT ||
|
|
self->data->module_type == MODULE_TYPE_130_USB4) {
|
|
guint64 tmp = fu_device_get_install_duration(device);
|
|
fu_device_set_install_duration(device, tmp + 20);
|
|
}
|
|
|
|
/* 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_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"dock containing hub2 version %s is not supported",
|
|
hub_version);
|
|
return FALSE;
|
|
}
|
|
self->passive_flow = PASSIVE_REBOOT_MASK;
|
|
fu_device_add_flag(device, FWUPD_DEVICE_FLAG_SKIPS_RESTART);
|
|
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,
|
|
"A pending update will be completed "
|
|
"next time the dock is "
|
|
"unplugged from your computer");
|
|
}
|
|
} 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, "BaseType", self->base_type);
|
|
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, FuProgress *progress, 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);
|
|
guint32 cmd = EC_CMD_PASSIVE | /* cmd */
|
|
1 << 8 | /* length of data arguments */
|
|
self->passive_flow << 16;
|
|
|
|
g_return_val_if_fail(device != NULL, FALSE);
|
|
|
|
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);
|
|
}
|
|
|
|
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,
|
|
FuProgress *progress,
|
|
FwupdInstallFlags flags,
|
|
GError **error)
|
|
{
|
|
FuDellDockEc *self = FU_DELL_DOCK_EC(device);
|
|
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);
|
|
|
|
/* progress */
|
|
fu_progress_set_id(progress, G_STRLOC);
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 15);
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 85);
|
|
|
|
/* get default image */
|
|
fw = fu_firmware_get_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);
|
|
|
|
/* meet the minimum EC version */
|
|
if ((flags & FWUPD_INSTALL_FLAG_FORCE) == 0 &&
|
|
(self->data->module_type != MODULE_TYPE_130_USB4) &&
|
|
(fu_common_vercmp_full(dynamic_version,
|
|
self->ec_minimum_version,
|
|
FWUPD_VERSION_FORMAT_QUAD) < 0)) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"New EC version %s is less than minimum required %s",
|
|
dynamic_version,
|
|
self->ec_minimum_version);
|
|
return FALSE;
|
|
}
|
|
|
|
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;
|
|
|
|
/* erase */
|
|
if (!fu_dell_dock_hid_erase_bank(fu_device_get_proxy(device), 0xff, error))
|
|
return FALSE;
|
|
fu_progress_step_done(progress);
|
|
|
|
/* 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_progress_set_percentage_full(fu_progress_get_child(progress), nwritten, fw_size);
|
|
nwritten += write_size;
|
|
data += write_size;
|
|
address += write_size;
|
|
} while (nwritten < fw_size);
|
|
fu_progress_step_done(progress);
|
|
|
|
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 */
|
|
self->passive_flow |= PASSIVE_RESET_MASK;
|
|
fu_device_add_flag(device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION);
|
|
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_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)
|
|
{
|
|
FuDellDockEc *self = FU_DELL_DOCK_EC(device);
|
|
|
|
if (!fu_device_open(fu_device_get_proxy(device), error))
|
|
return FALSE;
|
|
if (!self->data->dock_type)
|
|
return fu_dell_dock_is_valid_dock(device, error);
|
|
return TRUE;
|
|
}
|
|
|
|
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_set_progress(FuDevice *self, FuProgress *progress)
|
|
{
|
|
fu_progress_set_id(progress, G_STRLOC);
|
|
fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED);
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0); /* detach */
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 100); /* write */
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0); /* attach */
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0); /* reload */
|
|
}
|
|
|
|
static void
|
|
fu_dell_dock_ec_init(FuDellDockEc *self)
|
|
{
|
|
self->data = g_new0(FuDellDockDockDataStructure, 1);
|
|
self->raw_versions = g_new0(FuDellDockDockPackageFWVersion, 1);
|
|
fu_device_add_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->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;
|
|
klass_device->set_progress = fu_dell_dock_ec_set_progress;
|
|
}
|
|
|
|
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;
|
|
}
|