fwupd/plugins/dell-dock/fu-dell-dock-hid.c
2020-03-28 12:46:03 +00:00

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;
}