Introduce plugin for Synaptics MST hubs

This commit is contained in:
Mario Limonciello 2017-01-10 19:54:55 -06:00 committed by Mario Limonciello
parent e3c8832dd0
commit 24afb470f2
13 changed files with 1869 additions and 0 deletions

View File

@ -309,6 +309,25 @@ else
fi
AM_CONDITIONAL(HAVE_DELL, test x$has_dell = xyes)
# Synaptics mst support
AC_ARG_ENABLE(synaptics,
AS_HELP_STRING([--enable-synaptics],
[Enable Synaptics MST hub support]),,
enable_synaptics=maybe)
if test x$enable_synaptics != xno &&
test x$has_libsmbios = xyes; then
AC_DEFINE(HAVE_SYNAPTICS,1,[Use Synaptics MST hub support])
has_synaptics=yes
else
has_synaptics=no
if test "x$enable_synaptics" = "xyes"; then
if test x$has_libsmbios = xno; then
AC_MSG_ERROR([Synaptics MST support requested but 'libsmbios-dev' was not found])
fi
fi
fi
AM_CONDITIONAL(HAVE_SYNAPTICS, test x$has_synaptics = xyes)
# systemd support
AC_ARG_WITH([systemdunitdir],
AS_HELP_STRING([--with-systemdunitdir=DIR], [Directory for systemd service files]),
@ -355,6 +374,7 @@ plugins/ebitdo/Makefile
plugins/raspberrypi/Makefile
plugins/raspberrypi/rpiupdate/Makefile
plugins/steelseries/Makefile
plugins/synapticsmst/Makefile
plugins/test/Makefile
plugins/thunderbolt/Makefile
plugins/udev/Makefile
@ -382,5 +402,6 @@ echo "
libelf: $has_libelf
UEFI: $has_fwup
Dell: $has_dell
Synaptics MST: $has_synaptics
Thunderbolt: $has_thunderbolt
"

View File

@ -3,6 +3,7 @@ SUBDIRS = \
dfu \
ebitdo \
raspberrypi \
synapticsmst \
steelseries \
test \
udev \

View File

@ -0,0 +1,50 @@
AM_CPPFLAGS = \
$(APPSTREAM_GLIB_CFLAGS) \
$(GLIB_CFLAGS) \
$(GUSB_CFLAGS) \
$(LIBSMBIOS_CFLAGS) \
$(PIE_CFLAGS) \
$(EFIVAR_CFLAGS) \
-I$(top_srcdir)/src \
-I$(top_srcdir)/libfwupd \
-DLOCALEDIR=\""$(localedir)"\"
plugindir = $(libdir)/fwupd-plugins-2
plugin_LTLIBRARIES = libfu_plugin_synapticsmst.la
libfu_plugin_synapticsmst_la_SOURCES = \
synapticsmst-device.c \
synapticsmst-device.h \
synapticsmst-common.c \
synapticsmst-common.h \
fu-plugin-synapticsmst.c
libfu_plugin_synapticsmst_la_LIBADD = \
$(GUSB_LIBS) \
$(LIBSMBIOS_LIBS) \
$(GLIB_LIBS)
libfu_plugin_synapticsmst_la_LDFLAGS = -module -avoid-version
libfu_plugin_synapticsmst_la_CFLAGS = \
$(PIE_CFLAGS) \
$(WARN_CFLAGS) \
-DG_LOG_DOMAIN=\"FuPluginSynapticsMST\"
noinst_PROGRAMS = \
synapticsmst-tool
synapticsmst_tool_SOURCES = \
synapticsmst-common.c \
synapticsmst-device.c \
synapticsmst-tool.c
synapticsmst_tool_LDADD = \
$(lib_LTLIBRARIES) \
$(LIBSMBIOS_LIBS) \
$(APPSTREAM_GLIB_LIBS) \
$(GLIB_LIBS) \
$(LIBM)
synapticsmst_tool_CFLAGS = $(AM_CFLAGS) $(WARN_CFLAGS)
-include $(top_srcdir)/git.mk

View File

@ -0,0 +1,58 @@
# Synaptics MST
This plugin supports querying and flashing Synaptics MST hubs used in Dell systems
and docks.
## Requirements
### (Kernel) DP Aux Interface
Kernel 4.6 introduced an DRM DP Aux interface for manipulation of the registers
needed to access an MST hub.
This patch can be backported to earlier kernels:
https://github.com/torvalds/linux/commit/e94cb37b34eb8a88fe847438dba55c3f18bf024a
### libsmbios
At compilation time and runtime you will need libsmbios_c version 2.3.0 or later
* source: https://github.com/dell/libsmbios
* rpms: https://apps.fedoraproject.org/packages/libsmbios
* debs (Debian): http://tracker.debian.org/pkg/libsmbios
* debs (Ubuntu): http://launchpad.net/ubuntu/+source/libsmbios
If you don't want or need this functionality you can use the
`--disable-dell` option.
## Usage
Supported devices will be displayed in `# fwupdmgr get-devices` output.
Here is an example output from a Dell WD15 dock:
```
Dell WD15/TB16 wired Dock with Synaptics [VMM3332]
Guid: 653cd006-5433-57db-8632-0413af4d3fcc
DeviceID: MST-1-1-0-0
Plugin: synapticsmst
Flags: allow-online
Version: 3.10.002
Created: 2017-01-13
Modified: 2017-01-13
Trusted: none
```
Payloads can be flashed just like any other plugin from LVFS.
## Supported devices
Not all Dell systems or accessories contain MST hubs.
Here is a sample list of systems known to support them however:
1. Dell WD15 dock
2. Dell TB16 dock
3. Latitude E5570
4. Latitude E5470
5. Latitude E5270
6. Latitude E7470
7. Latitude E7270
8. Latitude E7450
9. Latitude E7250
10. Latitude E5550
11. Latitude E5450
12. Latitude E5250
13. Latitude Rugged 5414
14. Latitude Rugged 7214
15. Latitude Rugged 7414

View File

@ -0,0 +1 @@
../dell/fu-dell-flash.h

View File

@ -0,0 +1 @@
../dell/fu-plugin-dell.h

View File

@ -0,0 +1,197 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
*
* Copyright (C) 2016 Mario Limonciello <mario.limonciello@dell.com>
*
* Licensed under the GNU General Public License Version 2
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "synapticsmst-device.h"
#include "synapticsmst-common.h"
#include "fu-plugin-dell.h"
#include "fu-plugin.h"
#include "fu-plugin-vfuncs.h"
#define SYNAPTICS_FLASH_MODE_DELAY 2
static gboolean
fu_plugin_synapticsmst_enumerate (FuPlugin *plugin,
GError **error)
{
guint8 i;
const gchar *aux_node = NULL;
const gchar *board_str = NULL;
g_autofree gchar *name = NULL;
g_autoptr(FuDevice) dev = NULL;
g_autoptr(SynapticsMSTDevice) device = NULL;
if (!synapticsmst_common_check_supported_system (error))
return FALSE;
for (i=0; i<MAX_DP_AUX_NODES; i++) {
aux_node = synapticsmst_device_get_aux_node (i);
dev = fu_plugin_cache_lookup (plugin, aux_node);
/* If we open succesfully a device exists here */
if (synapticsmst_common_open_aux_node (aux_node)) {
synapticsmst_common_close_aux_node ();
/* node already exists */
if (dev != NULL)
continue;
g_debug ("SynapticsMST: Adding device at %s", aux_node);
/* TODO: adding cascading (remote) */
device = synapticsmst_device_new (SYNAPTICSMST_DEVICE_KIND_DIRECT, aux_node);
if (!synapticsmst_device_enumerate_device(device, error)) {
g_debug("SynapticsMST: Error enumerating device at %s", aux_node);
continue;
}
board_str = synapticsmst_device_boardID_to_string(synapticsmst_device_get_boardID(device));
name = g_strdup_printf ("%s with Synaptics [%s]", board_str,
synapticsmst_device_get_chipID(device));
if (board_str == NULL) {
g_debug ("SynapticsMST: invalid board ID");
continue;
}
/* create the device */
dev = fu_device_new ();
fu_device_set_id (dev, aux_node);
fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_ALLOW_ONLINE);
fu_device_set_name (dev, name);
fu_device_set_version (dev, synapticsmst_device_get_version (device));
/* GUID is created by board ID */
fu_device_add_guid (dev, synapticsmst_device_get_guid(device));
fu_plugin_device_add (plugin, dev);
fu_plugin_cache_add (plugin, aux_node, dev);
}
/* No device exists here, but was there - remove from DB */
else if (dev != NULL) {
g_debug ("SynapticsMST: Removing device at %s",
aux_node);
fu_plugin_device_remove (plugin, dev);
fu_plugin_cache_remove (plugin, aux_node);
} else {
/* Nothing to see here - move on*/
g_debug ("SynapticsMST: no device found on %s", aux_node);
}
}
return TRUE;
}
static void
fu_synapticsmst_write_progress_cb (goffset current, goffset total, gpointer user_data)
{
FuPlugin *plugin = FU_PLUGIN (user_data);
gdouble percentage = -1.f;
if (total > 0)
percentage = (100.f * (gdouble) current) / (gdouble) total;
g_debug ("written %" G_GOFFSET_FORMAT "/%" G_GOFFSET_FORMAT "[%.1f%%]",
current, total, percentage);
fu_plugin_set_percentage (plugin, (guint) percentage);
}
gboolean
fu_plugin_update_online (FuPlugin *plugin,
FuDevice *dev,
GBytes *blob_fw,
FwupdInstallFlags flags,
GError **error)
{
const gchar *device_id;
g_autoptr(SynapticsMSTDevice) device = NULL;
/* sleep to allow device wakeup to complete */
g_debug ("SynapticsMST: Waiting %d seconds for MST hub wakeup\n",
SYNAPTICS_FLASH_MODE_DELAY);
g_usleep (SYNAPTICS_FLASH_MODE_DELAY * 1000000);
device_id = fu_device_get_id (dev);
device = synapticsmst_device_new (SYNAPTICSMST_DEVICE_KIND_DIRECT, device_id);
if (!synapticsmst_device_enumerate_device (device, error))
return FALSE;
if (synapticsmst_device_boardID_to_string (synapticsmst_device_get_boardID(device)) != NULL) {
fu_plugin_set_status (plugin, FWUPD_STATUS_DEVICE_WRITE);
if (!synapticsmst_device_write_firmware (device, blob_fw,
fu_synapticsmst_write_progress_cb, plugin,
error))
return FALSE;
} else {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"Unknown device");
return FALSE;
}
/* Re-run device enumeration to find the new device version */
fu_plugin_set_status (plugin, FWUPD_STATUS_DEVICE_RESTART);
fu_plugin_device_remove (plugin, dev);
fu_plugin_cache_remove (plugin, device_id);
if (!fu_plugin_synapticsmst_enumerate (plugin, error))
return FALSE;
return TRUE;
}
static void
fu_plugin_synapticsmst_redo_enumeration_cb (GUsbContext *ctx,
GUsbDevice *usb_device,
FuPlugin *plugin)
{
guint16 pid;
guint16 vid;
vid = g_usb_device_get_vid (usb_device);
pid = g_usb_device_get_pid (usb_device);
/* Only look up if this was a dock connected */
if (vid != DOCK_NIC_VID || pid != DOCK_NIC_PID)
return;
/* Request daemon to redo coldplug, this wakes up Dell devices */
fu_plugin_recoldplug (plugin);
}
gboolean
fu_plugin_startup (FuPlugin *plugin, GError **error)
{
GUsbContext *usb_ctx = fu_plugin_get_usb_context (plugin);
g_signal_connect (usb_ctx, "device-added",
G_CALLBACK (fu_plugin_synapticsmst_redo_enumeration_cb),
plugin);
g_signal_connect (usb_ctx, "device-removed",
G_CALLBACK (fu_plugin_synapticsmst_redo_enumeration_cb),
plugin);
return TRUE;
}
gboolean
fu_plugin_coldplug (FuPlugin *plugin, GError **error)
{
/* look for host devices or already plugged in dock devices */
if (!fu_plugin_synapticsmst_enumerate (plugin, error))
g_debug ("SynapticsMST: Error enumerating.");
return TRUE;
}

View File

@ -0,0 +1,349 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
*
* Copyright (C) 2015 Richard Hughes <richard@hughsie.com>
* Copyright (C) 2016 Mario Limonciello <mario.limonciello@dell.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 <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <smbios_c/smbios.h>
#include <glib-object.h>
//#include <sys/types.h>
//#include <string.h>
#include "synapticsmst-common.h"
#define UNIT_SIZE 32
#define MAX_WAIT_TIME 3 // second
typedef enum {
DPCD_SUCCESS = 0,
DPCD_SEEK_FAIL,
DPCD_ACCESS_FAIL,
} dpcd_return;
gint fd = 0;
guchar
synapticsmst_common_read_dpcd (gint offset, gint *buf, gint length)
{
if (lseek (fd, offset, SEEK_SET) != offset) {
// g_debug ("dpcd read fail in finding address %05x\n", offset);
return DPCD_SEEK_FAIL;
}
if (read (fd, buf, length) != length) {
// g_debug ("dpcd read fail reading from address %05x\n", offset);
return DPCD_ACCESS_FAIL;
}
return DPCD_SUCCESS;
}
guchar
synapticsmst_common_write_dpcd (gint offset, gint *buf, gint length)
{
if (lseek(fd, offset, SEEK_SET) != offset) {
// g_debug ("dpcd write fail in finding address %05x\n", offset);
return DPCD_SEEK_FAIL;
}
if (write(fd, buf, length) != length) {
// g_debug ("dpcd write fail in writing to address %05x\n", offset);
return DPCD_ACCESS_FAIL;
}
return DPCD_SUCCESS;
}
guchar
synapticsmst_common_open_aux_node (const gchar *filename)
{
guchar byte[4];
fd = open (filename, O_RDWR);
if (synapticsmst_common_read_dpcd (REG_RC_CAP, (gint *)byte, 1) == DPCD_SUCCESS) {
if (byte[0] & 0x04) {
synapticsmst_common_read_dpcd (REG_VENDOR_ID, (gint *)byte, 3);
if (byte[0] == 0x90 && byte[1] == 0xCC && byte[2] == 0x24) {
return 1;
}
}
}
fd = 0;
return 0;
}
void
synapticsmst_common_close_aux_node (void)
{
close (fd);
}
guchar
synapticsmst_common_rc_set_command (gint rc_cmd, gint length, gint offset, guchar *buf)
{
guchar rc = 0;
gint cur_offset = offset;
gint cur_length;
gint data_left = length;
gint cmd;
gint readData = 0;
long deadline;
struct timespec t_spec;
do{
if (data_left > UNIT_SIZE) {
cur_length = UNIT_SIZE;
} else {
cur_length = data_left;
}
if (cur_length) {
// write data
rc = synapticsmst_common_write_dpcd (REG_RC_DATA, (gint *)buf, cur_length);
if (rc) {
break;
}
//write offset
rc = synapticsmst_common_write_dpcd (REG_RC_OFFSET, &cur_offset, 4);
if (rc) {
break;
}
// write length
rc = synapticsmst_common_write_dpcd (REG_RC_LEN, &cur_length, 4);
if (rc) {
break;
}
}
// send command
cmd = 0x80 | rc_cmd;
rc = synapticsmst_common_write_dpcd (REG_RC_CMD, &cmd, 1);
if (rc) {
break;
}
// wait command complete
clock_gettime (CLOCK_REALTIME, &t_spec);
deadline = t_spec.tv_sec + MAX_WAIT_TIME;
do {
rc = synapticsmst_common_read_dpcd (REG_RC_CMD, &readData, 2);
clock_gettime (CLOCK_REALTIME, &t_spec);
if (t_spec.tv_sec > deadline) {
rc = -1;
}
} while (rc == 0 && readData & 0x80);
if (rc) {
// g_debug ("checking result timeout");
break;
}
else if (readData & 0xFF00) {
rc = (readData >> 8) & 0xFF;
break;
}
buf += cur_length;
cur_offset += cur_length;
data_left -= cur_length;
} while (data_left);
return rc;
}
guchar
synapticsmst_common_rc_get_command(gint rc_cmd, gint length, gint offset, guchar *buf)
{
guchar rc = 0;
gint cur_offset = offset;
gint cur_length;
gint data_need = length;
gint cmd;
gint readData = 0;
long deadline;
struct timespec t_spec;
while (data_need) {
if (data_need > UNIT_SIZE) {
cur_length = UNIT_SIZE;
}
else {
cur_length = data_need;
}
if (cur_length) {
//write offset
rc = synapticsmst_common_write_dpcd (REG_RC_OFFSET, &cur_offset, 4);
if (rc) {
break;
}
// write length
rc = synapticsmst_common_write_dpcd (REG_RC_LEN, &cur_length, 4);
if (rc) {
break;
}
}
// send command
cmd = 0x80 | rc_cmd;
rc = synapticsmst_common_write_dpcd (REG_RC_CMD, &cmd, 1);
if (rc) {
break;
}
// wait command complete
clock_gettime (CLOCK_REALTIME, &t_spec);
deadline = t_spec.tv_sec + MAX_WAIT_TIME;
do {
rc = synapticsmst_common_read_dpcd (REG_RC_CMD, &readData, 2);
clock_gettime (CLOCK_REALTIME, &t_spec);
if (t_spec.tv_sec > deadline) {
rc = -1;
}
} while (rc == 0 && readData & 0x80);
if (rc) {
// g_debug ("checking result timeout");
break;
}
else if (readData & 0xFF00) {
rc = (readData >> 8) & 0xFF;
break;
}
if (cur_length) {
rc = synapticsmst_common_read_dpcd (REG_RC_DATA, (gint *)buf, cur_length);
if (rc) {
break;
}
}
buf += cur_length;
cur_offset += cur_length;
data_need -= cur_length;
}
return rc;
}
guchar
synapticsmst_common_rc_special_get_command (gint rc_cmd,
gint cmd_length,
gint cmd_offset,
guchar *cmd_data,
gint length,
guchar *buf)
{
guchar rc = 0;
gint readData = 0;
gint cmd;
long deadline;
struct timespec t_spec;
do {
if (cmd_length) {
// write cmd data
if (cmd_data != NULL) {
rc = synapticsmst_common_write_dpcd (REG_RC_DATA, (gint *)cmd_data, cmd_length);
if (rc) {
break;
}
}
// write offset
rc = synapticsmst_common_write_dpcd (REG_RC_OFFSET, &cmd_offset, 4);
if (rc) {
break;
}
// write length
rc = synapticsmst_common_write_dpcd (REG_RC_LEN, &cmd_length, 4);
if (rc) {
break;
}
}
// send command
cmd = 0x80 | rc_cmd;
rc = synapticsmst_common_write_dpcd (REG_RC_CMD, &cmd, 1);
if (rc) {
break;
}
// wait command complete
clock_gettime (CLOCK_REALTIME, &t_spec);
deadline = t_spec.tv_sec + MAX_WAIT_TIME;
do {
rc = synapticsmst_common_read_dpcd (REG_RC_CMD, &readData, 2);
clock_gettime (CLOCK_REALTIME, &t_spec);
if (t_spec.tv_sec > deadline) {
rc = -1;
}
} while (rc == 0 && readData & 0x80);
if (rc) {
// g_debug ("checking result timeout");
break;
}
else if (readData & 0xFF00) {
rc = (readData >> 8) & 0xFF;
break;
}
if (length) {
rc = synapticsmst_common_read_dpcd (REG_RC_DATA, (gint *)buf, length);
if (rc) {
break;
}
}
} while (0);
return rc;
}
gboolean synapticsmst_common_check_supported_system (GError **error)
{
guint8 dell_supported = 0;
struct smbios_struct *de_table;
de_table = smbios_get_next_struct_by_type (0, 0xDE);
smbios_struct_get_data (de_table, &(dell_supported), 0x00, sizeof(guint8));
if (dell_supported != 0xDE) {
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"SynapticsMST: firmware updating not supported. (%x)",
dell_supported);
return FALSE;
}
return TRUE;
}

View File

@ -0,0 +1,90 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
*
* Copyright (C) 2015 Richard Hughes <richard@hughsie.com>
* Copyright (C) 2016 Mario Limonciello <mario.limonciello@dell.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
*/
#ifndef __SYNAPTICSMST_COMMON_H
#define __SYNAPTICSMST_COMMON_H
#include <glib.h>
#include <gio/gio.h>
#define ADDR_CUSTOMER_ID 0X10E
#define ADDR_BOARD_ID 0x10F
#define REG_RC_CAP 0x4B0
#define REG_RC_STATE 0X4B1
#define REG_RC_CMD 0x4B2
#define REG_RC_RESULT 0x4B3
#define REG_RC_LEN 0x4B8
#define REG_RC_OFFSET 0x4BC
#define REG_RC_DATA 0x4C0
#define REG_VENDOR_ID 0x500
#define REG_CHIP_ID 0x507
#define REG_FIRMWARE_VERSIOIN 0x50A
typedef enum {
UPDC_COMMAND_SUCCESS = 0,
UPDC_COMMAND_INVALID,
UPDC_COMMAND_UNSUPPORT,
UPDC_COMMAND_FAILED,
UPDC_COMMAND_DISABLED,
}RC_STATUS;
typedef enum {
UPDC_ENABLE_RC = 1,
UPDC_DISABLE_RC,
UPDC_GET_ID,
UPDC_GET_VERSION,
UPDC_ENABLE_FLASH_CHIP_ERASE = 8,
UPDC_CAL_EEPROM_CHECKSUM = 0X11,
UPDC_FLASH_ERASE = 0X14,
UPDC_CAL_EEPROM_CHECK_CRC8 = 0X16,
UPDC_CAL_EEPROM_CHECK_CRC16,
UPDC_WRITE_TO_EEPROM = 0X20,
UPDC_READ_FROM_EEPROM = 0X30,
}RC_COMMAND;
guchar synapticsmst_common_read_dpcd (gint offset,
gint *buf,
gint length);
guchar synapticsmst_common_write_dpcd (gint offset,
gint *buf,
gint length);
guchar synapticsmst_common_open_aux_node (const gchar *filename);
void synapticsmst_common_close_aux_node (void);
guchar synapticsmst_common_rc_set_command (gint rc_cmd,
gint length,
gint offset,
guchar *buf);
guchar synapticsmst_common_rc_get_command (gint rc_cmd,
gint length,
gint offset,
guchar *buf);
guchar synapticsmst_common_rc_special_get_command (gint rc_cmd,
gint cmd_length,
gint cmd_offset,
guchar *cmd_data,
gint length,
guchar *buf);
gboolean synapticsmst_common_check_supported_system (GError **error);
#endif /* __SYNAPTICSMST_COMMON_H */

View File

@ -0,0 +1,591 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
*
* Copyright (C) 2015 Richard Hughes <richard@hughsie.com>
* Copyright (C) 2016 Mario Limonciello <mario.limonciello@dell.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 <smbios_c/system_info.h>
#include "synapticsmst-device.h"
#include "synapticsmst-common.h"
#define BLOCK_UNIT 64
typedef struct
{
SynapticsMSTDeviceKind kind;
const gchar *devfs_node;
gchar *version;
SynapticsMSTDeviceBoardID boardID;
gchar *chipID;
gchar *guid;
} SynapticsMSTDevicePrivate;
G_DEFINE_TYPE_WITH_PRIVATE (SynapticsMSTDevice, synapticsmst_device, G_TYPE_OBJECT)
#define GET_PRIVATE(o) (synapticsmst_device_get_instance_private (o))
/**
* synapticsmst_device_kind_from_string:
* @kind: the string.
*
* Converts the text representation to an enumerated value.
*
* Returns: (transfer full): a #SynapticsMSTDeviceKind, or %SYNAPTICSMST_DEVICE_KIND_UNKNOWN for unknown.
*
* Since: 0.1.0
**/
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;
}
/**
* synapticsmst_device_kind_to_string:
* @kind: the #SynapticsMSTDeviceKind.
*
* Converts the enumerated value to an text representation.
*
* Returns: string version of @kind
*
* Since: 0.1.0
**/
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_boardID_to_string (SynapticsMSTDeviceBoardID boardID)
{
if (boardID == SYNAPTICSMST_DEVICE_BOARDID_SYNA_EVB)
return "SYNA evb baord";
if (boardID == SYNAPTICSMST_DEVICE_BOARDID_X6)
return "Dell X6 Dock";
if (boardID == SYNAPTICSMST_DEVICE_BOARDID_X7)
return "Dell X7 Dock";
if (boardID == SYNAPTICSMST_DEVICE_BOARDID_TRINITY_WIRE)
return "Dell Trinity Wire Dock";
if (boardID == SYNAPTICSMST_DEVICE_BOARDID_TRINITY_WIRELESS)
return "Dell Trinity Wireless Dock";
return NULL;
}
/**
* synapticsmst_device_get_guid:
*
* Returns: string version of GUID for use with fwupd
*
**/
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);
g_free (priv->version);
g_free (priv->chipID);
g_free (priv->guid);
G_OBJECT_CLASS (synapticsmst_device_parent_class)->finalize (object);
}
static void
synapticsmst_device_init (SynapticsMSTDevice *device)
{
}
static void
synapticsmst_device_class_init (SynapticsMSTDeviceClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = synapticsmst_device_finalize;
}
/**
* synapticsmst_device_get_kind:
* @device: a #SynapticsMSTDevice instance.
*
* Gets the device kind.
*
* Returns: the #SynapticsMSTDeviceKind
*
* Since: 0.1.0
**/
SynapticsMSTDeviceKind
synapticsmst_device_get_kind (SynapticsMSTDevice *device)
{
SynapticsMSTDevicePrivate *priv = GET_PRIVATE (device);
return priv->kind;
}
SynapticsMSTDeviceBoardID
synapticsmst_device_get_boardID (SynapticsMSTDevice *device)
{
SynapticsMSTDevicePrivate *priv = GET_PRIVATE (device);
return priv->boardID;
}
gboolean
synapticsmst_device_enable_remote_control (SynapticsMSTDevice *device, GError **error)
{
const gchar *sc = "PRIUS";
if (synapticsmst_common_rc_set_command (UPDC_ENABLE_RC, 5, 0, (guchar*)sc)) {
g_set_error_literal (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"Failed to enable MST remote control");
return FALSE;
} else {
return TRUE;
}
}
gboolean
synapticsmst_device_disable_remote_control (SynapticsMSTDevice *device, GError **error)
{
if (synapticsmst_common_rc_set_command (UPDC_DISABLE_RC, 0, 0, (guchar*)NULL)) {
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_enumerate_device (SynapticsMSTDevice *device, GError **error)
{
SynapticsMSTDevicePrivate *priv = GET_PRIVATE (device);
guint8 byte[16];
guint16 system_id;
if (synapticsmst_common_open_aux_node(priv->devfs_node)) {
guint8 ret;
// enable remote control
if (!synapticsmst_device_enable_remote_control(device, error)) {
return FALSE;
}
// read firmware version
ret = synapticsmst_common_read_dpcd (REG_FIRMWARE_VERSIOIN, (gint *)byte, 3);
if (ret) {
g_set_error_literal (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"Failed to read dpcd from device");
return FALSE;
}
priv->version = g_strdup_printf("%1d.%02d.%03d", byte[0], byte[1], byte[2]);
// read board ID
synapticsmst_common_rc_get_command(UPDC_READ_FROM_EEPROM, 2, ADDR_CUSTOMER_ID, byte);
if (ret) {
g_set_error_literal (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"Failed to read from EEPROM of device");
return FALSE;
}
if (byte[0] == 0x01) {
priv->boardID = (byte[0] << 8) | (byte[1]);
} else if (byte[0] == 0x00) {
priv->boardID = (byte[0] << 8) | (byte[1]); // remove this when release
} else {
priv->boardID = 0;
}
// read board chipID
synapticsmst_common_read_dpcd (REG_CHIP_ID, (gint *)byte, 2);
if (ret) {
g_set_error_literal (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"Failed to read dpcd from device");
return FALSE;
}
priv->chipID = g_strdup_printf("VMM%02x%02x", byte[0], byte[1]);
/* set up GUID */
/* If this is a dock, ignore system ID */
system_id = (guint16) sysinfo_get_dell_system_id ();
if ((priv->boardID == SYNAPTICSMST_DEVICE_BOARDID_TRINITY_WIRE) ||
(priv->boardID == SYNAPTICSMST_DEVICE_BOARDID_TRINITY_WIRELESS))
priv->guid = g_strdup_printf ("MST-dell-bmeie-%u",
priv->boardID);
/* This is a host system */
else
priv->guid = g_strdup_printf ("MST-dell-%04x-%u",
system_id, priv->boardID);
// disable remote control and close aux node
synapticsmst_device_disable_remote_control(device,error);
synapticsmst_common_close_aux_node();
} else {
//g_print ("fail to open aux node %s\n", priv->devfs_node);
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"Failed to open device in DP Aux Node %d",
synapticsmst_device_get_aux_node_to_int (device));
return FALSE;
}
return TRUE;
}
const gchar *
synapticsmst_device_get_version (SynapticsMSTDevice *device)
{
SynapticsMSTDevicePrivate *priv = GET_PRIVATE (device);
return priv->version;
}
const gchar *
synapticsmst_device_get_chipID (SynapticsMSTDevice *device)
{
SynapticsMSTDevicePrivate *priv = GET_PRIVATE (device);
return priv->chipID;
}
static gboolean
synapticsmst_device_get_flash_checksum (SynapticsMSTDevice *device, gint length, gint offset, guint32 *checksum, GError **error)
{
if (synapticsmst_common_rc_special_get_command (UPDC_CAL_EEPROM_CHECKSUM,
length, offset,
NULL, 4,
(guchar *) 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 offset = 0;
guint32 write_loops = 0;
guint32 data_to_write = 0;
guint8 percentage = 0;
guint8 ret = 0;
guint16 tmp;
SynapticsMSTDevicePrivate *priv = GET_PRIVATE (device);
// 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,
"Failed to flash firmware: 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,
"Failed to flash firmware: 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,
"Failed to flash firmware: 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,
"Failed to flash firmware : 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,
"Failed to flash firmware : 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,
"Failed to flash firmware: 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,
"Failed to flash firmware: firmware checksum error");
return FALSE;
}
// check firmware and board ID again
tmp = (*(payload_data + 0x10E) << 8) + *(payload_data + 0x10F);
if (tmp != synapticsmst_device_get_boardID(device)) {
g_set_error_literal (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"Failed to flash firmware: board ID mismatch");
return FALSE;
}
if (synapticsmst_common_open_aux_node(priv->devfs_node)) {
guint16 erase_ctrl = 0xFFFF;
// enable remote control
if (!synapticsmst_device_enable_remote_control(device, error)) {
return FALSE;
}
// erase SPI flash
if (synapticsmst_common_rc_set_command (UPDC_FLASH_ERASE, 2, 0, (guint8 *)&erase_ctrl)) {
g_set_error_literal (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"Failed to flash firmware: can't erase flash");
return FALSE;
}
// update firmware
//g_print ("Writing FW to %s\n", priv->devfs_node);
write_loops = (payload_len / BLOCK_UNIT);
data_to_write = payload_len;
ret = 0;
offset = 0;
if (payload_len % BLOCK_UNIT) {
write_loops++;
}
if (progress_cb == NULL)
g_print ("updating... 0%%");
for (guint32 i = 0; i < write_loops; i++) {
guint8 length = BLOCK_UNIT;
if (data_to_write < BLOCK_UNIT) {
length = data_to_write;
}
ret = synapticsmst_common_rc_set_command (UPDC_WRITE_TO_EEPROM,
length, offset,
payload_data + offset);
if (ret) {
ret = synapticsmst_common_rc_set_command (UPDC_WRITE_TO_EEPROM,
length, offset,
payload_data + offset); // repeat once
}
if (ret) {
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_print ("\rupdating... %d%%", percentage);
}
if (progress_cb == NULL)
g_print ("\n");
if (ret) {
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"Failed to flash firmware: "
"can't write flash at offset 0x%04x",
offset);
} else {
guint32 flash_checksum = 0;
// check data just written
checksum = 0;
for (guint32 i = 0; i < payload_len; i++) {
checksum += *(payload_data + i);
}
if (synapticsmst_device_get_flash_checksum (device,
payload_len,
0,
&flash_checksum,
error)) {
if (checksum != flash_checksum) {
ret = -1;
g_set_error_literal (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"Failed to flash firmware: "
"checksum mismatch");
}
} else {
ret = -1;
}
}
// disable remote control and close aux node
synapticsmst_device_disable_remote_control(device, error);
synapticsmst_common_close_aux_node();
} else {
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"Failed to flash firmware : can't open DP Aux node %d",
synapticsmst_device_get_aux_node_to_int (device));
return FALSE;
}
if (ret) {
return FALSE;
} else {
return TRUE;
}
}
/**
* synapticsmst_device_new:
*
* Creates a new #SynapticsMSTDevice.
*
* Returns: (transfer full): a #SynapticsMSTDevice
*
* Since: 0.1.0
**/
SynapticsMSTDevice *
synapticsmst_device_new (SynapticsMSTDeviceKind kind, const gchar *aux_node)
{
SynapticsMSTDevice *device;
SynapticsMSTDevicePrivate *priv;
device = g_object_new (SYNAPTICSMST_TYPE_DEVICE, NULL);
priv = GET_PRIVATE (device);
priv->devfs_node = aux_node;
priv->kind = kind;
priv->version = NULL;
return SYNAPTICSMST_DEVICE (device);
}
const gchar *
synapticsmst_device_get_aux_node (guint8 index)
{
if (index == 0) {
return "/dev/drm_dp_aux0";
} else if (index == 1) {
return "/dev/drm_dp_aux1";
} else if (index == 2) {
return "/dev/drm_dp_aux2";
} else {
return "";
}
}
guint8
synapticsmst_device_get_aux_node_to_int (SynapticsMSTDevice *device)
{
SynapticsMSTDevicePrivate *priv = GET_PRIVATE (device);
if (g_strcmp0(priv->devfs_node, "/dev/drm_dp_aux0") == 0) {
return 0;
}
if (g_strcmp0(priv->devfs_node, "/dev/drm_dp_aux1") == 0) {
return 1;
}
if (g_strcmp0(priv->devfs_node, "/dev/drm_dp_aux2") == 0) {
return 2;
}
return 0xFF;
}

View File

@ -0,0 +1,109 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
*
* Copyright (C) 2015 Richard Hughes <richard@hughsie.com>
* Copyright (C) 2016 Mario Limonciello <mario.limonciello@dell.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
*/
#ifndef __SYNAPTICSMST_DEVICE_H
#define __SYNAPTICSMST_DEVICE_H
#include <glib-object.h>
#include <gio/gio.h>
G_BEGIN_DECLS
#define SYNAPTICSMST_TYPE_DEVICE (synapticsmst_device_get_type ())
G_DECLARE_DERIVABLE_TYPE (SynapticsMSTDevice, synapticsmst_device, SYNAPTICSMST, DEVICE, GObject)
#define MAX_DP_AUX_NODES 3
struct _SynapticsMSTDeviceClass
{
GObjectClass parent_class;
/*< private >*/
void (*_as_reserved1) (void);
void (*_as_reserved2) (void);
void (*_as_reserved3) (void);
void (*_as_reserved4) (void);
void (*_as_reserved5) (void);
void (*_as_reserved6) (void);
void (*_as_reserved7) (void);
void (*_as_reserved8) (void);
};
/**
* SynapticsMSTDeviceKind:
* @SYNAPTICSMST_DEVICE_KIND_UNKNOWN: Type invalid or not known
* @SYNAPTICSMST_DEVICE_KIND_DIRECT: Directly addressable
* @SYNAPTICSMST_DEVICE_KIND_REMOTE: Requires remote register work
*
* The device type.
**/
typedef enum {
SYNAPTICSMST_DEVICE_KIND_UNKNOWN,
SYNAPTICSMST_DEVICE_KIND_DIRECT,
SYNAPTICSMST_DEVICE_KIND_REMOTE,
/*< private >*/
SYNAPTICSMST_DEVICE_KIND_LAST
} SynapticsMSTDeviceKind;
typedef enum {
SYNAPTICSMST_DEVICE_BOARDID_UNKNOW = 0,
SYNAPTICSMST_DEVICE_BOARDID_SYNA_EVB = 0x0082, // should be removed before release
SYNAPTICSMST_DEVICE_BOARDID_X6 = 0x0110,
SYNAPTICSMST_DEVICE_BOARDID_X7,
SYNAPTICSMST_DEVICE_BOARDID_TRINITY_WIRE,
SYNAPTICSMST_DEVICE_BOARDID_TRINITY_WIRELESS
} SynapticsMSTDeviceBoardID;
SynapticsMSTDevice *synapticsmst_device_new (SynapticsMSTDeviceKind kind,
const gchar *aux_node);
const gchar *synapticsmst_device_get_aux_node (guint8 index);
/* helpers */
SynapticsMSTDeviceKind synapticsmst_device_kind_from_string (const gchar *kind);
const gchar *synapticsmst_device_kind_to_string (SynapticsMSTDeviceKind kind);
const gchar *synapticsmst_device_boardID_to_string (SynapticsMSTDeviceBoardID boardID);
const gchar *synapticsmst_device_get_guid (SynapticsMSTDevice *device);
gboolean synapticsmst_device_enable_remote_control (SynapticsMSTDevice *device,
GError **error);
gboolean synapticsmst_device_disable_remote_control (SynapticsMSTDevice *device,
GError **error);
/* getters */
SynapticsMSTDeviceKind synapticsmst_device_get_kind (SynapticsMSTDevice *device);
SynapticsMSTDeviceBoardID synapticsmst_device_get_boardID (SynapticsMSTDevice *device);
const gchar *synapticsmst_device_get_devfs_node (SynapticsMSTDevice *device);
const gchar *synapticsmst_device_get_version (SynapticsMSTDevice *device);
const gchar *synapticsmst_device_get_chipID (SynapticsMSTDevice *device);
guint8 synapticsmst_device_get_aux_node_to_int(SynapticsMSTDevice *device);
/* object methods */
gboolean synapticsmst_device_enumerate_device (SynapticsMSTDevice *devices,
GError **error);
gboolean synapticsmst_device_write_firmware (SynapticsMSTDevice *device,
GBytes *fw,
GFileProgressCallback progress_cb,
gpointer user_data,
GError **error);
G_END_DECLS
#endif /* __SYNAPTICSMST_DEVICE_H */

View File

@ -0,0 +1,400 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
*
* Copyright (C) 2015 Richard Hughes <richard@hughsie.com>
* Copyright (C) 2016 Mario Limonciello <mario_limonciello@dell.com>
*
* Licensed under the GNU General Public License Version 2
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "synapticsmst-device.h"
#include "synapticsmst-common.h"
#include <stdlib.h>
#include <stdio.h>
#include <glib/gi18n.h>
#include <glib-unix.h>
#include <libintl.h>
#include <locale.h>
#include <libfwupd/fwupd-error.h>
typedef struct {
GCancellable *cancellable;
GPtrArray *cmd_array;
gboolean force;
GPtrArray *device_array;
} SynapticsMSTToolPrivate;
static void
synapticsmst_tool_private_free (SynapticsMSTToolPrivate *priv)
{
if (priv == NULL)
return;
g_object_unref (priv->cancellable);
if (priv->cmd_array != NULL)
g_ptr_array_unref (priv->cmd_array);
if (priv->device_array != NULL)
g_ptr_array_unref (priv->device_array);
g_free (priv);
}
G_DEFINE_AUTOPTR_CLEANUP_FUNC(SynapticsMSTToolPrivate, synapticsmst_tool_private_free)
typedef gboolean (*FuUtilPrivateCb) (SynapticsMSTToolPrivate *util,
gchar **values,
guint8 device_index,
GError **error);
typedef struct {
gchar *name;
gchar *arguments;
gchar *description;
FuUtilPrivateCb callback;
} FuUtilItem;
static void
synapticsmst_tool_item_free (FuUtilItem *item)
{
g_free (item->name);
g_free (item->arguments);
g_free (item->description);
g_free (item);
}
static gint
synapticsmst_tool_sort_command_name_cb (FuUtilItem **item1, FuUtilItem **item2)
{
return g_strcmp0 ((*item1)->name, (*item2)->name);
}
static void
synapticsmst_tool_add (GPtrArray *array,
const gchar *name,
const gchar *arguments,
const gchar *description,
FuUtilPrivateCb callback)
{
guint i;
FuUtilItem *item;
g_auto(GStrv) names = NULL;
g_return_if_fail (name != NULL);
g_return_if_fail (description != NULL);
g_return_if_fail (callback != NULL);
/* add each one */
names = g_strsplit (name, ",", -1);
for (i = 0; names[i] != NULL; i++) {
item = g_new0 (FuUtilItem, 1);
item->name = g_strdup (names[i]);
if (i == 0) {
item->description = g_strdup (description);
} else {
/* TRANSLATORS: this is a command alias, e.g. 'get-devices' */
item->description = g_strdup_printf (_("Alias to %s"),
names[0]);
}
item->arguments = g_strdup (arguments);
item->callback = callback;
g_ptr_array_add (array, item);
}
}
static gchar *
synapticsmst_tool_get_descriptions (GPtrArray *array)
{
guint i;
gsize j;
gsize len;
const gsize max_len = 31;
FuUtilItem *item;
GString *string;
/* print each command */
string = g_string_new ("");
for (i = 0; i < array->len; i++) {
item = g_ptr_array_index (array, i);
g_string_append (string, " ");
g_string_append (string, item->name);
len = strlen (item->name) + 2;
if (item->arguments != NULL) {
g_string_append (string, " ");
g_string_append (string, item->arguments);
len += strlen (item->arguments) + 1;
}
if (len < max_len) {
for (j = len; j < max_len + 1; j++)
g_string_append_c (string, ' ');
g_string_append (string, item->description);
g_string_append_c (string, '\n');
} else {
g_string_append_c (string, '\n');
for (j = 0; j < max_len + 1; j++)
g_string_append_c (string, ' ');
g_string_append (string, item->description);
g_string_append_c (string, '\n');
}
}
/* remove trailing newline */
if (string->len > 0)
g_string_set_size (string, string->len - 1);
return g_string_free (string, FALSE);
}
static gboolean
synapticsmst_tool_scan_aux_nodes (SynapticsMSTToolPrivate *priv, GError **error)
{
gboolean ret = FALSE;
priv->device_array = g_ptr_array_new ();
for (guint8 i = 0; i < MAX_DP_AUX_NODES; i++) {
if (synapticsmst_common_open_aux_node(synapticsmst_device_get_aux_node (i))) {
g_autoptr(SynapticsMSTDevice) device = NULL;
device = synapticsmst_device_new (SYNAPTICSMST_DEVICE_KIND_DIRECT,
synapticsmst_device_get_aux_node(i));
g_ptr_array_add (priv->device_array, g_object_ref(device));
synapticsmst_common_close_aux_node ();
ret = TRUE;
}
}
if (!ret) {
g_set_error_literal (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"No Synaptics MST Device found");
}
return ret;
}
static gboolean
synapticsmst_tool_enumerate (SynapticsMSTToolPrivate *priv,
gchar **values,
guint8 device_index,
GError **error)
{
SynapticsMSTDevice *device = NULL;
g_print ("\nMST Devices :\n");
for (guint8 i = 0; i < priv->device_array->len; i++) {
device = g_ptr_array_index (priv->device_array, i);
g_print ("[Device %1d]\n", i+1);
if (synapticsmst_device_enumerate_device (device, error)) {
const gchar *boardID = synapticsmst_device_boardID_to_string (synapticsmst_device_get_boardID (device));
if (boardID != NULL) {
g_print ("Device : %s with Synaptics %s\n",
boardID,
synapticsmst_device_get_chipID (device));
g_print ("Connect Type : %s in DP Aux Node %d\n",
synapticsmst_device_kind_to_string (synapticsmst_device_get_kind (device)),
synapticsmst_device_get_aux_node_to_int (device));
g_print ("Firmware version : %s\n", synapticsmst_device_get_version (device));
} else {
g_print ("Unknown Device\n");
}
g_print ("\n");
} else {
return FALSE;
}
}
return TRUE;
}
static gboolean
synapticsmst_tool_flash (SynapticsMSTToolPrivate *priv,
gchar **values,
guint8 device_index,
GError **error)
{
SynapticsMSTDevice *device = NULL;
gsize len;
g_autofree guint8 *data = NULL;
g_autoptr(GBytes) fw = NULL;
/* incorrect args */
if (g_strv_length (values) != 1) {
g_set_error_literal (error,
G_IO_ERROR,
G_IO_ERROR_FAILED,
"Incorrect arguments, expected FILENAME]");
return FALSE;
}
device = g_ptr_array_index(priv->device_array, (device_index - 1));
if (synapticsmst_device_enumerate_device (device, error)) {
if (synapticsmst_device_boardID_to_string (synapticsmst_device_get_boardID (device)) != NULL) {
g_autoptr(GError) error_local = NULL;
if (!g_file_get_contents (values[0],
(gchar **) &data, &len,
&error_local)) {
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"Failed to flash firmware: "
"can't load file %s: %s",
values[0],
error_local->message);
return FALSE;
}
fw = g_bytes_new(data, len);
if (!synapticsmst_device_write_firmware (device, fw, NULL, NULL, error))
return FALSE;
g_print ("Update Sucessfully. Please reset device to apply new firmware.\n");
} else {
g_set_error_literal (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"Failed to flash firmware: unknown device");
return FALSE;
}
} else {
return FALSE;
}
return TRUE;
}
static gboolean
synapticsmst_tool_run (SynapticsMSTToolPrivate *priv,
const gchar *command,
gchar **values,
guint8 device_index,
GError **error)
{
guint i;
FuUtilItem *item;
/* find command */
for (i = 0; i < priv->cmd_array->len; i++) {
item = g_ptr_array_index (priv->cmd_array, i);
if (g_strcmp0 (item->name, command) == 0)
return item->callback (priv, values, device_index, error);
}
/* not found */
g_set_error_literal (error,
G_IO_ERROR,
G_IO_ERROR_FAILED,
/* TRANSLATORS: error message */
_("Command not found"));
return FALSE;
}
static gboolean
synapticsmst_tool_sigint_cb (gpointer user_data)
{
SynapticsMSTToolPrivate *priv = (SynapticsMSTToolPrivate *) user_data;
g_debug ("Handling SIGINT");
g_cancellable_cancel (priv->cancellable);
return FALSE;
}
int
main (int argc, char **argv)
{
gboolean ret;
gboolean verbose = FALSE;
guint8 device_index = 0;
g_autofree gchar *cmd_descriptions = NULL;
g_autoptr(SynapticsMSTToolPrivate) priv = g_new0 (SynapticsMSTToolPrivate, 1);
//g_autoptr(SynapticsMSTDevice) device = NULL;
g_autoptr(GError) error = NULL;
g_autoptr(GOptionContext) context = NULL;
const GOptionEntry options[] = {
{ "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose,
"Print verbose debug statements", NULL },
{ "force", '\0', 0, G_OPTION_ARG_NONE, &priv->force,
"Force the action ignoring all warnings", NULL },
{ NULL}
};
setlocale (LC_ALL, "");
bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
textdomain (GETTEXT_PACKAGE);
/* add commands */
priv->cmd_array = g_ptr_array_new_with_free_func ((GDestroyNotify) synapticsmst_tool_item_free);
synapticsmst_tool_add (priv->cmd_array,
"enumerate",
NULL,
/* TRANSLATORS: command description */
_("Enumerate all Synaptics MST devices"),
synapticsmst_tool_enumerate);
synapticsmst_tool_add (priv->cmd_array,
"flash",
NULL,
/* TRANSLATORS: command description */
_("Flash firmware file to MST device"),
synapticsmst_tool_flash);
/* do stuff on ctrl+c */
priv->cancellable = g_cancellable_new ();
g_unix_signal_add_full (G_PRIORITY_DEFAULT,
SIGINT,
synapticsmst_tool_sigint_cb,
priv,
NULL);
/* sort by command name */
g_ptr_array_sort (priv->cmd_array,
(GCompareFunc) synapticsmst_tool_sort_command_name_cb);
/* get a list of the commands */
context = g_option_context_new (NULL);
cmd_descriptions = synapticsmst_tool_get_descriptions (priv->cmd_array);
g_option_context_set_summary (context, cmd_descriptions);
g_set_application_name (_("Synaptics Multistream Transport Utility"));
g_option_context_add_main_entries (context, options, NULL);
ret = g_option_context_parse (context, &argc, &argv, &error);
if (!ret) {
/* TRANSLATORS: the user didn't read the man page */
g_print ("%s: %s\n", _("Failed to parse arguments"), error->message);
return EXIT_FAILURE;
}
/* set verbose? */
if (verbose)
g_setenv ("G_MESSAGES_DEBUG", "all", FALSE);
/* check avaliable dp aux nodes and add devices */
ret = synapticsmst_tool_scan_aux_nodes(priv, &error);
if (!ret) {
g_print ("%s\n", error->message);
return EXIT_FAILURE;
}
/* run the specified command */
if (argc == 4) {
device_index = strtol(argv[3], NULL, 10);
}
ret = synapticsmst_tool_run (priv, argv[1], (gchar**) &argv[2], device_index, &error);
if (!ret) {
g_print ("%s\n", error->message);
return EXIT_FAILURE;
}
/* success/ */
return EXIT_SUCCESS;
}

View File

@ -1,5 +1,6 @@
policy/org.freedesktop.fwupd.policy.in
libdfu/dfu-tool.c
plugins/synapticsmst/synapticsmst-tool.c
src/fu-debug.c
src/fu-main.c
src/fu-util.c