mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-16 23:26:19 +00:00
765 lines
20 KiB
C
765 lines
20 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 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;
|
|
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 (fu_plugin_thunderbolt_can_update (device)) {
|
|
if (is_host) {
|
|
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");
|
|
}
|
|
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, "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);
|
|
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_instance_id (dev, device_id);
|
|
if (version != NULL)
|
|
fu_device_set_version (dev, version);
|
|
if (is_host)
|
|
fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_INTERNAL);
|
|
|
|
/* we never open the device, so convert the instance IDs */
|
|
fu_device_setup (dev, NULL);
|
|
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 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 };
|
|
|
|
fu_plugin_set_build_hash (plugin, FU_BUILD_HASH);
|
|
fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_SUPPORTS_PROTOCOL, "com.intel.thunderbolt");
|
|
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_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 == 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;
|
|
}
|