mirror of
https://git.proxmox.com/git/fwupd
synced 2025-08-05 19:48:47 +00:00

We load the Thunderbolt controller firmware to see if the controller is in native mode, as this changes the GUID. If the controller is asleep the firmware is not cached by the kernel and it can take more than 4 seconds to read out 504kB of firmware. We only need the first two 64-byte chunks, so only read what is required. This speeds up fwupd starting substantially, and also means we don't have to allocate a giant chunk of heap memory just to inspect one byte. Fixes: https://github.com/hughsie/fwupd/issues/848
732 lines
19 KiB
C
732 lines
19 KiB
C
/*
|
|
* Copyright (C) 2017 Christian J. Kellner <christian@kellner.me>
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1+
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
|
|
#include "fu-plugin-vfuncs.h"
|
|
#include "fu-device-metadata.h"
|
|
#include "fu-thunderbolt-image.h"
|
|
|
|
#ifndef HAVE_GUDEV_232
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wunused-function"
|
|
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GUdevDevice, g_object_unref)
|
|
#pragma clang diagnostic pop
|
|
#endif
|
|
|
|
#define TBT_NVM_RETRY_TIMEOUT 200 /* ms */
|
|
#define FU_PLUGIN_THUNDERBOLT_UPDATE_TIMEOUT 60000 /* ms */
|
|
|
|
typedef void (*UEventNotify) (FuPlugin *plugin,
|
|
GUdevDevice *udevice,
|
|
const gchar *action,
|
|
gpointer user_data);
|
|
|
|
struct FuPluginData {
|
|
GUdevClient *udev;
|
|
};
|
|
|
|
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 gboolean
|
|
udev_device_get_sysattr_guint64 (GUdevDevice *device,
|
|
const gchar *name,
|
|
guint64 *val_out,
|
|
GError **error)
|
|
{
|
|
const gchar *sysfs;
|
|
|
|
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 FALSE;
|
|
}
|
|
|
|
*val_out = g_ascii_strtoull (sysfs, NULL, 16);
|
|
if (*val_out == 0x0) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"failed to parse %s", sysfs);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static guint16
|
|
fu_plugin_thunderbolt_udev_get_id (GUdevDevice *device,
|
|
const gchar *name,
|
|
GError **error)
|
|
{
|
|
|
|
guint64 id = 0;
|
|
|
|
if (!udev_device_get_sysattr_guint64 (device, name, &id, error))
|
|
return 0x0;
|
|
|
|
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 gchar *
|
|
fu_plugin_thunderbolt_parse_version (const gchar *version_raw)
|
|
{
|
|
g_auto(GStrv) split = NULL;
|
|
if (version_raw == NULL)
|
|
return NULL;
|
|
split = g_strsplit (version_raw, ".", -1);
|
|
if (g_strv_length (split) != 2)
|
|
return NULL;
|
|
return g_strdup_printf ("%02x.%02x",
|
|
(guint) g_ascii_strtoull (split[0], NULL, 16),
|
|
(guint) g_ascii_strtoull (split[1], NULL, 16));
|
|
}
|
|
|
|
static gchar *
|
|
fu_plugin_thunderbolt_udev_get_version (GUdevDevice *udevice)
|
|
{
|
|
const gchar *version = NULL;
|
|
|
|
for (guint i = 0; i < 50; i++) {
|
|
version = g_udev_device_get_sysfs_attr (udevice, "nvm_version");
|
|
if (version != NULL)
|
|
break;
|
|
g_debug ("Attempt %u: Failed to read NVM version", i);
|
|
if (errno != EAGAIN)
|
|
break;
|
|
g_usleep (TBT_NVM_RETRY_TIMEOUT * 1000);
|
|
}
|
|
|
|
return fu_plugin_thunderbolt_parse_version (version);
|
|
}
|
|
|
|
static gboolean
|
|
fu_plugin_thunderbolt_is_native (GUdevDevice *udevice, gboolean *is_native, GError **error)
|
|
{
|
|
gsize nr_chunks;
|
|
g_autoptr(GFile) nvmem = NULL;
|
|
g_autoptr(GBytes) controller_fw = NULL;
|
|
g_autoptr(GInputStream) istr = NULL;
|
|
|
|
nvmem = fu_plugin_thunderbolt_find_nvmem (udevice, TRUE, error);
|
|
if (nvmem == NULL)
|
|
return FALSE;
|
|
|
|
/* read just enough bytes to read the status byte */
|
|
nr_chunks = (FU_TBT_OFFSET_NATIVE + FU_TBT_CHUNK_SZ - 1) / FU_TBT_CHUNK_SZ;
|
|
istr = G_INPUT_STREAM (g_file_read (nvmem, NULL, error));
|
|
if (istr == NULL)
|
|
return FALSE;
|
|
controller_fw = g_input_stream_read_bytes (istr,
|
|
nr_chunks * FU_TBT_CHUNK_SZ,
|
|
NULL, error);
|
|
if (controller_fw == NULL)
|
|
return FALSE;
|
|
|
|
return fu_thunderbolt_image_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 *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 *version = NULL;
|
|
g_autofree gchar *vendor_id = NULL;
|
|
g_autofree gchar *device_id = NULL;
|
|
g_autoptr(FuDevice) dev = NULL;
|
|
g_autoptr(GError) error_vid = NULL;
|
|
g_autoptr(GError) error_did = 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_vid);
|
|
if (vid == 0x0)
|
|
g_warning ("failed to get Vendor ID: %s", error_vid->message);
|
|
|
|
did = fu_plugin_thunderbolt_udev_get_id (device, "device", &error_did);
|
|
if (did == 0x0)
|
|
g_warning ("failed to get Device ID: %s", error_did->message);
|
|
|
|
dev = fu_device_new ();
|
|
|
|
is_host = fu_plugin_thunderbolt_is_host (device);
|
|
|
|
version = fu_plugin_thunderbolt_udev_get_version (device);
|
|
/* test for safe mode */
|
|
if (is_host && version == NULL) {
|
|
g_autoptr(GError) error_local = 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_local) &&
|
|
!g_error_matches (error_local, 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 = g_strdup ("00.00");
|
|
is_safemode = TRUE;
|
|
device_id = g_strdup ("TBT-safemode");
|
|
fu_device_set_metadata_boolean (dev, FU_DEVICE_METADATA_TBT_IS_SAFE_MODE, TRUE);
|
|
}
|
|
fu_plugin_add_report_metadata (plugin, "ThunderboltSafeMode",
|
|
is_safemode ? "True" : "False");
|
|
}
|
|
if (!is_safemode) {
|
|
if (is_host) {
|
|
g_autoptr(GError) error_local = NULL;
|
|
if (!fu_plugin_thunderbolt_is_native (device, &is_native, &error_local)) {
|
|
g_warning ("failed to get native mode status: %s", error_local->message);
|
|
return;
|
|
}
|
|
fu_plugin_add_report_metadata (plugin,
|
|
"ThunderboltNative",
|
|
is_native ? "True" : "False");
|
|
}
|
|
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);
|
|
} else {
|
|
fu_device_set_update_error (dev, "Device is in safe mode");
|
|
}
|
|
|
|
fu_device_set_physical_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");
|
|
}
|
|
|
|
fu_device_set_quirks (dev, fu_plugin_get_quirks (plugin));
|
|
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_has_flag (dev, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG) &&
|
|
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;
|
|
g_autofree gchar *version = NULL;
|
|
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 = fu_plugin_thunderbolt_udev_get_version (device);
|
|
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;
|
|
|
|
if (action == NULL)
|
|
return TRUE;
|
|
|
|
g_debug ("uevent for %s: %s", g_udev_device_get_sysfs_path (device), action);
|
|
|
|
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_thunderbolt_image_validate (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);
|
|
}
|
|
|
|
/* 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);
|
|
}
|
|
|
|
void
|
|
fu_plugin_destroy (FuPlugin *plugin)
|
|
{
|
|
FuPluginData *data = fu_plugin_get_data (plugin);
|
|
g_object_unref (data->udev);
|
|
}
|
|
|
|
static gboolean
|
|
fu_plugin_thunderbolt_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_coldplug (FuPlugin *plugin, GError **error)
|
|
{
|
|
return fu_plugin_thunderbolt_coldplug (plugin, error);
|
|
}
|
|
|
|
gboolean
|
|
fu_plugin_recoldplug (FuPlugin *plugin, GError **error)
|
|
{
|
|
return fu_plugin_thunderbolt_coldplug (plugin, error);
|
|
}
|
|
|
|
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;
|
|
g_autoptr(GUdevDevice) udevice = NULL;
|
|
g_autoptr(GError) error_local = NULL;
|
|
gboolean install_force = (flags & FWUPD_INSTALL_FLAG_FORCE) != 0;
|
|
gboolean device_ignore_validation = fu_device_has_flag (dev, FWUPD_DEVICE_FLAG_IGNORE_VALIDATION);
|
|
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 (!install_force && !device_ignore_validation) {
|
|
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)) {
|
|
g_prefix_error (error,
|
|
"could not write firmware to thunderbolt device at %s: ",
|
|
devpath);
|
|
return FALSE;
|
|
}
|
|
|
|
if (!fu_plugin_thunderbolt_trigger_update (udevice, error)) {
|
|
g_prefix_error (error, "could not start thunderbolt device upgrade: ");
|
|
return FALSE;
|
|
}
|
|
|
|
fu_device_set_status (dev, FWUPD_STATUS_DEVICE_RESTART);
|
|
fu_device_set_remove_delay (dev, FU_PLUGIN_THUNDERBOLT_UPDATE_TIMEOUT);
|
|
fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
fu_plugin_update_attach (FuPlugin *plugin,
|
|
FuDevice *dev,
|
|
GError **error)
|
|
{
|
|
FuPluginData *data = fu_plugin_get_data (plugin);
|
|
const gchar *devpath;
|
|
const gchar *attribute;
|
|
guint64 status;
|
|
g_autoptr(GUdevDevice) udevice = NULL;
|
|
|
|
devpath = fu_device_get_metadata (dev, "sysfs-path");
|
|
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;
|
|
}
|
|
|
|
/* now check if the update actually worked */
|
|
attribute = g_udev_device_get_sysfs_attr (udevice, "nvm_authenticate");
|
|
if (attribute == NULL) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"failed to find nvm_authenticate attribute for %s",
|
|
fu_device_get_name (dev));
|
|
return FALSE;
|
|
}
|
|
status = g_ascii_strtoull (attribute, NULL, 16);
|
|
if ((status == 0x00 && errno == EINVAL) ||
|
|
(status == G_MAXUINT64 && errno == ERANGE)) {
|
|
g_set_error (error, G_IO_ERROR,
|
|
g_io_error_from_errno (errno),
|
|
"failed to read 'nvm_authenticate: %s",
|
|
g_strerror (errno));
|
|
return FALSE;
|
|
}
|
|
|
|
/* 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;
|
|
}
|