fwupd/plugins/thunderbolt/fu-plugin-thunderbolt.c
Mario Limonciello 97acc2bf74 thunderbolt: Rather than hardcoding to PCI slot numbers, use domain in GUID
This allows the PCI topology to change, but assumes that thunderbolt host controllers
are enumerated in the same order every time.
It won't matter if the first controller jumped from bus 5 to 7 and consequently the
second from 65 to 71, but rather that the first was enumerated followed by the second.
2020-03-28 09:52:38 -05:00

823 lines
22 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 <sys/utsname.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include "fu-plugin-vfuncs.h"
#include "fu-hash.h"
#include "fu-device-metadata.h"
#include "fu-thunderbolt-image.h"
#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 gboolean
fu_plugin_thunderbolt_safe_kernel (FuPlugin *plugin, GError **error)
{
g_autofree gchar *minimum_kernel = NULL;
struct utsname name_tmp;
memset (&name_tmp, 0, sizeof(struct utsname));
if (uname (&name_tmp) < 0) {
g_debug ("Failed to read current kernel version");
return TRUE;
}
minimum_kernel = fu_plugin_get_config_value (plugin, "MinimumKernelVersion");
if (minimum_kernel == NULL) {
g_debug ("Ignoring kernel safety checks");
return TRUE;
}
if (fu_common_vercmp_full (name_tmp.release,
minimum_kernel,
FWUPD_VERSION_FORMAT_TRIPLET) < 0) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"kernel %s may not have full Thunderbolt support",
name_tmp.release);
return FALSE;
}
g_debug ("Using kernel %s (minimum %s)", name_tmp.release, minimum_kernel);
return TRUE;
}
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,
"missing sysfs attribute %s", name);
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_uint16 (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,
"%s overflows",
name);
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 gboolean
fu_plugin_thunderbolt_can_update (GUdevDevice *udevice)
{
g_autoptr(GError) nvmem_error = NULL;
g_autoptr(GFile) non_active_nvmem = NULL;
non_active_nvmem = fu_plugin_thunderbolt_find_nvmem (udevice, FALSE,
&nvmem_error);
if (non_active_nvmem == NULL) {
g_debug ("%s", nvmem_error->message);
return FALSE;
}
return TRUE;
}
static void
fu_plugin_thunderbolt_add (FuPlugin *plugin, GUdevDevice *device)
{
FuDevice *dev_tmp;
const gchar *name = NULL;
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;
guint16 gen;
g_autofree gchar *id = NULL;
g_autofree gchar *version = NULL;
g_autofree gchar *vendor_id = NULL;
g_autofree gchar *device_id = NULL;
g_autofree gchar *domain_id = NULL;
g_autoptr(FuDevice) dev = NULL;
g_autoptr(GError) error_vid = NULL;
g_autoptr(GError) error_did = NULL;
g_autoptr(GError) error_gen = NULL;
g_autoptr(GError) error_setup = 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;
}
/* these may be missing on ICL or later */
vid = fu_plugin_thunderbolt_udev_get_uint16 (device, "vendor", &error_vid);
if (vid == 0x0)
g_debug ("failed to get Vendor ID: %s", error_vid->message);
did = fu_plugin_thunderbolt_udev_get_uint16 (device, "device", &error_did);
if (did == 0x0)
g_debug ("failed to get Device ID: %s", error_did->message);
/* requires kernel 5.5 or later, non-fatal if not available */
gen = fu_plugin_thunderbolt_udev_get_uint16 (device, "generation", &error_gen);
if (gen == 0)
g_debug ("Unable to read generation: %s", error_gen->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 (fu_plugin_thunderbolt_can_update (device)) {
/* USB4 controllers don't have a concept of legacy vs native
* so don't try to read a native attribute from their NVM */
if (is_host && gen < 4) {
g_autofree gchar *domain = g_path_get_basename (devpath);
g_autoptr(GError) native_error = NULL;
if (!fu_plugin_thunderbolt_is_native (device,
&is_native,
&native_error)) {
g_warning ("failed to get native mode status: %s",
native_error->message);
return;
}
fu_plugin_add_report_metadata (plugin,
"ThunderboltNative",
is_native ? "True" : "False");
domain_id = g_strdup_printf ("TBT-%04x%04x%s-controller%s",
(guint) vid,
(guint) did,
is_native ? "-native" : "",
domain);
}
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_add_flag (dev, FWUPD_DEVICE_FLAG_DUAL_IMAGE);
} else {
device_id = g_strdup ("TBT-fixed");
fu_device_set_update_error (dev, "Missing non-active nvmem");
}
} 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);
if (!is_host)
name = g_udev_device_get_sysfs_attr (device, "device_name");
if (name == NULL) {
if (gen == 4)
name = "USB4 Controller";
else
name = "Thunderbolt Controller";
}
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, "thunderbolt");
fu_device_set_protocol (dev, "com.intel.thunderbolt");
fu_device_set_version_format (dev, FWUPD_VERSION_FORMAT_PAIR);
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_instance_id (dev, device_id);
if (domain_id != NULL)
fu_device_add_instance_id (dev, domain_id);
if (version != NULL)
fu_device_set_version (dev, version);
if (is_host)
fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_INTERNAL);
fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_REQUIRE_AC);
/* we never open the device, so convert the instance IDs */
if (!fu_device_setup (dev, &error_setup)) {
g_warning ("failed to setup: %s", error_setup->message);
return;
}
fu_plugin_cache_add (plugin, id, dev);
fu_plugin_device_add (plugin, dev);
/* inhibit the idle sleep of the daemon */
fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_INHIBITS_IDLE,
"thunderbolt requires device wakeup");
}
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 not 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 not 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 };
fu_plugin_set_build_hash (plugin, FU_BUILD_HASH);
data->udev = g_udev_client_new (subsystems);
g_signal_connect (data->udev, "uevent",
G_CALLBACK (udev_uevent_cb), plugin);
/* dell-dock plugin uses a slower bus for flashing */
fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_BETTER_THAN, "dell_dock");
}
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_startup (FuPlugin *plugin, GError **error)
{
return fu_plugin_thunderbolt_safe_kernel (plugin, error);
}
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/fwupd/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 == 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;
}