mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-28 09:34:21 +00:00
498 lines
13 KiB
C
498 lines
13 KiB
C
/*
|
|
* Copyright (C) 2018 Realtek Semiconductor Corporation
|
|
* 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 <errno.h>
|
|
|
|
#include "fu-hid-device.h"
|
|
#include "fwupd-error.h"
|
|
|
|
#include "fu-dell-dock-hid.h"
|
|
|
|
#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
|
|
#define HUB_CMD_WRITE_DATA 0x40
|
|
#define HUB_EXT_READ_STATUS 0x09
|
|
#define HUB_EXT_MCUMODIFYCLOCK 0x06
|
|
#define HUB_EXT_I2C_WRITE 0xC6
|
|
#define HUB_EXT_WRITEFLASH 0xC8
|
|
#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;
|
|
guint8 ext;
|
|
union {
|
|
guint32 dwregaddr;
|
|
struct {
|
|
guint8 cmd_data0;
|
|
guint8 cmd_data1;
|
|
guint8 cmd_data2;
|
|
guint8 cmd_data3;
|
|
};
|
|
};
|
|
guint16 bufferlen;
|
|
FuHIDI2CParameters parameters;
|
|
guint8 extended_cmdarea[53];
|
|
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_cb (FuDevice *self, gpointer user_data, GError **error)
|
|
{
|
|
guint8 *outbuffer = (guint8 *) user_data;
|
|
return fu_hid_device_set_report (FU_HID_DEVICE (self), 0x0,
|
|
outbuffer, 192,
|
|
HIDI2C_TRANSACTION_TIMEOUT,
|
|
FU_HID_DEVICE_FLAG_NONE,
|
|
error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_dell_dock_hid_set_report (FuDevice *self,
|
|
guint8 *outbuffer,
|
|
GError **error)
|
|
{
|
|
return fu_device_retry (self, fu_dell_dock_hid_set_report_cb,
|
|
HID_MAX_RETRIES, outbuffer, error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_dell_dock_hid_get_report_cb (FuDevice *self, gpointer user_data, GError **error)
|
|
{
|
|
guint8 *inbuffer = (guint8 *) user_data;
|
|
return fu_hid_device_get_report (FU_HID_DEVICE (self), 0x0,
|
|
inbuffer, 192,
|
|
HIDI2C_TRANSACTION_TIMEOUT,
|
|
FU_HID_DEVICE_FLAG_NONE,
|
|
error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_dell_dock_hid_get_report (FuDevice *self,
|
|
guint8 *inbuffer,
|
|
GError **error)
|
|
{
|
|
return fu_device_retry (self, fu_dell_dock_hid_get_report_cb,
|
|
HID_MAX_RETRIES, inbuffer, error);
|
|
}
|
|
|
|
gboolean
|
|
fu_dell_dock_hid_get_hub_version (FuDevice *self,
|
|
GError **error)
|
|
{
|
|
g_autofree gchar *version = NULL;
|
|
FuHIDCmdBuffer cmd_buffer = {
|
|
.cmd = HUB_CMD_READ_DATA,
|
|
.ext = HUB_EXT_READ_STATUS,
|
|
.cmd_data0 = 0,
|
|
.cmd_data1 = 0,
|
|
.cmd_data2 = 0,
|
|
.cmd_data3 = 0,
|
|
.bufferlen = GUINT16_TO_LE (12),
|
|
.parameters = {.i2cslaveaddr = 0, .regaddrlen = 0, .i2cspeed = 0},
|
|
.extended_cmdarea[0 ... 52] = 0,
|
|
};
|
|
|
|
if (!fu_dell_dock_hid_set_report (self, (guint8 *) &cmd_buffer,
|
|
error)) {
|
|
g_prefix_error (error, "failed to query hub version: ");
|
|
return FALSE;
|
|
}
|
|
if (!fu_dell_dock_hid_get_report (self, cmd_buffer.data, error)) {
|
|
g_prefix_error (error, "failed to query hub version: ");
|
|
return FALSE;
|
|
}
|
|
|
|
version = g_strdup_printf ("%02x.%02x",
|
|
cmd_buffer.data[10],
|
|
cmd_buffer.data[11]);
|
|
fu_device_set_version_format (self, FWUPD_VERSION_FORMAT_PAIR);
|
|
fu_device_set_version (self, version);
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
fu_dell_dock_hid_raise_mcu_clock (FuDevice *self,
|
|
gboolean enable,
|
|
GError **error)
|
|
{
|
|
FuHIDCmdBuffer cmd_buffer = {
|
|
.cmd = HUB_CMD_WRITE_DATA,
|
|
.ext = HUB_EXT_MCUMODIFYCLOCK,
|
|
.cmd_data0 = (guint8) enable,
|
|
.cmd_data1 = 0,
|
|
.cmd_data2 = 0,
|
|
.cmd_data3 = 0,
|
|
.bufferlen = 0,
|
|
.parameters = {.i2cslaveaddr = 0, .regaddrlen = 0, .i2cspeed = 0},
|
|
.extended_cmdarea[0 ... 52] = 0,
|
|
};
|
|
|
|
if (!fu_dell_dock_hid_set_report (self, (guint8 *) &cmd_buffer,
|
|
error)) {
|
|
g_prefix_error (error,
|
|
"failed to set mcu clock to %d: ",
|
|
enable);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
fu_dell_dock_hid_get_ec_status (FuDevice *self,
|
|
guint8 *status1,
|
|
guint8 *status2,
|
|
GError **error)
|
|
{
|
|
FuHIDCmdBuffer cmd_buffer = {
|
|
.cmd = HUB_CMD_WRITE_DATA,
|
|
.ext = HUB_EXT_READ_STATUS,
|
|
.cmd_data0 = 0,
|
|
.cmd_data1 = 0,
|
|
.cmd_data2 = 0,
|
|
.cmd_data3 = 0,
|
|
.bufferlen = GUINT16_TO_LE (27),
|
|
.parameters = {.i2cslaveaddr = 0, .regaddrlen = 0, .i2cspeed = 0},
|
|
.extended_cmdarea[0 ... 52] = 0,
|
|
};
|
|
|
|
if (!fu_dell_dock_hid_set_report (self, (guint8 *) &cmd_buffer,
|
|
error)) {
|
|
g_prefix_error (error, "failed to get EC status: ");
|
|
return FALSE;
|
|
}
|
|
if (!fu_dell_dock_hid_get_report (self, cmd_buffer.data, error)) {
|
|
g_prefix_error (error, "failed to get EC status: ");
|
|
return FALSE;
|
|
}
|
|
|
|
*status1 = cmd_buffer.data[25];
|
|
*status2 = cmd_buffer.data[26];
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
fu_dell_dock_hid_erase_bank (FuDevice *self, guint8 idx, GError **error)
|
|
{
|
|
FuHIDCmdBuffer cmd_buffer = {
|
|
.cmd = HUB_CMD_WRITE_DATA,
|
|
.ext = HUB_EXT_ERASEBANK,
|
|
.cmd_data0 = 0,
|
|
.cmd_data1 = idx,
|
|
.cmd_data2 = 0,
|
|
.cmd_data3 = 0,
|
|
.bufferlen = 0,
|
|
.parameters = {.i2cslaveaddr = 0, .regaddrlen = 0, .i2cspeed = 0},
|
|
.extended_cmdarea[0 ... 52] = 0,
|
|
};
|
|
|
|
if (!fu_dell_dock_hid_set_report (self, (guint8 *) &cmd_buffer,
|
|
error)) {
|
|
g_prefix_error (error, "failed to erase bank: ");
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
fu_dell_dock_hid_write_flash (FuDevice *self,
|
|
guint32 dwAddr,
|
|
const guint8 *input,
|
|
gsize write_size,
|
|
GError **error)
|
|
{
|
|
FuHIDCmdBuffer cmd_buffer = {
|
|
.cmd = HUB_CMD_WRITE_DATA,
|
|
.ext = HUB_EXT_WRITEFLASH,
|
|
.dwregaddr = GUINT32_TO_LE (dwAddr),
|
|
.bufferlen = GUINT16_TO_LE (write_size),
|
|
.parameters = {.i2cslaveaddr = 0, .regaddrlen = 0, .i2cspeed = 0},
|
|
.extended_cmdarea[0 ... 52] = 0,
|
|
};
|
|
|
|
g_return_val_if_fail (write_size <= HIDI2C_MAX_WRITE, FALSE);
|
|
|
|
memcpy (cmd_buffer.data, input, write_size);
|
|
if (!fu_dell_dock_hid_set_report (self, (guint8 *) &cmd_buffer,
|
|
error)) {
|
|
g_prefix_error (
|
|
error, "failed to write %" G_GSIZE_FORMAT " flash to %x: ",
|
|
write_size, dwAddr);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
fu_dell_dock_hid_verify_update (FuDevice *self,
|
|
gboolean *result,
|
|
GError **error)
|
|
{
|
|
FuHIDCmdBuffer cmd_buffer = {
|
|
.cmd = HUB_CMD_WRITE_DATA,
|
|
.ext = HUB_EXT_VERIFYUPDATE,
|
|
.cmd_data0 = 1,
|
|
.cmd_data1 = 0,
|
|
.cmd_data2 = 0,
|
|
.cmd_data3 = 0,
|
|
.bufferlen = GUINT16_TO_LE (1),
|
|
.parameters = {.i2cslaveaddr = 0, .regaddrlen = 0, .i2cspeed = 0},
|
|
.extended_cmdarea[0 ... 52] = 0,
|
|
};
|
|
|
|
if (!fu_dell_dock_hid_set_report (self, (guint8 *) &cmd_buffer,
|
|
error)) {
|
|
g_prefix_error (error, "failed to verify update: ");
|
|
return FALSE;
|
|
}
|
|
if (!fu_dell_dock_hid_get_report (self, cmd_buffer.data, error)) {
|
|
g_prefix_error (error, "failed to verify update: ");
|
|
return FALSE;
|
|
}
|
|
*result = cmd_buffer.data[0];
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
fu_dell_dock_hid_i2c_write (FuDevice *self,
|
|
const guint8 *input,
|
|
gsize write_size,
|
|
const FuHIDI2CParameters *parameters,
|
|
GError **error)
|
|
{
|
|
FuHIDCmdBuffer cmd_buffer = {
|
|
.cmd = HUB_CMD_WRITE_DATA,
|
|
.ext = HUB_EXT_I2C_WRITE,
|
|
.dwregaddr = 0,
|
|
.bufferlen = GUINT16_TO_LE (write_size),
|
|
.parameters = {.i2cslaveaddr = parameters->i2cslaveaddr,
|
|
.regaddrlen = 0,
|
|
.i2cspeed = parameters->i2cspeed | 0x80},
|
|
.extended_cmdarea[0 ... 52] = 0,
|
|
};
|
|
|
|
g_return_val_if_fail (write_size <= HIDI2C_MAX_WRITE, FALSE);
|
|
|
|
memcpy (cmd_buffer.data, input, write_size);
|
|
|
|
return fu_dell_dock_hid_set_report (self, (guint8 *) &cmd_buffer, error);
|
|
}
|
|
|
|
gboolean
|
|
fu_dell_dock_hid_i2c_read (FuDevice *self,
|
|
guint32 cmd,
|
|
gsize read_size,
|
|
GBytes **bytes,
|
|
const FuHIDI2CParameters *parameters,
|
|
GError **error)
|
|
{
|
|
FuHIDCmdBuffer cmd_buffer = {
|
|
.cmd = HUB_CMD_WRITE_DATA,
|
|
.ext = HUB_EXT_I2C_READ,
|
|
.dwregaddr = GUINT32_TO_LE (cmd),
|
|
.bufferlen = GUINT16_TO_LE (read_size),
|
|
.parameters = {.i2cslaveaddr = parameters->i2cslaveaddr,
|
|
.regaddrlen = parameters->regaddrlen,
|
|
.i2cspeed = parameters->i2cspeed | 0x80},
|
|
.extended_cmdarea[0 ... 52] = 0,
|
|
.data[0 ... 191] = 0,
|
|
};
|
|
|
|
g_return_val_if_fail (read_size <= HIDI2C_MAX_READ, FALSE);
|
|
g_return_val_if_fail (bytes != NULL, FALSE);
|
|
g_return_val_if_fail (parameters->regaddrlen < HIDI2C_MAX_REGISTER, FALSE);
|
|
|
|
if (!fu_dell_dock_hid_set_report (self, (guint8 *) &cmd_buffer, error))
|
|
return FALSE;
|
|
if (!fu_dell_dock_hid_get_report (self, cmd_buffer.data, error))
|
|
return FALSE;
|
|
|
|
*bytes = g_bytes_new (cmd_buffer.data, read_size);
|
|
|
|
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;
|
|
}
|