mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-16 02:34:37 +00:00

The synapticsmst-common.c file had some global state so that cascade devices could use the device fd. This made the control flow error prone, and it meant that the fd could be leaked trivially on any error path. Moving the fd ownership to the device is the logical place for the control, and means we can create "connections" to access the main device and the cascade devices. This fixes the warnings detected by Coverity.
448 lines
12 KiB
C
448 lines
12 KiB
C
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
|
|
*
|
|
* Copyright (C) 2017 Mario Limonciello <mario.limonciello@dell.com>
|
|
* Copyright (C) 2017 Peichen Huang <peichenhuang@tw.synaptics.com>
|
|
* Copyright (C) 2017 Richard Hughes <richard@hughsie.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-common.h"
|
|
#include "synapticsmst-device.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);
|
|
g_ptr_array_unref (priv->device_array);
|
|
if (priv->cmd_array != NULL)
|
|
g_ptr_array_unref (priv->cmd_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)
|
|
{
|
|
SynapticsMSTDevice *cascade_device = NULL;
|
|
guint8 aux_node = 0;
|
|
guint8 layer = 0;
|
|
guint16 rad = 0;
|
|
|
|
/* add all direct devices */
|
|
for (guint8 i = 0; i < MAX_DP_AUX_NODES; i++) {
|
|
g_autoptr(GError) error_local = NULL;
|
|
g_autoptr(SynapticsMSTDevice) device = NULL;
|
|
|
|
/* can we open the device? */
|
|
device = synapticsmst_device_new (SYNAPTICSMST_DEVICE_KIND_DIRECT, i, 0, 0);
|
|
if (!synapticsmst_device_open (device, &error_local)) {
|
|
if (g_error_matches (error_local,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_PERMISSION_DENIED)) {
|
|
g_set_error (error,
|
|
error_local->domain,
|
|
error_local->code,
|
|
"failed to open aux node: %s",
|
|
error_local->message);
|
|
return FALSE;
|
|
}
|
|
/* ignore */
|
|
continue;
|
|
}
|
|
|
|
/* add device to results */
|
|
g_ptr_array_add (priv->device_array, g_object_ref (device));
|
|
}
|
|
|
|
/* no devices */
|
|
if (priv->device_array->len == 0) {
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"No Synaptics MST Device Found");
|
|
return FALSE;
|
|
}
|
|
|
|
/* add all cascaded devices */
|
|
for (guint8 i = 0; i < priv->device_array->len; i++) {
|
|
SynapticsMSTDevice *device = g_ptr_array_index (priv->device_array, i);
|
|
aux_node = synapticsmst_device_get_aux_node (device);
|
|
if (!synapticsmst_device_open (device, error)) {
|
|
g_prefix_error (error,
|
|
"failed to open aux node %d again",
|
|
aux_node);
|
|
return FALSE;
|
|
}
|
|
if (!synapticsmst_device_enable_remote_control (device, error))
|
|
return FALSE;
|
|
for (guint8 j = 0; j < 2; j++) {
|
|
if (synapticsmst_device_scan_cascade_device (device, j)) {
|
|
layer = synapticsmst_device_get_layer (device) + 1;
|
|
rad = synapticsmst_device_get_rad (device) | (j << (2 * (layer - 1)));
|
|
cascade_device = synapticsmst_device_new (SYNAPTICSMST_DEVICE_KIND_REMOTE,
|
|
aux_node, layer, rad);
|
|
g_ptr_array_add (priv->device_array, cascade_device);
|
|
}
|
|
}
|
|
if (!synapticsmst_device_disable_remote_control (device, error))
|
|
return FALSE;
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
synapticsmst_tool_enumerate (SynapticsMSTToolPrivate *priv,
|
|
gchar **values,
|
|
guint8 device_index,
|
|
GError **error)
|
|
{
|
|
SynapticsMSTDevice *device = NULL;
|
|
|
|
/* check avaliable dp aux nodes and add devices */
|
|
if (!synapticsmst_tool_scan_aux_nodes (priv, error))
|
|
return FALSE;
|
|
|
|
g_print ("\nMST Devices:\n");
|
|
/* enumerate all devices one by one */
|
|
for (guint8 i = 0; i < priv->device_array->len; i++) {
|
|
const gchar *board_id = NULL;
|
|
device = g_ptr_array_index (priv->device_array, i);
|
|
g_print ("[Device %1d]\n", i+1);
|
|
if (!synapticsmst_device_enumerate_device (device, error))
|
|
return FALSE;
|
|
|
|
board_id = synapticsmst_device_board_id_to_string (synapticsmst_device_get_board_id (device));
|
|
if (board_id != NULL) {
|
|
g_print ("Device: %s with Synaptics %s\n",
|
|
board_id,
|
|
synapticsmst_device_get_chip_id (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 (device));
|
|
g_print ("Firmware version: %s\n", synapticsmst_device_get_version (device));
|
|
} else {
|
|
g_print ("Unknown Device\n");
|
|
}
|
|
g_print ("\n");
|
|
}
|
|
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;
|
|
g_autoptr(GError) error_local = 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;
|
|
}
|
|
|
|
/* check avaliable dp aux nodes and add devices */
|
|
if (!synapticsmst_tool_scan_aux_nodes (priv, error))
|
|
return FALSE;
|
|
|
|
device = g_ptr_array_index (priv->device_array, (device_index - 1));
|
|
if (!synapticsmst_device_enumerate_device (device, error))
|
|
return FALSE;
|
|
|
|
if (synapticsmst_device_board_id_to_string (synapticsmst_device_get_board_id (device)) == NULL) {
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"failed to flash firmware: unknown device");
|
|
return FALSE;
|
|
}
|
|
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)) {
|
|
g_prefix_error (error, "failed to flash firmware: ");
|
|
return FALSE;
|
|
}
|
|
g_print ("Update Sucessfully. Please reset device to apply new firmware\n");
|
|
|
|
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 (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);
|
|
|
|
/* list of devices */
|
|
priv->device_array = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
|
|
|
|
/* 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);
|
|
|
|
/* 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;
|
|
}
|