mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-14 05:13:34 +00:00

The type of Dell dock gets used in the device GUID, so make that mandatory for device enumeration to succeed. Only relying on the synapticsmst plugin to be cold plugged after the dell plugin isn't enough to ensure this. Signed-off-by: Sjoerd Simons <sjoerd.simons@collabora.co.uk>
815 lines
21 KiB
C
815 lines
21 KiB
C
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
|
|
*
|
|
* Copyright (C) 2015-2017 Richard Hughes <richard@hughsie.com>
|
|
* Copyright (C) 2016 Mario Limonciello <mario.limonciello@dell.com>
|
|
* Copyright (C) 2017 Peichen Huang <peichenhuang@tw.synaptics.com>
|
|
*
|
|
* Licensed under the GNU Lesser General Public License Version 2.1
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
#include "config.h"
|
|
#include <string.h>
|
|
#include <glib-object.h>
|
|
#include <fcntl.h>
|
|
#include <stdlib.h>
|
|
#include "synapticsmst-device.h"
|
|
#include "synapticsmst-common.h"
|
|
|
|
#define BLOCK_UNIT 64
|
|
|
|
typedef struct
|
|
{
|
|
SynapticsMSTDeviceKind kind;
|
|
gchar *version;
|
|
SynapticsMSTDeviceBoardID board_id;
|
|
gchar *chip_id;
|
|
gchar *guid;
|
|
gchar *aux_node;
|
|
guint8 layer;
|
|
guint16 rad;
|
|
gint fd;
|
|
gboolean has_cascade;
|
|
gchar *fw_dir;
|
|
gboolean test_mode;
|
|
} SynapticsMSTDevicePrivate;
|
|
|
|
G_DEFINE_TYPE_WITH_PRIVATE (SynapticsMSTDevice, synapticsmst_device, G_TYPE_OBJECT)
|
|
|
|
#define GET_PRIVATE(o) (synapticsmst_device_get_instance_private (o))
|
|
|
|
SynapticsMSTDeviceKind
|
|
synapticsmst_device_kind_from_string (const gchar *kind)
|
|
{
|
|
if (g_strcmp0 (kind, "DIRECT") == 0)
|
|
return SYNAPTICSMST_DEVICE_KIND_DIRECT;
|
|
if (g_strcmp0 (kind, "REMOTE") == 0)
|
|
return SYNAPTICSMST_DEVICE_KIND_REMOTE;
|
|
return SYNAPTICSMST_DEVICE_KIND_UNKNOWN;
|
|
}
|
|
|
|
const gchar *
|
|
synapticsmst_device_kind_to_string (SynapticsMSTDeviceKind kind)
|
|
{
|
|
if (kind == SYNAPTICSMST_DEVICE_KIND_DIRECT)
|
|
return "DIRECT";
|
|
if (kind == SYNAPTICSMST_DEVICE_KIND_REMOTE)
|
|
return "REMOTE";
|
|
return NULL;
|
|
}
|
|
|
|
const gchar *
|
|
synapticsmst_device_board_id_to_string (SynapticsMSTDeviceBoardID board_id)
|
|
{
|
|
if (board_id == SYNAPTICSMST_DEVICE_BOARDID_DELL_X6)
|
|
return "Dell X6 Platform";
|
|
if (board_id == SYNAPTICSMST_DEVICE_BOARDID_DELL_X7)
|
|
return "Dell X7 Platform";
|
|
if (board_id == SYNAPTICSMST_DEVICE_BOARDID_DELL_WD15_TB16_WIRE)
|
|
return "Dell WD15/TB16 wired Dock";
|
|
if (board_id == SYNAPTICSMST_DEVICE_BOARDID_DELL_WLD15_WIRELESS)
|
|
return "Dell WLD15 Wireless Dock";
|
|
if (board_id == SYNAPTICSMST_DEVICE_BOARDID_DELL_X7_RUGGED)
|
|
return "Dell Rugged Platform";
|
|
if ((board_id & 0xFF00) == SYNAPTICSMST_DEVICE_BOARDID_EVB)
|
|
return "SYNA evb board";
|
|
return "Unknown Platform";
|
|
}
|
|
|
|
const gchar *
|
|
synapticsmst_device_get_guid (SynapticsMSTDevice *device)
|
|
{
|
|
SynapticsMSTDevicePrivate *priv = GET_PRIVATE (device);
|
|
return priv->guid;
|
|
}
|
|
|
|
static void
|
|
synapticsmst_device_finalize (GObject *object)
|
|
{
|
|
SynapticsMSTDevice *device = SYNAPTICSMST_DEVICE (object);
|
|
SynapticsMSTDevicePrivate *priv = GET_PRIVATE (device);
|
|
|
|
if (priv->fd > 0)
|
|
close (priv->fd);
|
|
|
|
g_free (priv->fw_dir);
|
|
g_free (priv->aux_node);
|
|
g_free (priv->version);
|
|
g_free (priv->chip_id);
|
|
g_free (priv->guid);
|
|
G_OBJECT_CLASS (synapticsmst_device_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
synapticsmst_device_init (SynapticsMSTDevice *device)
|
|
{
|
|
SynapticsMSTDevicePrivate *priv = GET_PRIVATE (device);
|
|
const gchar *tmp;
|
|
|
|
priv->test_mode = FALSE;
|
|
priv->fw_dir = g_strdup ("/dev");
|
|
tmp = g_getenv ("FWUPD_SYNAPTICSMST_FW_DIR");
|
|
if (tmp != NULL) {
|
|
priv->test_mode = TRUE;
|
|
priv->fw_dir = g_strdup (tmp);
|
|
}
|
|
}
|
|
|
|
static void
|
|
synapticsmst_device_class_init (SynapticsMSTDeviceClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
object_class->finalize = synapticsmst_device_finalize;
|
|
}
|
|
|
|
SynapticsMSTDeviceKind
|
|
synapticsmst_device_get_kind (SynapticsMSTDevice *device)
|
|
{
|
|
SynapticsMSTDevicePrivate *priv = GET_PRIVATE (device);
|
|
return priv->kind;
|
|
}
|
|
|
|
SynapticsMSTDeviceBoardID
|
|
synapticsmst_device_get_board_id (SynapticsMSTDevice *device)
|
|
{
|
|
SynapticsMSTDevicePrivate *priv = GET_PRIVATE (device);
|
|
return priv->board_id;
|
|
}
|
|
|
|
static gboolean
|
|
synapticsmst_device_enable_remote_control (SynapticsMSTDevice *device, GError **error)
|
|
{
|
|
SynapticsMSTDevicePrivate *priv = GET_PRIVATE (device);
|
|
g_autoptr(SynapticsMSTConnection) connection = NULL;
|
|
|
|
/* in test mode we need to open a different file node instead */
|
|
if (priv->test_mode) {
|
|
g_autofree gchar *filename = NULL;
|
|
close(priv->fd);
|
|
filename = g_strdup_printf ("%s/remote/%s",
|
|
priv->fw_dir,
|
|
priv->aux_node);
|
|
if (!g_file_test (filename, G_FILE_TEST_EXISTS)) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_NOT_FOUND,
|
|
"no device exists %s",
|
|
filename);
|
|
return FALSE;
|
|
}
|
|
priv->fd = open (filename, O_RDWR);
|
|
if (priv->fd == -1) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_PERMISSION_DENIED,
|
|
"cannot open device %s",
|
|
filename);
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
connection = synapticsmst_common_new (priv->fd, priv->layer, priv->rad);
|
|
if (synapticsmst_common_enable_remote_control (connection)) {
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"Failed to enable MST remote control");
|
|
return FALSE;
|
|
} else {
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
synapticsmst_device_disable_remote_control (SynapticsMSTDevice *device, GError **error)
|
|
{
|
|
SynapticsMSTDevicePrivate *priv = GET_PRIVATE (device);
|
|
g_autoptr(SynapticsMSTConnection) connection = NULL;
|
|
|
|
/* in test mode we need to open a different file node instead */
|
|
if (priv->test_mode) {
|
|
g_autofree gchar *filename = NULL;
|
|
close(priv->fd);
|
|
filename = g_strdup_printf ("%s/%s",
|
|
priv->fw_dir,
|
|
priv->aux_node);
|
|
if (!g_file_test (filename, G_FILE_TEST_EXISTS)) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_NOT_FOUND,
|
|
"no device exists %s",
|
|
filename);
|
|
return FALSE;
|
|
}
|
|
priv->fd = open (filename, O_RDWR);
|
|
if (priv->fd == -1) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_PERMISSION_DENIED,
|
|
"cannot open device %s",
|
|
filename);
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
connection = synapticsmst_common_new (priv->fd, priv->layer, priv->rad);
|
|
if (synapticsmst_common_disable_remote_control (connection)) {
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"Failed to disable MST remote control");
|
|
return FALSE;
|
|
} else {
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
gboolean
|
|
synapticsmst_device_scan_cascade_device (SynapticsMSTDevice *device,
|
|
GError ** error,
|
|
guint8 tx_port)
|
|
{
|
|
SynapticsMSTDevicePrivate *priv = GET_PRIVATE (device);
|
|
guint8 layer = priv->layer + 1;
|
|
guint16 rad = priv->rad | (tx_port << (2 * (priv->layer)));
|
|
guint8 byte[4];
|
|
guint8 rc;
|
|
g_autoptr(SynapticsMSTConnection) connection = NULL;
|
|
|
|
if (priv->test_mode)
|
|
return TRUE;
|
|
|
|
/* reset */
|
|
priv->has_cascade = FALSE;
|
|
|
|
if (!synapticsmst_device_enable_remote_control (device, error)) {
|
|
g_prefix_error (error,
|
|
"failed to scan cascade device on tx_port %d: ",
|
|
tx_port);
|
|
return FALSE;
|
|
}
|
|
|
|
connection = synapticsmst_common_new (priv->fd, layer, rad);
|
|
rc = synapticsmst_common_read_dpcd (connection, REG_RC_CAP, (gint *)byte, 1);
|
|
if (rc == DPCD_SUCCESS ) {
|
|
if (byte[0] & 0x04) {
|
|
synapticsmst_common_read_dpcd (connection, REG_VENDOR_ID, (gint *)byte, 3);
|
|
if (byte[0] == 0x90 && byte[1] == 0xCC && byte[2] == 0x24)
|
|
priv->has_cascade = TRUE;
|
|
}
|
|
}
|
|
|
|
if (!synapticsmst_device_disable_remote_control (device, error)) {
|
|
g_prefix_error (error,
|
|
"failed to scan cascade device on tx_port %d: ",
|
|
tx_port);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
synapticsmst_device_read_board_id (SynapticsMSTDevice *device,
|
|
SynapticsMSTConnection *connection,
|
|
guint8 *byte,
|
|
GError **error)
|
|
{
|
|
SynapticsMSTDevicePrivate *priv = GET_PRIVATE (device);
|
|
guint8 rc;
|
|
|
|
if (priv->test_mode) {
|
|
g_autofree gchar *filename = NULL;
|
|
gint fd;
|
|
filename = g_strdup_printf ("%s/remote/%s_eeprom",
|
|
priv->fw_dir,
|
|
priv->aux_node);
|
|
if (!g_file_test (filename, G_FILE_TEST_EXISTS)) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_NOT_FOUND,
|
|
"no device exists %s",
|
|
filename);
|
|
return FALSE;
|
|
}
|
|
fd = open (filename, O_RDONLY);
|
|
if (fd == -1) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_PERMISSION_DENIED,
|
|
"cannot open device %s",
|
|
filename);
|
|
return FALSE;
|
|
}
|
|
if (read (fd, byte, 2) != 2) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"error reading EEPROM file %s",
|
|
filename);
|
|
close (fd);
|
|
return FALSE;
|
|
}
|
|
close (fd);
|
|
} else {
|
|
rc = synapticsmst_common_rc_get_command (connection,
|
|
UPDC_READ_FROM_EEPROM,
|
|
2, ADDR_CUSTOMER_ID, byte);
|
|
if (rc) {
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"Failed to read from EEPROM of device");
|
|
return FALSE;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
synapticsmst_device_enumerate_device (SynapticsMSTDevice *device,
|
|
const gchar *dock_type,
|
|
const gchar *system_type,
|
|
GError **error)
|
|
{
|
|
SynapticsMSTDevicePrivate *priv = GET_PRIVATE (device);
|
|
guint8 byte[16];
|
|
g_autofree gchar *system = NULL;
|
|
guint8 rc;
|
|
g_autoptr(SynapticsMSTConnection) connection = NULL;
|
|
|
|
//FIXME?
|
|
if (!synapticsmst_device_open (device, error)) {
|
|
g_prefix_error (error, "Failed to open device in DP Aux Node %s: ",
|
|
synapticsmst_device_get_aux_node (device));
|
|
return FALSE;
|
|
}
|
|
|
|
/* enable remote control */
|
|
if (!synapticsmst_device_enable_remote_control (device, error))
|
|
return FALSE;
|
|
|
|
/* read firmware version */
|
|
connection = synapticsmst_common_new (priv->fd, priv->layer, priv->rad);
|
|
rc = synapticsmst_common_read_dpcd (connection,
|
|
REG_FIRMWARE_VERSION,
|
|
(gint *)byte, 3);
|
|
if (rc) {
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"Failed to read dpcd from device");
|
|
goto error_disable_remote;
|
|
}
|
|
priv->version = g_strdup_printf ("%1d.%02d.%03d", byte[0], byte[1], byte[2]);
|
|
|
|
/* read board ID */
|
|
if (!synapticsmst_device_read_board_id (device, connection, byte, error))
|
|
goto error_disable_remote;
|
|
priv->board_id = (byte[0] << 8) | (byte[1]);
|
|
|
|
/* read board chip_id */
|
|
rc = synapticsmst_common_read_dpcd (connection,
|
|
REG_CHIP_ID,
|
|
(gint *)byte, 2);
|
|
if (rc) {
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"Failed to read dpcd from device");
|
|
goto error_disable_remote;
|
|
}
|
|
priv->chip_id = g_strdup_printf ("VMM%02x%02x", byte[0], byte[1]);
|
|
|
|
switch (priv->board_id >> 8) {
|
|
/* only dell is supported for today */
|
|
case CUSTOMERID_DELL:
|
|
/* If this is a dock, use dock ID*/
|
|
if (priv->test_mode)
|
|
system = g_strdup_printf ("test-%s", priv->chip_id);
|
|
else if (priv->board_id == SYNAPTICSMST_DEVICE_BOARDID_DELL_WD15_TB16_WIRE) {
|
|
if (dock_type == NULL) {
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"Unknown Dell dock type");
|
|
goto error_disable_remote;
|
|
}
|
|
system = g_strdup_printf ("%s-%s", dock_type, priv->chip_id);
|
|
system = g_ascii_strdown (system, -1);
|
|
}
|
|
else if (priv->board_id == SYNAPTICSMST_DEVICE_BOARDID_DELL_WLD15_WIRELESS)
|
|
system = g_strdup ("wld15");
|
|
|
|
/* This is a host system, use system ID */
|
|
else
|
|
system = g_strdup (system_type);
|
|
|
|
/* set up GUID
|
|
* GUID is MST-$SYSTEMID-$BOARDID
|
|
* $BOARDID includes CUSTOMERID in first byte, BOARD in second byte */
|
|
if (system != NULL)
|
|
priv->guid = g_strdup_printf ("MST-%s-%u", system,
|
|
priv->board_id);
|
|
break;
|
|
/* EVB development board */
|
|
case 0:
|
|
priv->board_id = (byte[0] << 8 | byte[1]);
|
|
break;
|
|
/* unknown */
|
|
default:
|
|
g_warning ("Unknown board_id %x", priv->board_id);
|
|
priv->board_id = 0xFF;
|
|
}
|
|
|
|
/* disable remote control */
|
|
if (!synapticsmst_device_disable_remote_control (device, error))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
|
|
error_disable_remote:
|
|
synapticsmst_device_disable_remote_control (device, NULL);
|
|
return FALSE;
|
|
}
|
|
|
|
const gchar *
|
|
synapticsmst_device_get_aux_node (SynapticsMSTDevice *device)
|
|
{
|
|
SynapticsMSTDevicePrivate *priv = GET_PRIVATE (device);
|
|
return priv->aux_node;
|
|
}
|
|
|
|
const gchar *
|
|
synapticsmst_device_get_version (SynapticsMSTDevice *device)
|
|
{
|
|
SynapticsMSTDevicePrivate *priv = GET_PRIVATE (device);
|
|
return priv->version;
|
|
}
|
|
|
|
const gchar *
|
|
synapticsmst_device_get_chip_id (SynapticsMSTDevice *device)
|
|
{
|
|
SynapticsMSTDevicePrivate *priv = GET_PRIVATE (device);
|
|
return priv->chip_id;
|
|
}
|
|
|
|
guint16
|
|
synapticsmst_device_get_rad (SynapticsMSTDevice *device)
|
|
{
|
|
SynapticsMSTDevicePrivate *priv = GET_PRIVATE (device);
|
|
return priv->rad;
|
|
}
|
|
|
|
guint8
|
|
synapticsmst_device_get_layer (SynapticsMSTDevice *device)
|
|
{
|
|
SynapticsMSTDevicePrivate *priv = GET_PRIVATE (device);
|
|
return priv->layer;
|
|
}
|
|
|
|
gboolean
|
|
synapticsmst_device_get_cascade (SynapticsMSTDevice *device)
|
|
{
|
|
SynapticsMSTDevicePrivate *priv = GET_PRIVATE (device);
|
|
return priv->has_cascade;
|
|
}
|
|
|
|
static gboolean
|
|
synapticsmst_device_get_flash_checksum (SynapticsMSTDevice *device,
|
|
gint length, gint offset,
|
|
guint32 *checksum, GError **error)
|
|
{
|
|
SynapticsMSTDevicePrivate *priv = GET_PRIVATE (device);
|
|
g_autoptr(SynapticsMSTConnection) connection = NULL;
|
|
|
|
connection = synapticsmst_common_new (priv->fd, priv->layer, priv->rad);
|
|
if (synapticsmst_common_rc_special_get_command (connection,
|
|
UPDC_CAL_EEPROM_CHECKSUM,
|
|
length, offset,
|
|
NULL, 4,
|
|
(guint8 *)checksum)) {
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"Failed to get flash checksum");
|
|
return FALSE;
|
|
} else {
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
gboolean
|
|
synapticsmst_device_write_firmware (SynapticsMSTDevice *device,
|
|
GBytes *fw,
|
|
GFileProgressCallback progress_cb,
|
|
gpointer progress_data,
|
|
GError **error)
|
|
{
|
|
const guint8 *payload_data;
|
|
guint32 payload_len;
|
|
guint32 code_size = 0;
|
|
guint32 checksum = 0;
|
|
guint32 flash_checksum = 0;
|
|
guint32 offset = 0;
|
|
guint32 write_loops = 0;
|
|
guint32 data_to_write = 0;
|
|
guint8 percentage = 0;
|
|
guint8 rc = 0;
|
|
guint16 tmp;
|
|
guint16 erase_code = 0xFFFF;
|
|
SynapticsMSTDevicePrivate *priv = GET_PRIVATE (device);
|
|
g_autoptr(SynapticsMSTConnection) connection = NULL;
|
|
|
|
/* get firmware data and check size */
|
|
payload_data = g_bytes_get_data (fw, NULL);
|
|
payload_len = g_bytes_get_size (fw);
|
|
if (payload_len > 0x10000 || payload_len == 0) {
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"invalid file size");
|
|
return FALSE;
|
|
}
|
|
|
|
/* check firmware content */
|
|
for (guint8 i = 0; i < 128; i++)
|
|
checksum += *(payload_data + i);
|
|
|
|
if (checksum & 0xFF) {
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"EDID checksum error");
|
|
return FALSE;
|
|
}
|
|
|
|
checksum = 0;
|
|
offset = 128;
|
|
for (guint8 i = 0; i < 128; i++)
|
|
checksum += *(payload_data + offset + i);
|
|
|
|
if (checksum & 0xFF) {
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"EDID checksum error");
|
|
return FALSE;
|
|
}
|
|
|
|
checksum = 0;
|
|
offset = 0x100;
|
|
for (guint16 i = 0; i < 256; i++)
|
|
checksum += *(payload_data + offset + i);
|
|
|
|
if (checksum & 0xFF) {
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"configuration checksum error");
|
|
return FALSE;
|
|
}
|
|
|
|
checksum = 0;
|
|
offset = 0x200;
|
|
for (guint16 i = 0; i < 256; i++)
|
|
checksum += *(payload_data + offset + i);
|
|
if (checksum & 0xFF) {
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"configuration checksum error");
|
|
return FALSE;
|
|
}
|
|
|
|
checksum = 0;
|
|
offset = 0x400;
|
|
code_size = (*(payload_data + offset) << 8) + *(payload_data + offset + 1);
|
|
if (code_size >= 0xFFFF) {
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"invalid firmware size");
|
|
return FALSE;
|
|
}
|
|
for (guint32 i = 0; i < (code_size + 17); i++)
|
|
checksum += *(payload_data + offset + i);
|
|
|
|
if (checksum & 0xFF) {
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"firmware checksum error");
|
|
return FALSE;
|
|
}
|
|
|
|
/* TODO: May need a way to override this to cover field
|
|
* issues of invalid firmware flashed*/
|
|
/* check firmware and board ID again */
|
|
tmp = (*(payload_data + ADDR_CUSTOMER_ID) << 8) + *(payload_data + ADDR_BOARD_ID);
|
|
if (tmp != synapticsmst_device_get_board_id (device)) {
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"board ID mismatch");
|
|
return FALSE;
|
|
}
|
|
|
|
if (!synapticsmst_device_open (device, error)) {
|
|
g_prefix_error (error,
|
|
"can't open DP Aux node %s",
|
|
synapticsmst_device_get_aux_node (device));
|
|
return FALSE;
|
|
}
|
|
|
|
/* enable remote control */
|
|
if (!synapticsmst_device_enable_remote_control (device, error))
|
|
return FALSE;
|
|
|
|
/* erase SPI flash */
|
|
connection = synapticsmst_common_new (priv->fd, priv->layer, priv->rad);
|
|
if (synapticsmst_common_rc_set_command (connection,
|
|
UPDC_FLASH_ERASE,
|
|
2, 0, (guint8 *)&erase_code)) {
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"can't erase flash");
|
|
return FALSE;
|
|
}
|
|
|
|
/* update firmware */
|
|
write_loops = (payload_len / BLOCK_UNIT);
|
|
data_to_write = payload_len;
|
|
rc = 0;
|
|
offset = 0;
|
|
|
|
if (payload_len % BLOCK_UNIT)
|
|
write_loops++;
|
|
|
|
if (progress_cb == NULL)
|
|
g_debug ("updating... 0%%");
|
|
|
|
for (guint32 i = 0; i < write_loops; i++) {
|
|
guint8 length = BLOCK_UNIT;
|
|
if (data_to_write < BLOCK_UNIT)
|
|
length = data_to_write;
|
|
|
|
rc = synapticsmst_common_rc_set_command (connection,
|
|
UPDC_WRITE_TO_EEPROM,
|
|
length, offset,
|
|
payload_data + offset);
|
|
if (rc) {
|
|
/* repeat once */
|
|
rc = synapticsmst_common_rc_set_command (connection,
|
|
UPDC_WRITE_TO_EEPROM,
|
|
length, offset,
|
|
payload_data + offset);
|
|
}
|
|
|
|
if (rc)
|
|
break;
|
|
|
|
offset += length;
|
|
data_to_write -= length;
|
|
percentage = i * 100 / (write_loops - 1);
|
|
if (progress_cb != NULL) {
|
|
progress_cb ((goffset) i * 100,
|
|
(goffset) (write_loops -1) * 100,
|
|
progress_data);
|
|
} else {
|
|
g_debug ("updating... %d%%\n", percentage);
|
|
}
|
|
}
|
|
|
|
if (rc) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"can't write flash at offset 0x%04x",
|
|
offset);
|
|
} else {
|
|
/* check data just written */
|
|
checksum = 0;
|
|
for (guint32 i = 0; i < payload_len; i++) {
|
|
checksum += *(payload_data + i);
|
|
}
|
|
|
|
flash_checksum = 0;
|
|
if (synapticsmst_device_get_flash_checksum (device,
|
|
payload_len,
|
|
0,
|
|
&flash_checksum,
|
|
error)) {
|
|
if (checksum != flash_checksum) {
|
|
rc = -1;
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"checksum mismatch");
|
|
}
|
|
} else {
|
|
rc = -1;
|
|
}
|
|
|
|
}
|
|
|
|
/* disable remote control and close aux node */
|
|
if (!synapticsmst_device_disable_remote_control (device, error))
|
|
return FALSE;
|
|
|
|
if (rc) {
|
|
return FALSE;
|
|
} else {
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
SynapticsMSTDevice *
|
|
synapticsmst_device_new (SynapticsMSTDeviceKind kind,
|
|
const gchar *aux_node,
|
|
guint8 layer,
|
|
guint16 rad)
|
|
{
|
|
SynapticsMSTDevice *device;
|
|
SynapticsMSTDevicePrivate *priv;
|
|
|
|
device = g_object_new (SYNAPTICSMST_TYPE_DEVICE, NULL);
|
|
priv = GET_PRIVATE (device);
|
|
|
|
priv->aux_node = g_strdup(aux_node);
|
|
priv->kind = kind;
|
|
priv->version = NULL;
|
|
priv->layer = layer;
|
|
priv->rad = rad;
|
|
priv->has_cascade = FALSE;
|
|
|
|
return SYNAPTICSMST_DEVICE (device);
|
|
}
|
|
|
|
gboolean
|
|
synapticsmst_device_open (SynapticsMSTDevice *device, GError **error)
|
|
{
|
|
SynapticsMSTDevicePrivate *priv = GET_PRIVATE (device);
|
|
g_autofree gchar *filename = NULL;
|
|
guint8 byte[4];
|
|
g_autoptr(SynapticsMSTConnection) connection = NULL;
|
|
|
|
/* file doesn't exist on this system */
|
|
filename = g_strdup_printf ("%s/%s", priv->fw_dir, priv->aux_node);
|
|
if (!g_file_test (filename, G_FILE_TEST_EXISTS)) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_NOT_FOUND,
|
|
"no device exists %s",
|
|
filename);
|
|
return FALSE;
|
|
}
|
|
|
|
/* can't open aux node, try use sudo to get the permission */
|
|
priv->fd = open (filename, O_RDWR);
|
|
if (priv->fd == -1) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_PERMISSION_DENIED,
|
|
"cannot open device %s",
|
|
filename);
|
|
return FALSE;
|
|
}
|
|
|
|
connection = synapticsmst_common_new (priv->fd, 0, 0);
|
|
if (synapticsmst_common_aux_node_read (connection, REG_RC_CAP, (gint *)byte, 1) == DPCD_SUCCESS) {
|
|
if (byte[0] & 0x04) {
|
|
synapticsmst_common_aux_node_read (connection,
|
|
REG_VENDOR_ID,
|
|
(gint *)byte, 3);
|
|
if (byte[0] == 0x90 && byte[1] == 0xCC && byte[2] == 0x24)
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
/* not a correct device */
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"no device");
|
|
close (priv->fd);
|
|
priv->fd = 0;
|
|
return FALSE;
|
|
}
|