dell-dock: Add support for flashing Thunderbolt over I2C

This uses an API in the Realtek USB 3.1G2 controller to perform
the flash procedure.
This commit is contained in:
Mario Limonciello 2019-01-11 08:42:52 -06:00 committed by Richard Hughes
parent 367f4590d6
commit d6e453b0ba
11 changed files with 548 additions and 4 deletions

View File

@ -114,3 +114,8 @@ FirmwareSizeMin=0x40000
FirmwareSizeMax=0x80000
Flags = require-ac
InstallDuration = 22
DellDockInstallDurationI2C = 181
DellDockUnlockTarget = 10
DellDockHubVersionLowest = 1.31
DellDockBlobMajorOffset = 0x400a
DellDockBlobMinorOffset = 0x4009

View File

@ -21,6 +21,7 @@
#include "fu-device.h"
#include "fu-dell-dock-i2c-ec.h"
#include "fu-dell-dock-i2c-mst.h"
#include "fu-dell-dock-i2c-tbt.h"
#include "fu-dell-dock-hub.h"
#include "fu-dell-dock-hid.h"
#include "fu-dell-dock-status.h"

View File

@ -17,6 +17,7 @@
#include "config.h"
#include <string.h>
#include <errno.h>
#include "fu-usb-device.h"
#include "fwupd-error.h"
@ -25,6 +26,7 @@
#define HIDI2C_MAX_REGISTER 4
#define HID_MAX_RETRIES 5
#define TBT_MAX_RETRIES 2
#define HIDI2C_TRANSACTION_TIMEOUT 2000
#define HUB_CMD_READ_DATA 0xC0
@ -36,6 +38,11 @@
#define HUB_EXT_I2C_READ 0xD6
#define HUB_EXT_VERIFYUPDATE 0xD9
#define HUB_EXT_ERASEBANK 0xE8
#define HUB_EXT_WRITE_TBT_FLASH 0xFF
#define TBT_COMMAND_WAKEUP 0x00000000
#define TBT_COMMAND_AUTHENTICATE 0xFFFFFFFF
#define TBT_COMMAND_AUTHENTICATE_STATUS 0xFFFFFFFE
typedef struct __attribute__ ((packed)) {
guint8 cmd;
@ -55,6 +62,20 @@ typedef struct __attribute__ ((packed)) {
guint8 data[192];
} FuHIDCmdBuffer;
typedef struct __attribute__ ((packed)) {
guint8 cmd;
guint8 ext;
guint8 i2cslaveaddr;
guint8 i2cspeed;
union {
guint32 startaddress;
guint32 tbt_command;
};
guint8 bufferlen;
guint8 extended_cmdarea[55];
guint8 data[192];
} FuTbtCmdBuffer;
static gboolean
fu_dell_dock_hid_set_report (FuDevice *self,
guint8 *outbuffer,
@ -374,3 +395,142 @@ fu_dell_dock_hid_i2c_read (FuDevice *self,
return TRUE;
}
gboolean
fu_dell_dock_hid_tbt_wake (FuDevice *self,
const FuHIDI2CParameters *parameters,
GError **error)
{
FuTbtCmdBuffer cmd_buffer = {
.cmd = HUB_CMD_READ_DATA, /* special write command that reads status result */
.ext = HUB_EXT_WRITE_TBT_FLASH,
.i2cslaveaddr = parameters->i2cslaveaddr,
.i2cspeed = parameters->i2cspeed, /* unlike other commands doesn't need | 0x80 */
.tbt_command = TBT_COMMAND_WAKEUP,
.bufferlen = 0,
.extended_cmdarea[0 ... 53] = 0,
.data[0 ... 191] = 0,
};
if (!fu_dell_dock_hid_set_report (self, (guint8 *) &cmd_buffer, error)) {
g_prefix_error (error, "failed to set wake thunderbolt: ");
return FALSE;
}
if (!fu_dell_dock_hid_get_report (self, cmd_buffer.data, error)) {
g_prefix_error (error, "failed to get wake thunderbolt status: ");
return FALSE;
}
g_debug ("thunderbolt wake result: 0x%x", cmd_buffer.data[1]);
return TRUE;
}
static const gchar *
fu_dell_dock_hid_tbt_map_error (guint32 code)
{
if (code == 1)
return g_strerror (EINVAL);
else if (code == 2)
return g_strerror (EPERM);
return g_strerror (EIO);
}
gboolean
fu_dell_dock_hid_tbt_write (FuDevice *self,
guint32 start_addr,
const guint8 *input,
gsize write_size,
const FuHIDI2CParameters *parameters,
GError **error)
{
FuTbtCmdBuffer cmd_buffer = {
.cmd = HUB_CMD_READ_DATA, /* It's a special write command that reads status result */
.ext = HUB_EXT_WRITE_TBT_FLASH,
.i2cslaveaddr = parameters->i2cslaveaddr,
.i2cspeed = parameters->i2cspeed, /* unlike other commands doesn't need | 0x80 */
.startaddress = GUINT32_TO_LE (start_addr),
.bufferlen = write_size,
.extended_cmdarea[0 ... 53] = 0,
};
guint8 result;
g_return_val_if_fail (input != NULL, FALSE);
g_return_val_if_fail (write_size <= HIDI2C_MAX_WRITE, FALSE);
memcpy (cmd_buffer.data, input, write_size);
for (gint i = 1; i <= TBT_MAX_RETRIES; i++) {
if (!fu_dell_dock_hid_set_report (self, (guint8 *) &cmd_buffer, error)) {
g_prefix_error (error, "failed to run TBT update: ");
return FALSE;
}
if (!fu_dell_dock_hid_get_report (self, cmd_buffer.data, error)) {
g_prefix_error (error, "failed to get TBT flash status: ");
return FALSE;
}
result = cmd_buffer.data[1] & 0xf;
if (result == 0)
break;
g_debug ("attempt %d/%d: Thunderbolt write failed: %x",
i, TBT_MAX_RETRIES, result);
}
if (result != 0) {
g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL,
"Writing address 0x%04x failed: %s",
start_addr, fu_dell_dock_hid_tbt_map_error (result));
return FALSE;
}
return TRUE;
}
gboolean
fu_dell_dock_hid_tbt_authenticate (FuDevice *self,
const FuHIDI2CParameters *parameters,
GError **error)
{
FuTbtCmdBuffer cmd_buffer = {
.cmd = HUB_CMD_READ_DATA, /* It's a special write command that reads status result */
.ext = HUB_EXT_WRITE_TBT_FLASH,
.i2cslaveaddr = parameters->i2cslaveaddr,
.i2cspeed = parameters->i2cspeed, /* unlike other commands doesn't need | 0x80 */
.tbt_command = GUINT32_TO_LE (TBT_COMMAND_AUTHENTICATE),
.bufferlen = 0,
.extended_cmdarea[0 ... 53] = 0,
};
guint8 result;
if (!fu_dell_dock_hid_set_report (self, (guint8 *) &cmd_buffer, error)) {
g_prefix_error (error, "failed to send authentication: ");
return FALSE;
}
cmd_buffer.tbt_command = GUINT32_TO_LE (TBT_COMMAND_AUTHENTICATE_STATUS);
/* needs at least 2 seconds */
g_usleep (2000000);
for (gint i = 1; i <= TBT_MAX_RETRIES; i++) {
if (!fu_dell_dock_hid_set_report (self, (guint8 *) &cmd_buffer, error)) {
g_prefix_error (error, "failed to set check authentication: ");
return FALSE;
}
if (!fu_dell_dock_hid_get_report (self, cmd_buffer.data, error)) {
g_prefix_error (error, "failed to get check authentication: ");
return FALSE;
}
result = cmd_buffer.data[1] & 0xf;
if (result == 0)
break;
g_debug ("attempt %d/%d: Thunderbolt authenticate failed: %x",
i, TBT_MAX_RETRIES, result);
g_usleep (500000);
}
if (result != 0) {
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Thunderbolt authentication failed: %s",
fu_dell_dock_hid_tbt_map_error (result));
return FALSE;
}
return TRUE;
}

View File

@ -78,4 +78,19 @@ gboolean fu_dell_dock_hid_verify_update (FuDevice *self,
gboolean *result,
GError **error);
gboolean fu_dell_dock_hid_tbt_wake (FuDevice *self,
const FuHIDI2CParameters *parameters,
GError **error);
gboolean fu_dell_dock_hid_tbt_write (FuDevice *self,
guint32 start_addr,
const guint8 *input,
gsize write_size,
const FuHIDI2CParameters *parameters,
GError **error);
gboolean fu_dell_dock_hid_tbt_authenticate (FuDevice *self,
const FuHIDI2CParameters *parameters,
GError **error);
#endif /* __FU_DELL_DOCK_HID_H */

View File

@ -37,6 +37,11 @@
#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)))
typedef enum {
FW_UPDATE_IN_PROGRESS,
FW_UPDATE_COMPLETE,
@ -130,6 +135,7 @@ struct _FuDellDockEc {
guint8 board_min;
gchar *ec_minimum_version;
guint64 blob_version_offset;
guint32 dock_unlock_status;
};
G_DEFINE_TYPE (FuDellDockEc, fu_dell_dock_ec, FU_TYPE_DEVICE)
@ -165,11 +171,17 @@ fu_dell_dock_ec_get_symbiote (FuDevice *device)
}
gboolean
fu_dell_dock_ec_has_tbt (FuDevice *device)
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;
return self->data->module_type == MODULE_TYPE_TBT;
/* 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;
}
static const gchar*
@ -348,7 +360,8 @@ fu_dell_dock_ec_get_dock_info (FuDevice *device,
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 && fu_dell_dock_ec_has_tbt (device)) {
} 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",
@ -504,6 +517,7 @@ fu_dell_dock_ec_modify_lock (FuDevice *device,
gboolean unlocked,
GError **error)
{
FuDellDockEc *self = FU_DELL_DOCK_EC (device);
guint32 cmd;
g_return_val_if_fail (device != NULL, FALSE);
@ -524,6 +538,12 @@ fu_dell_dock_ec_modify_lock (FuDevice *device,
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;
}

View File

@ -31,7 +31,7 @@ FuDellDockEc *fu_dell_dock_ec_new (FuDevice *symbiote);
G_END_DECLS
gboolean fu_dell_dock_ec_has_tbt (FuDevice *device);
gboolean fu_dell_dock_ec_needs_tbt (FuDevice *device);
gboolean fu_dell_dock_ec_modify_lock (FuDevice *self,
guint8 target,
gboolean unlocked,

View File

@ -0,0 +1,295 @@
/*
* 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 <string.h>
#include "fwupd-error.h"
#include "fu-device.h"
#include "fu-common.h"
#include "fu-common-version.h"
#include "fu-dell-dock-common.h"
#define I2C_TBT_ADDRESS 0xa2
const FuHIDI2CParameters tbt_base_settings = {
.i2cslaveaddr = 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;
FuDevice *symbiote;
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,
GBytes *blob_fw,
GError **error)
{
FuDellDockTbt *self = FU_DELL_DOCK_TBT (device);
guint32 start_offset = 0;
gsize image_size;
const guint8 *buffer = g_bytes_get_data (blob_fw, &image_size);
guint16 target_system = 0;
g_autoptr(GTimer) timer = g_timer_new ();
g_autofree gchar *dynamic_version = NULL;
g_return_val_if_fail (device != NULL, FALSE);
g_return_val_if_fail (blob_fw != NULL, FALSE);
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 (self->symbiote, &tbt_base_settings, error))
return FALSE;
g_usleep (2000000);
fu_device_set_status (device, 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 (self->symbiote,
i,
buffer,
write_size,
&tbt_base_settings,
error))
return FALSE;
fu_device_set_progress_full (device, i, image_size);
}
g_debug ("writing took %f seconds",
g_timer_elapsed (timer, NULL));
fu_device_set_status (device, FWUPD_STATUS_DEVICE_BUSY);
if (fu_device_has_custom_flag (device, "skip-restart")) {
g_debug ("skipping TBT reset per quirk request");
} else if (!fu_dell_dock_hid_tbt_authenticate (self->symbiote,
&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 (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);
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;
} else if (g_strcmp0 (key, "DellDockInstallDurationI2C") == 0) {
guint64 tmp = fu_common_strtoull (value);
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) {
self->blob_major_offset = fu_common_strtoull (value);
return TRUE;
} else if (g_strcmp0 (key, "DellDockBlobMinorOffset") == 0) {
self->blob_minor_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_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 (device, version);
/* minimum version of NVM that supports this feature */
if (version == NULL || fu_common_vercmp (version, MIN_NVM) < 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 (self->symbiote);
if (fu_common_vercmp (hub_version, self->hub_minimum_version) < 0) {
fu_device_set_update_error (device,
"Updates over I2C are disabled due to insufficient USB 3.1 G2 hub version");
return TRUE;
}
fu_device_add_flag (device, FWUPD_DEVICE_FLAG_UPDATABLE);
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_guid (device, DELL_DOCK_TBT_GUID);
return TRUE;
}
static gboolean
fu_dell_dock_tbt_open (FuDevice *device, GError **error)
{
FuDellDockTbt *self = FU_DELL_DOCK_TBT (device);
FuDevice *parent;
g_return_val_if_fail (self->unlock_target != 0, FALSE);
parent = fu_device_get_parent (device);
if (parent == NULL) {
g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "no parent");
return FALSE;
}
if (self->symbiote == NULL)
self->symbiote = g_object_ref (fu_dell_dock_ec_get_symbiote (parent));
if (!fu_device_open (self->symbiote, 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 (self->symbiote, error);
}
static void
fu_dell_dock_tbt_finalize (GObject *object)
{
FuDellDockTbt *self = FU_DELL_DOCK_TBT (object);
g_object_unref (self->symbiote);
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 *device)
{}
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 (void)
{
FuDellDockTbt *device = NULL;
device = g_object_new (FU_TYPE_DELL_DOCK_TBT, NULL);
return device;
}

View File

@ -0,0 +1,34 @@
/*
* 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
*/
#ifndef __FU_DELLDOCK_I2C_TBT_H
#define __FU_DELLDOCK_I2C_TBT_H
#include "config.h"
#include "fu-device.h"
G_BEGIN_DECLS
#define FU_TYPE_DELL_DOCK_TBT (fu_dell_dock_tbt_get_type ())
G_DECLARE_FINAL_TYPE (FuDellDockTbt, fu_dell_dock_tbt, FU, DELL_DOCK_TBT, FuDevice)
FuDellDockTbt *fu_dell_dock_tbt_new (void);
G_END_DECLS
#endif /* __FU_DELLDOCK_I2C_TBT_H */

View File

@ -68,6 +68,16 @@ fu_plugin_dell_dock_probe (FuPlugin *plugin,
error))
return FALSE;
/* create TBT endpoint if Thunderbolt SKU and Thunderbolt link inactive */
if (fu_dell_dock_ec_needs_tbt (FU_DEVICE (ec_device))) {
g_autoptr(FuDellDockTbt) tbt_device = fu_dell_dock_tbt_new ();
fu_device_add_child (FU_DEVICE (ec_device), FU_DEVICE (tbt_device));
if (!fu_plugin_dell_dock_create_node (plugin,
FU_DEVICE (tbt_device),
error))
return FALSE;
}
return TRUE;
}

View File

@ -13,6 +13,7 @@ shared_module('fu_plugin_dell_dock',
'fu-dell-dock-status.c',
'fu-dell-dock-i2c-ec.c',
'fu-dell-dock-hub.c',
'fu-dell-dock-i2c-tbt.c',
'fu-dell-dock-i2c-mst.c'
],
include_directories : [

View File

@ -590,6 +590,9 @@ fu_plugin_init (FuPlugin *plugin)
data->udev = g_udev_client_new (subsystems);
g_signal_connect (data->udev, "uevent",
G_CALLBACK (udev_uevent_cb), plugin);
/* dell-dock plugin uses a slower bus for flashing */
fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_BETTER_THAN, "dell_dock");
}
void