fwupd/plugins/synapticsmst/fu-plugin-synapticsmst.c
Richard Hughes 67a756c93f synapticsmst: Refactor away the global state
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.
2017-01-19 13:41:04 +00:00

312 lines
9.3 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 <smbios_c/smbios.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
synapticsmst_common_check_supported_system (GError **error)
{
gint i;
guint8 dell_supported = 0;
gboolean kernel_support = FALSE;
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,
"MST firmware updating not supported by OEM (%x)",
dell_supported);
return FALSE;
}
for (i = 0; i < MAX_DP_AUX_NODES; i++) {
if (kernel_support)
break;
kernel_support = g_file_test (synapticsmst_device_aux_node_to_string (i),
G_FILE_TEST_EXISTS);
}
if (!kernel_support) {
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"MST firmware updating not supported, missing kernel support.");
return FALSE;
}
return TRUE;
}
static gboolean
fu_synaptics_add_device (FuPlugin *plugin,
SynapticsMSTDevice *device,
guint8 aux_node,
guint8 layer,
guint8 rad,
GError **error) {
g_autoptr(FuDevice) dev = NULL;
const gchar *board_str = NULL;
const gchar *guid_str = NULL;
g_autofree gchar *name = NULL;
g_autofree gchar *dev_id_str = NULL;
if (!synapticsmst_device_enumerate_device (device, error)) {
g_debug ("error enumerating device at %u", aux_node);
return FALSE;
}
board_str = synapticsmst_device_board_id_to_string (synapticsmst_device_get_board_id (device));
name = g_strdup_printf ("%s with Synaptics [%s]", board_str,
synapticsmst_device_get_chip_id (device));
guid_str = synapticsmst_device_get_guid (device);
/* Store $KIND-$AUXNODE-$LAYER-$RAD as device ID */
dev_id_str = g_strdup_printf ("MST-%u-%u-%u-%u",
synapticsmst_device_get_kind(device),
aux_node, layer, rad);
if (board_str == NULL) {
g_debug ("invalid board ID (%x)", synapticsmst_device_get_board_id (device));
return FALSE;
}
/* create the device */
dev = fu_device_new ();
fu_device_set_id (dev, dev_id_str);
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));
fu_device_add_guid (dev, guid_str);
fu_plugin_device_add (plugin, dev);
fu_plugin_cache_add (plugin, dev_id_str, dev);
return TRUE;
}
static gboolean
fu_plugin_synapticsmst_enumerate (FuPlugin *plugin,
GError **error)
{
guint8 i;
guint8 j;
guint16 rad = 0;
guint8 layer = 0;
const gchar *aux_node_str = NULL;
g_autoptr(FuDevice) dev = NULL;
for (i = 0; i < MAX_DP_AUX_NODES; i++) {
g_autoptr(GError) error_local = NULL;
g_autoptr(SynapticsMSTDevice) device = NULL;
aux_node_str = synapticsmst_device_aux_node_to_string (i);
dev = fu_plugin_cache_lookup (plugin, aux_node_str);
/* If we open succesfully a device exists here */
device = synapticsmst_device_new (SYNAPTICSMST_DEVICE_KIND_DIRECT, i, 0, 0);
if (!synapticsmst_device_open (device, NULL)) {
/* No device exists here, but was there - remove from DB */
if (dev != NULL) {
g_debug ("removing device at %s", aux_node_str);
fu_plugin_device_remove (plugin, dev);
fu_plugin_cache_remove (plugin, aux_node_str);
} else {
/* Nothing to see here - move on*/
g_debug ("no device found on %s", aux_node_str);
}
continue;
}
/* node already exists */
if (dev != NULL)
continue;
/* Add direct devices */
g_debug ("adding direct device at %s", aux_node_str);
if (!fu_synaptics_add_device (plugin, device, i, 0, 0,
&error_local)) {
g_warning ("failed to add device: %s", error_local->message);
continue;
}
/* Check for cascaded devices */
if (!synapticsmst_device_open (device, &error_local)) {
g_warning ("failed to open device: %s",
error_local->message);
continue;
}
if (!synapticsmst_device_enable_remote_control (device, &error_local)) {
g_warning ("failed to enable remote control: %s",
error_local->message);
continue;
}
for (j = 0; j < 2; j++) {
if (synapticsmst_device_scan_cascade_device (device, j)) {
g_autoptr(SynapticsMSTDevice) device_cascade = NULL;
layer = synapticsmst_device_get_layer (device) + 1;
rad = synapticsmst_device_get_rad (device) | (j << (2 * (layer - 1)));
device_cascade = synapticsmst_device_new (SYNAPTICSMST_DEVICE_KIND_REMOTE,
i, layer, rad);
g_debug ("adding cascaded device at %s (%d,%d)", aux_node_str, layer, rad);
if (!fu_synaptics_add_device (plugin,
device_cascade,
i,
layer,
rad,
&error_local)) {
continue;
}
}
}
if (!synapticsmst_device_disable_remote_control (device, &error_local)) {
g_warning ("failed to disable remote control: %s",
error_local->message);
continue;
}
}
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)
{
g_autoptr(SynapticsMSTDevice) device = NULL;
const gchar *device_id;
SynapticsMSTDeviceKind kind;
guint8 aux_node;
guint8 layer;
guint8 rad;
g_auto (GStrv) split = NULL;
/* extract details to build a new device */
device_id = fu_device_get_id (dev);
split = g_strsplit (device_id, "-", -1);
kind = g_ascii_strtoull (split[1], NULL, 0);
aux_node = g_ascii_strtoull (split[2], NULL, 0);
layer = g_ascii_strtoull (split[3], NULL, 0);
rad = g_ascii_strtoull (split[4], NULL, 0);
/* sleep to allow device wakeup to complete */
g_debug ("waiting %d seconds for MST hub wakeup",
SYNAPTICS_FLASH_MODE_DELAY);
g_usleep (SYNAPTICS_FLASH_MODE_DELAY * 1000000);
device = synapticsmst_device_new (kind, aux_node, layer, rad);
if (!synapticsmst_device_enumerate_device (device, error))
return FALSE;
if (synapticsmst_device_board_id_to_string (synapticsmst_device_get_board_id (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)) {
g_prefix_error (error, "failed to flash firmware: ");
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);
if (!synapticsmst_device_enumerate_device (device, error)) {
return FALSE;
}
fu_device_set_version (dev, synapticsmst_device_get_version (device));
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)
{
/* verify that this is a supported system */
if (!synapticsmst_common_check_supported_system (error))
return FALSE;
/* look for host devices or already plugged in dock devices */
if (!fu_plugin_synapticsmst_enumerate (plugin, error))
g_debug ("error enumerating");
return TRUE;
}