mirror of
https://git.proxmox.com/git/fwupd
synced 2025-07-10 09:48:38 +00:00

Kernel v4.15 added support for native enumeration of Thunderbolt topology. The enumeration mode affects both the BIOS and TBT FW operation so they must agree on it. Platforms may support both modes, native and "legacy" (or "BIOS-assist"). This change makes sure the new image is compatible with the current controller mode (otherwise the BIOS and TBT FW will not be alligned on it at least until next boot) and also adds a new GUID generation logic for a controller in "native" mode so LVFS could contain 2 images, one for the "legacy" mode and one for "native". Signed-off-by: Yehezkel Bernat <yehezkel.bernat@intel.com>
868 lines
22 KiB
C
868 lines
22 KiB
C
/* -*- mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
|
|
*
|
|
* Copyright (C) 2017 Christian J. Kellner <christian@kellner.me>
|
|
*
|
|
* 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 <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include <gio/gio.h>
|
|
#include <glib.h>
|
|
#include <gudev/gudev.h>
|
|
|
|
#include "fu-plugin-thunderbolt.h"
|
|
#include "fu-plugin-vfuncs.h"
|
|
#include "fu-device-metadata.h"
|
|
#include "fu-thunderbolt-image.h"
|
|
|
|
#ifndef HAVE_GUDEV_232
|
|
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GUdevDevice, g_object_unref)
|
|
#endif
|
|
|
|
typedef void (*UEventNotify) (FuPlugin *plugin,
|
|
GUdevDevice *udevice,
|
|
const gchar *action,
|
|
gpointer user_data);
|
|
|
|
struct FuPluginData {
|
|
GUdevClient *udev;
|
|
|
|
/* in the case we are updating */
|
|
UEventNotify update_notify;
|
|
gpointer update_data;
|
|
|
|
/* the timeout we wait for the device
|
|
* to be updated to re-appear, in ms.
|
|
* defaults to:
|
|
* FU_PLUGIN_THUNDERBOLT_UPDATE_TIMEOUT_MS */
|
|
guint timeout;
|
|
};
|
|
|
|
static gchar *
|
|
fu_plugin_thunderbolt_gen_id_from_syspath (const gchar *syspath)
|
|
{
|
|
gchar *id;
|
|
id = g_strdup_printf ("tbt-%s", syspath);
|
|
g_strdelimit (id, "/:.-", '_');
|
|
return id;
|
|
}
|
|
|
|
|
|
static gchar *
|
|
fu_plugin_thunderbolt_gen_id (GUdevDevice *device)
|
|
{
|
|
const gchar *syspath = g_udev_device_get_sysfs_path (device);
|
|
return fu_plugin_thunderbolt_gen_id_from_syspath (syspath);
|
|
}
|
|
|
|
static guint64
|
|
udev_device_get_sysattr_guint64 (GUdevDevice *device,
|
|
const gchar *name,
|
|
GError **error)
|
|
{
|
|
const gchar *sysfs;
|
|
guint64 val;
|
|
|
|
sysfs = g_udev_device_get_sysfs_attr (device, name);
|
|
if (sysfs == NULL) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"failed get id %s for %s", name, sysfs);
|
|
return 0x0;
|
|
}
|
|
|
|
val = g_ascii_strtoull (sysfs, NULL, 16);
|
|
if (val == 0x0) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"failed to parse %s", sysfs);
|
|
return 0x0;
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
static guint16
|
|
fu_plugin_thunderbolt_udev_get_id (GUdevDevice *device,
|
|
const gchar *name,
|
|
GError **error)
|
|
{
|
|
|
|
guint64 id;
|
|
|
|
id = udev_device_get_sysattr_guint64 (device, name, error);
|
|
if (id == 0x0)
|
|
return id;
|
|
|
|
if (id > G_MAXUINT16) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"vendor id overflows");
|
|
return 0x0;
|
|
}
|
|
|
|
return (guint16) id;
|
|
}
|
|
|
|
static gboolean
|
|
fu_plugin_thunderbolt_is_host (GUdevDevice *device)
|
|
{
|
|
g_autoptr(GUdevDevice) parent = NULL;
|
|
const gchar *name;
|
|
|
|
/* the (probably safe) assumption this code makes is
|
|
* that the thunderbolt device which is a direct child
|
|
* of the domain is the host controller device itself */
|
|
parent = g_udev_device_get_parent (device);
|
|
name = g_udev_device_get_name (parent);
|
|
if (name == NULL)
|
|
return FALSE;
|
|
|
|
return g_str_has_prefix (name, "domain");
|
|
}
|
|
|
|
static GFile *
|
|
fu_plugin_thunderbolt_find_nvmem (GUdevDevice *udevice,
|
|
gboolean active,
|
|
GError **error)
|
|
{
|
|
const gchar *nvmem_dir = active ? "nvm_active" : "nvm_non_active";
|
|
const gchar *devpath;
|
|
const gchar *name;
|
|
g_autoptr(GDir) d = NULL;
|
|
|
|
devpath = g_udev_device_get_sysfs_path (udevice);
|
|
if (G_UNLIKELY (devpath == NULL)) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR, FWUPD_ERROR_INTERNAL,
|
|
"Could not determine sysfs path for device");
|
|
return NULL;
|
|
}
|
|
|
|
d = g_dir_open (devpath, 0, error);
|
|
if (d == NULL)
|
|
return NULL;
|
|
|
|
while ((name = g_dir_read_name (d)) != NULL) {
|
|
if (g_str_has_prefix (name, nvmem_dir)) {
|
|
g_autoptr(GFile) parent = g_file_new_for_path (devpath);
|
|
g_autoptr(GFile) nvm_dir = g_file_get_child (parent, name);
|
|
return g_file_get_child (nvm_dir, "nvmem");
|
|
}
|
|
}
|
|
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED,
|
|
"Could not find non-volatile memory location");
|
|
return NULL;
|
|
}
|
|
|
|
static gboolean
|
|
fu_plugin_thunderbolt_is_native (GUdevDevice *udevice, gboolean *is_native, GError **error)
|
|
{
|
|
g_autoptr(GFile) nvmem = NULL;
|
|
g_autoptr(GBytes) controller_fw = NULL;
|
|
gchar *content;
|
|
gsize length;
|
|
|
|
nvmem = fu_plugin_thunderbolt_find_nvmem (udevice, TRUE, error);
|
|
if (nvmem == NULL)
|
|
return FALSE;
|
|
|
|
if (!g_file_load_contents (nvmem, NULL, &content, &length, NULL, error))
|
|
return FALSE;
|
|
|
|
controller_fw = g_bytes_new_take (content, length);
|
|
|
|
return fu_plugin_thunderbolt_controller_is_native (controller_fw,
|
|
is_native,
|
|
error);
|
|
}
|
|
|
|
static void
|
|
fu_plugin_thunderbolt_add (FuPlugin *plugin, GUdevDevice *device)
|
|
{
|
|
FuDevice *dev_tmp;
|
|
const gchar *name;
|
|
const gchar *uuid;
|
|
const gchar *vendor;
|
|
const gchar *version;
|
|
const gchar *devpath;
|
|
const gchar *devtype;
|
|
gboolean is_host;
|
|
gboolean is_safemode = FALSE;
|
|
gboolean is_native = FALSE;
|
|
guint16 did;
|
|
guint16 vid;
|
|
g_autofree gchar *id = NULL;
|
|
g_autofree gchar *vendor_id = NULL;
|
|
g_autofree gchar *device_id = NULL;
|
|
g_autoptr(FuDevice) dev = NULL;
|
|
g_autoptr(GError) error = NULL;
|
|
|
|
uuid = g_udev_device_get_sysfs_attr (device, "unique_id");
|
|
if (uuid == NULL) {
|
|
/* most likely the domain itself, ignore */
|
|
return;
|
|
}
|
|
|
|
devpath = g_udev_device_get_sysfs_path (device);
|
|
|
|
devtype = g_udev_device_get_devtype (device);
|
|
if (g_strcmp0 (devtype, "thunderbolt_device") != 0) {
|
|
g_debug ("ignoring %s device at %s", devtype, devpath);
|
|
return;
|
|
}
|
|
|
|
g_debug ("adding udev device: %s at %s", uuid, devpath);
|
|
|
|
id = fu_plugin_thunderbolt_gen_id (device);
|
|
dev_tmp = fu_plugin_cache_lookup (plugin, id);
|
|
if (dev_tmp != NULL) {
|
|
/* devices that are force-powered are re-added */
|
|
g_debug ("ignoring duplicate %s", id);
|
|
return;
|
|
}
|
|
|
|
vid = fu_plugin_thunderbolt_udev_get_id (device, "vendor", &error);
|
|
if (vid == 0x0)
|
|
g_warning ("failed to get Vendor ID: %s", error->message);
|
|
|
|
did = fu_plugin_thunderbolt_udev_get_id (device, "device", &error);
|
|
if (did == 0x0)
|
|
g_warning ("failed to get Device ID: %s", error->message);
|
|
|
|
dev = fu_device_new ();
|
|
|
|
/* test for safe mode */
|
|
is_host = fu_plugin_thunderbolt_is_host (device);
|
|
version = g_udev_device_get_sysfs_attr (device, "nvm_version");
|
|
if (is_host && version == NULL) {
|
|
g_autofree gchar *test_safe = NULL;
|
|
g_autofree gchar *safe_path = NULL;
|
|
/* glib can't return a properly mapped -ENODATA but the
|
|
* kernel only returns -ENODATA or -EAGAIN */
|
|
safe_path = g_build_path ("/", devpath, "nvm_version", NULL);
|
|
if (!g_file_get_contents (safe_path, &test_safe, NULL, &error) &&
|
|
!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) {
|
|
g_warning ("%s is in safe mode -- VID/DID will "
|
|
"need to be set by another plugin",
|
|
devpath);
|
|
version = "0.0";
|
|
is_safemode = TRUE;
|
|
device_id = g_strdup ("TBT-safemode");
|
|
fu_device_set_metadata_boolean (dev, FU_DEVICE_METADATA_TBT_IS_SAFE_MODE, TRUE);
|
|
}
|
|
}
|
|
if (!is_safemode) {
|
|
if (is_host) {
|
|
if (!fu_plugin_thunderbolt_is_native (device, &is_native, &error)) {
|
|
g_warning ("failed to get native mode status: %s", error->message);
|
|
return;
|
|
}
|
|
}
|
|
vendor_id = g_strdup_printf ("TBT:0x%04X", (guint) vid);
|
|
device_id = g_strdup_printf ("TBT-%04x%04x%s",
|
|
(guint) vid,
|
|
(guint) did,
|
|
is_native ? "-native" : "");
|
|
fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_UPDATABLE);
|
|
}
|
|
|
|
fu_device_set_platform_id (dev, uuid);
|
|
|
|
fu_device_set_metadata (dev, "sysfs-path", devpath);
|
|
name = g_udev_device_get_sysfs_attr (device, "device_name");
|
|
if (name != NULL) {
|
|
if (is_host) {
|
|
g_autofree gchar *pretty_name = NULL;
|
|
pretty_name = g_strdup_printf ("%s Thunderbolt Controller", name);
|
|
fu_device_set_name (dev, pretty_name);
|
|
} else {
|
|
fu_device_set_name (dev, name);
|
|
}
|
|
}
|
|
if (is_host) {
|
|
fu_device_set_summary (dev, "Unmatched performance for high-speed I/O");
|
|
fu_device_add_icon (dev, "computer");
|
|
} else {
|
|
fu_device_add_icon (dev, "audio-card");
|
|
}
|
|
|
|
vendor = g_udev_device_get_sysfs_attr (device, "vendor_name");
|
|
if (vendor != NULL)
|
|
fu_device_set_vendor (dev, vendor);
|
|
if (vendor_id != NULL)
|
|
fu_device_set_vendor_id (dev, vendor_id);
|
|
if (device_id != NULL)
|
|
fu_device_add_guid (dev, device_id);
|
|
if (version != NULL)
|
|
fu_device_set_version (dev, version);
|
|
if (is_host)
|
|
fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_INTERNAL);
|
|
|
|
fu_plugin_cache_add (plugin, id, dev);
|
|
fu_plugin_device_add (plugin, dev);
|
|
}
|
|
|
|
static void
|
|
fu_plugin_thunderbolt_remove (FuPlugin *plugin, GUdevDevice *device)
|
|
{
|
|
FuDevice *dev;
|
|
g_autofree gchar *id = NULL;
|
|
|
|
id = fu_plugin_thunderbolt_gen_id (device);
|
|
dev = fu_plugin_cache_lookup (plugin, id);
|
|
if (dev == NULL)
|
|
return;
|
|
|
|
/* on supported systems other plugins may use a GPIO to force
|
|
* power on supported devices even when in low power mode --
|
|
* this will happen in coldplug_prepare and prepare_for_update */
|
|
if (fu_plugin_thunderbolt_is_host (device) &&
|
|
fu_device_get_metadata_boolean (dev, FU_DEVICE_METADATA_TBT_CAN_FORCE_POWER)) {
|
|
g_debug ("ignoring remove event as force powered");
|
|
return;
|
|
}
|
|
|
|
fu_plugin_device_remove (plugin, dev);
|
|
fu_plugin_cache_remove (plugin, id);
|
|
}
|
|
|
|
static void
|
|
fu_plugin_thunderbolt_change (FuPlugin *plugin, GUdevDevice *device)
|
|
{
|
|
FuDevice *dev;
|
|
const gchar *version;
|
|
g_autofree gchar *id = NULL;
|
|
|
|
id = fu_plugin_thunderbolt_gen_id (device);
|
|
dev = fu_plugin_cache_lookup (plugin, id);
|
|
if (dev == NULL) {
|
|
g_warning ("got change event for unknown device, adding instead");
|
|
fu_plugin_thunderbolt_add (plugin, device);
|
|
return;
|
|
}
|
|
|
|
version = g_udev_device_get_sysfs_attr (device, "nvm_version");
|
|
fu_device_set_version (dev, version);
|
|
}
|
|
|
|
static gboolean
|
|
udev_uevent_cb (GUdevClient *udev,
|
|
const gchar *action,
|
|
GUdevDevice *device,
|
|
gpointer user_data)
|
|
{
|
|
FuPlugin *plugin = (FuPlugin *) user_data;
|
|
FuPluginData *data = fu_plugin_get_data (plugin);
|
|
|
|
if (action == NULL)
|
|
return TRUE;
|
|
|
|
g_debug ("uevent for %s: %s", g_udev_device_get_sysfs_path (device), action);
|
|
|
|
if (data->update_notify != NULL) {
|
|
g_debug ("using update notify handler for uevent");
|
|
|
|
data->update_notify (plugin, device, action, data->update_data);
|
|
return TRUE;
|
|
}
|
|
|
|
if (g_str_equal (action, "add")) {
|
|
fu_plugin_thunderbolt_add (plugin, device);
|
|
} else if (g_str_equal (action, "remove")) {
|
|
fu_plugin_thunderbolt_remove (plugin, device);
|
|
} else if (g_str_equal (action, "change")) {
|
|
fu_plugin_thunderbolt_change (plugin, device);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static FuPluginValidation
|
|
fu_plugin_thunderbolt_validate_firmware (GUdevDevice *udevice,
|
|
GBytes *blob_fw,
|
|
GError **error)
|
|
{
|
|
g_autoptr(GFile) nvmem = NULL;
|
|
g_autoptr(GBytes) controller_fw = NULL;
|
|
gchar *content;
|
|
gsize length;
|
|
|
|
nvmem = fu_plugin_thunderbolt_find_nvmem (udevice, TRUE, error);
|
|
if (nvmem == NULL)
|
|
return VALIDATION_FAILED;
|
|
|
|
if (!g_file_load_contents (nvmem, NULL, &content, &length, NULL, error))
|
|
return VALIDATION_FAILED;
|
|
|
|
controller_fw = g_bytes_new_take (content, length);
|
|
|
|
return fu_plugin_thunderbolt_validate_image (controller_fw,
|
|
blob_fw,
|
|
error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_plugin_thunderbolt_trigger_update (GUdevDevice *udevice,
|
|
GError **error)
|
|
{
|
|
|
|
const gchar *devpath;
|
|
ssize_t n;
|
|
int fd;
|
|
int r;
|
|
g_autofree gchar *auth_path = NULL;
|
|
|
|
devpath = g_udev_device_get_sysfs_path (udevice);
|
|
auth_path = g_build_filename (devpath, "nvm_authenticate", NULL);
|
|
|
|
fd = open (auth_path, O_WRONLY | O_CLOEXEC);
|
|
if (fd < 0) {
|
|
g_set_error (error, G_IO_ERROR,
|
|
g_io_error_from_errno (errno),
|
|
"could not open 'nvm_authenticate': %s",
|
|
g_strerror (errno));
|
|
return FALSE;
|
|
}
|
|
|
|
do {
|
|
n = write (fd, "1", 1);
|
|
if (n < 1 && errno != EINTR) {
|
|
g_set_error (error, G_IO_ERROR,
|
|
g_io_error_from_errno (errno),
|
|
"could write to 'nvm_authenticate': %s",
|
|
g_strerror (errno));
|
|
(void) close (fd);
|
|
return FALSE;
|
|
}
|
|
} while (n < 1);
|
|
|
|
r = close (fd);
|
|
if (r < 0 && errno != EINTR) {
|
|
g_set_error (error, G_IO_ERROR,
|
|
g_io_error_from_errno (errno),
|
|
"could close 'nvm_authenticate': %s",
|
|
g_strerror (errno));
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_plugin_thunderbolt_write_firmware (FuDevice *device,
|
|
GUdevDevice *udevice,
|
|
GBytes *blob_fw,
|
|
GError **error)
|
|
{
|
|
gsize fw_size;
|
|
gsize nwritten;
|
|
gssize n;
|
|
g_autoptr(GFile) nvmem = NULL;
|
|
g_autoptr(GOutputStream) os = NULL;
|
|
|
|
nvmem = fu_plugin_thunderbolt_find_nvmem (udevice, FALSE, error);
|
|
if (nvmem == NULL)
|
|
return FALSE;
|
|
|
|
os = (GOutputStream *) g_file_append_to (nvmem,
|
|
G_FILE_CREATE_NONE,
|
|
NULL,
|
|
error);
|
|
|
|
if (os == NULL)
|
|
return FALSE;
|
|
|
|
nwritten = 0;
|
|
fw_size = g_bytes_get_size (blob_fw);
|
|
fu_device_set_progress_full (device, nwritten, fw_size);
|
|
|
|
do {
|
|
g_autoptr(GBytes) fw_data = NULL;
|
|
|
|
fw_data = g_bytes_new_from_bytes (blob_fw,
|
|
nwritten,
|
|
fw_size - nwritten);
|
|
|
|
n = g_output_stream_write_bytes (os,
|
|
fw_data,
|
|
NULL,
|
|
error);
|
|
if (n < 0)
|
|
return FALSE;
|
|
|
|
nwritten += n;
|
|
fu_device_set_progress_full (device, nwritten, fw_size);
|
|
|
|
} while (nwritten < fw_size);
|
|
|
|
if (nwritten != fw_size) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_WRITE,
|
|
"Could not write all data to nvmem");
|
|
return FALSE;
|
|
}
|
|
|
|
return g_output_stream_close (os, NULL, error);
|
|
}
|
|
|
|
typedef struct UpdateData {
|
|
|
|
gboolean have_device;
|
|
GMainLoop *mainloop;
|
|
const gchar *target_uuid;
|
|
guint timeout_id;
|
|
|
|
GHashTable *changes;
|
|
} UpdateData;
|
|
|
|
static gboolean
|
|
on_wait_for_device_timeout (gpointer user_data)
|
|
{
|
|
UpdateData *data = (UpdateData *) user_data;
|
|
g_main_loop_quit (data->mainloop);
|
|
data->timeout_id = 0;
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
on_wait_for_device_added (FuPlugin *plugin,
|
|
GUdevDevice *device,
|
|
UpdateData *up_data)
|
|
{
|
|
FuDevice *dev;
|
|
const gchar *uuid;
|
|
const gchar *path;
|
|
const gchar *version;
|
|
g_autofree gchar *id = NULL;
|
|
|
|
uuid = g_udev_device_get_sysfs_attr (device, "unique_id");
|
|
if (uuid == NULL)
|
|
return;
|
|
|
|
dev = g_hash_table_lookup (up_data->changes, uuid);
|
|
if (dev == NULL) {
|
|
/* a previously unknown device, add it via
|
|
* the normal way */
|
|
fu_plugin_thunderbolt_add (plugin, device);
|
|
return;
|
|
}
|
|
|
|
/* ensure the device path is correct */
|
|
path = g_udev_device_get_sysfs_path (device);
|
|
fu_device_set_metadata (dev, "sysfs-path", path);
|
|
|
|
/* make sure the version is correct, might have changed
|
|
* after update. */
|
|
version = g_udev_device_get_sysfs_attr (device, "nvm_version");
|
|
fu_device_set_version (dev, version);
|
|
|
|
id = fu_plugin_thunderbolt_gen_id (device);
|
|
fu_plugin_cache_add (plugin, id, dev);
|
|
|
|
g_hash_table_remove (up_data->changes, uuid);
|
|
|
|
/* check if this device is the target*/
|
|
if (g_str_equal (uuid, up_data->target_uuid)) {
|
|
up_data->have_device = TRUE;
|
|
g_debug ("target (%s) re-appeared", uuid);
|
|
g_main_loop_quit (up_data->mainloop);
|
|
}
|
|
}
|
|
|
|
static void
|
|
on_wait_for_device_removed (FuPlugin *plugin,
|
|
GUdevDevice *device,
|
|
UpdateData *up_data)
|
|
{
|
|
g_autofree gchar *id = NULL;
|
|
FuDevice *dev;
|
|
const gchar *uuid;
|
|
|
|
id = fu_plugin_thunderbolt_gen_id (device);
|
|
dev = fu_plugin_cache_lookup (plugin, id);
|
|
|
|
if (dev == NULL)
|
|
return;
|
|
|
|
fu_plugin_cache_remove (plugin, id);
|
|
uuid = fu_device_get_platform_id (dev);
|
|
g_hash_table_insert (up_data->changes,
|
|
(gpointer) uuid,
|
|
g_object_ref (dev));
|
|
|
|
/* check if this device is the target */
|
|
if (g_str_equal (uuid, up_data->target_uuid)) {
|
|
up_data->have_device = FALSE;
|
|
g_debug ("target (%s) disappeared", uuid);
|
|
}
|
|
}
|
|
|
|
static void
|
|
on_wait_for_device_notify (FuPlugin *plugin,
|
|
GUdevDevice *device,
|
|
const char *action,
|
|
gpointer user_data)
|
|
{
|
|
UpdateData *up_data = (UpdateData *) user_data;
|
|
|
|
/* nb: action cannot be NULL since we are only called from
|
|
* udev_event_cb, which ensures that */
|
|
if (g_str_equal (action, "add")) {
|
|
on_wait_for_device_added (plugin, device, up_data);
|
|
} else if (g_str_equal (action, "remove")) {
|
|
on_wait_for_device_removed (plugin, device, up_data);
|
|
} else if (g_str_equal (action, "change")) {
|
|
fu_plugin_thunderbolt_change (plugin, device);
|
|
}
|
|
}
|
|
|
|
static void
|
|
remove_leftover_devices (gpointer key,
|
|
gpointer value,
|
|
gpointer user_data)
|
|
{
|
|
FuPlugin *plugin = FU_PLUGIN (user_data);
|
|
FuDevice *dev = FU_DEVICE (value);
|
|
const gchar *syspath = fu_device_get_metadata (dev, "sysfs-path");
|
|
g_autofree gchar *id = NULL;
|
|
|
|
id = fu_plugin_thunderbolt_gen_id_from_syspath (syspath);
|
|
|
|
fu_plugin_cache_remove (plugin, id);
|
|
fu_plugin_device_remove (plugin, dev);
|
|
}
|
|
|
|
static gboolean
|
|
fu_plugin_thunderbolt_wait_for_device (FuPlugin *plugin,
|
|
FuDevice *dev,
|
|
GError **error)
|
|
{
|
|
FuPluginData *data = fu_plugin_get_data (plugin);
|
|
UpdateData up_data = { TRUE, };
|
|
g_autoptr(GMainLoop) mainloop = NULL;
|
|
g_autoptr(GHashTable) changes = NULL;
|
|
|
|
up_data.mainloop = mainloop = g_main_loop_new (NULL, FALSE);
|
|
up_data.target_uuid = fu_device_get_platform_id (dev);
|
|
|
|
/* this will limit the maximum amount of time we wait for
|
|
* the device (i.e. 'dev') to re-appear. */
|
|
up_data.timeout_id = g_timeout_add (data->timeout,
|
|
on_wait_for_device_timeout,
|
|
&up_data);
|
|
|
|
/* this will capture the device added, removed, changed
|
|
* signals while we are updating. */
|
|
data->update_data = &up_data;
|
|
data->update_notify = on_wait_for_device_notify;
|
|
|
|
changes = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_object_unref);
|
|
up_data.changes = changes;
|
|
|
|
/* now we wait ... */
|
|
g_main_loop_run (mainloop);
|
|
|
|
/* restore original udev change handler */
|
|
data->update_data = NULL;
|
|
data->update_notify = NULL;
|
|
|
|
if (up_data.timeout_id > 0)
|
|
g_source_remove (up_data.timeout_id);
|
|
|
|
if (!up_data.have_device) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"timed out while waiting for device");
|
|
return FALSE;
|
|
}
|
|
|
|
g_hash_table_foreach (changes, remove_leftover_devices, plugin);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* internal interface */
|
|
|
|
void
|
|
fu_plugin_thunderbolt_set_timeout (FuPlugin *plugin, guint timeout_ms)
|
|
{
|
|
FuPluginData *data = fu_plugin_get_data (plugin);
|
|
data->timeout = timeout_ms;
|
|
}
|
|
|
|
/* virtual functions */
|
|
|
|
void
|
|
fu_plugin_init (FuPlugin *plugin)
|
|
{
|
|
FuPluginData *data = fu_plugin_alloc_data (plugin, sizeof (FuPluginData));
|
|
const gchar *subsystems[] = { "thunderbolt", NULL };
|
|
|
|
data->udev = g_udev_client_new (subsystems);
|
|
g_signal_connect (data->udev, "uevent",
|
|
G_CALLBACK (udev_uevent_cb), plugin);
|
|
|
|
data->timeout = FU_PLUGIN_THUNDERBOLT_UPDATE_TIMEOUT_MS;
|
|
}
|
|
|
|
void
|
|
fu_plugin_destroy (FuPlugin *plugin)
|
|
{
|
|
FuPluginData *data = fu_plugin_get_data (plugin);
|
|
g_object_unref (data->udev);
|
|
}
|
|
|
|
gboolean
|
|
fu_plugin_coldplug (FuPlugin *plugin, GError **error)
|
|
{
|
|
FuPluginData *data = fu_plugin_get_data (plugin);
|
|
GList *devices;
|
|
|
|
devices = g_udev_client_query_by_subsystem (data->udev, "thunderbolt");
|
|
for (GList *l = devices; l != NULL; l = l->next) {
|
|
GUdevDevice *device = l->data;
|
|
fu_plugin_thunderbolt_add (plugin, device);
|
|
}
|
|
|
|
g_list_foreach (devices, (GFunc) g_object_unref, NULL);
|
|
g_list_free (devices);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
gboolean
|
|
fu_plugin_update (FuPlugin *plugin,
|
|
FuDevice *dev,
|
|
GBytes *blob_fw,
|
|
FwupdInstallFlags flags,
|
|
GError **error)
|
|
{
|
|
FuPluginData *data = fu_plugin_get_data (plugin);
|
|
const gchar *devpath;
|
|
guint64 status;
|
|
g_autoptr(GUdevDevice) udevice = NULL;
|
|
g_autoptr(GError) error_local = NULL;
|
|
gboolean force = (flags & FWUPD_INSTALL_FLAG_FORCE) != 0;
|
|
FuPluginValidation validation;
|
|
|
|
devpath = fu_device_get_metadata (dev, "sysfs-path");
|
|
g_return_val_if_fail (devpath, FALSE);
|
|
|
|
udevice = g_udev_client_query_by_sysfs_path (data->udev, devpath);
|
|
if (udevice == NULL) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_FOUND,
|
|
"could not find thunderbolt device at %s",
|
|
devpath);
|
|
return FALSE;
|
|
}
|
|
|
|
validation = fu_plugin_thunderbolt_validate_firmware (udevice,
|
|
blob_fw,
|
|
&error_local);
|
|
if (validation != VALIDATION_PASSED) {
|
|
g_autofree gchar* msg = NULL;
|
|
switch (validation) {
|
|
case VALIDATION_FAILED:
|
|
msg = g_strdup_printf ("could not validate firmware: %s",
|
|
error_local->message);
|
|
break;
|
|
case UNKNOWN_DEVICE:
|
|
msg = g_strdup ("firmware validation seems to be passed but the device is unknown");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (!force) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_FILE,
|
|
"%s. "
|
|
"See https://github.com/hughsie/fwupd/wiki/Thunderbolt:-Validation-failed-or-unknown-device for more information.",
|
|
msg);
|
|
return FALSE;
|
|
}
|
|
g_warning ("%s", msg);
|
|
}
|
|
|
|
fu_device_set_status (dev, FWUPD_STATUS_DEVICE_WRITE);
|
|
if (!fu_plugin_thunderbolt_write_firmware (dev, udevice, blob_fw, &error_local)) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_WRITE,
|
|
"could not write firmware to thunderbolt device at %s: %s",
|
|
devpath, error_local->message);
|
|
return FALSE;
|
|
}
|
|
|
|
if (!fu_plugin_thunderbolt_trigger_update (udevice, &error_local)) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"Could not start thunderbolt device upgrade: %s",
|
|
error_local->message);
|
|
return FALSE;
|
|
}
|
|
|
|
fu_device_set_status (dev, FWUPD_STATUS_DEVICE_RESTART);
|
|
|
|
/* the device will disappear and we need to wait until it reappears,
|
|
* and then check if we find an error */
|
|
if (!fu_plugin_thunderbolt_wait_for_device (plugin, dev, &error_local)) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_FOUND,
|
|
"could not detect device after update: %s",
|
|
error_local->message);
|
|
return FALSE;
|
|
}
|
|
|
|
/* now check if the update actually worked */
|
|
status = udev_device_get_sysattr_guint64 (udevice,
|
|
"nvm_authenticate",
|
|
&error_local);
|
|
|
|
/* anything else then 0x0 means we got an error */
|
|
if (status != 0x0) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"update failed (status %" G_GINT64_MODIFIER "x)", status);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|