diff --git a/configure.ac b/configure.ac index a243c0d2e..65f7dade4 100644 --- a/configure.ac +++ b/configure.ac @@ -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 " diff --git a/plugins/Makefile.am b/plugins/Makefile.am index c8c13ec84..1dc1e1adb 100644 --- a/plugins/Makefile.am +++ b/plugins/Makefile.am @@ -3,6 +3,7 @@ SUBDIRS = \ dfu \ ebitdo \ raspberrypi \ + synapticsmst \ steelseries \ test \ udev \ diff --git a/plugins/synapticsmst/Makefile.am b/plugins/synapticsmst/Makefile.am new file mode 100644 index 000000000..72968e7a3 --- /dev/null +++ b/plugins/synapticsmst/Makefile.am @@ -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 diff --git a/plugins/synapticsmst/README.md b/plugins/synapticsmst/README.md new file mode 100644 index 000000000..8ba51ba60 --- /dev/null +++ b/plugins/synapticsmst/README.md @@ -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 diff --git a/plugins/synapticsmst/fu-dell-flash.h b/plugins/synapticsmst/fu-dell-flash.h new file mode 120000 index 000000000..fd9460b8c --- /dev/null +++ b/plugins/synapticsmst/fu-dell-flash.h @@ -0,0 +1 @@ +../dell/fu-dell-flash.h \ No newline at end of file diff --git a/plugins/synapticsmst/fu-plugin-dell.h b/plugins/synapticsmst/fu-plugin-dell.h new file mode 120000 index 000000000..f02e003c2 --- /dev/null +++ b/plugins/synapticsmst/fu-plugin-dell.h @@ -0,0 +1 @@ +../dell/fu-plugin-dell.h \ No newline at end of file diff --git a/plugins/synapticsmst/fu-plugin-synapticsmst.c b/plugins/synapticsmst/fu-plugin-synapticsmst.c new file mode 100644 index 000000000..fd40a76c2 --- /dev/null +++ b/plugins/synapticsmst/fu-plugin-synapticsmst.c @@ -0,0 +1,197 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2016 Mario Limonciello + * + * 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 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; +} diff --git a/plugins/synapticsmst/synapticsmst-common.c b/plugins/synapticsmst/synapticsmst-common.c new file mode 100644 index 000000000..59e8402e1 --- /dev/null +++ b/plugins/synapticsmst/synapticsmst-common.c @@ -0,0 +1,349 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2015 Richard Hughes + * Copyright (C) 2016 Mario Limonciello + * + * 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 +#include +#include +#include +#include +#include +#include + +#include + +//#include +//#include +#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; +} diff --git a/plugins/synapticsmst/synapticsmst-common.h b/plugins/synapticsmst/synapticsmst-common.h new file mode 100644 index 000000000..dc958ad64 --- /dev/null +++ b/plugins/synapticsmst/synapticsmst-common.h @@ -0,0 +1,90 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2015 Richard Hughes + * Copyright (C) 2016 Mario Limonciello + * + * 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 +#include + +#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 */ diff --git a/plugins/synapticsmst/synapticsmst-device.c b/plugins/synapticsmst/synapticsmst-device.c new file mode 100644 index 000000000..6b7936028 --- /dev/null +++ b/plugins/synapticsmst/synapticsmst-device.c @@ -0,0 +1,591 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2015 Richard Hughes + * Copyright (C) 2016 Mario Limonciello + * + * 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 +#include +#include + +#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; +} diff --git a/plugins/synapticsmst/synapticsmst-device.h b/plugins/synapticsmst/synapticsmst-device.h new file mode 100644 index 000000000..fcb8b00fc --- /dev/null +++ b/plugins/synapticsmst/synapticsmst-device.h @@ -0,0 +1,109 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2015 Richard Hughes + * Copyright (C) 2016 Mario Limonciello + * + * 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 +#include + +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 */ diff --git a/plugins/synapticsmst/synapticsmst-tool.c b/plugins/synapticsmst/synapticsmst-tool.c new file mode 100644 index 000000000..e3b02a2ae --- /dev/null +++ b/plugins/synapticsmst/synapticsmst-tool.c @@ -0,0 +1,400 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2015 Richard Hughes + * Copyright (C) 2016 Mario Limonciello + * + * 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 +#include +#include +#include +#include +#include +#include + +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; +} diff --git a/po/POTFILES.in b/po/POTFILES.in index d0bf954cf..77542c85c 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -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