mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-31 14:11:48 +00:00

Asking the user for the UID mapping isn't working very well, as it requires lots of manual handholding. It also doesn't work very well when the device vendor does not actually have a PCI ID or if the vendor has split into two entities. Just use the OUI address as an additional VendorID and match any of the device IDs against any of the metadata-supplied values.
809 lines
25 KiB
C
809 lines
25 KiB
C
/*
|
|
* Copyright (C) 2017 Christian J. Kellner <christian@kellner.me>
|
|
* Copyright (C) 2020 Mario Limonciello <mario.limonciello@dell.com>
|
|
*
|
|
* 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-common.h"
|
|
#include "fu-device-metadata.h"
|
|
#include "fu-thunderbolt-device.h"
|
|
#include "fu-thunderbolt-firmware.h"
|
|
#include "fu-thunderbolt-firmware-update.h"
|
|
#include "fu-udev-device-private.h"
|
|
|
|
typedef enum {
|
|
FU_THUNDERBOLT_DEVICE_TYPE_DEVICE_CONTROLLER,
|
|
FU_THUNDERBOLT_DEVICE_TYPE_HOST_CONTROLLER,
|
|
FU_THUNDERBOLT_DEVICE_TYPE_RETIMER
|
|
} FuThunderboltDeviceType;
|
|
|
|
struct _FuThunderboltDevice {
|
|
FuUdevDevice parent_instance;
|
|
FuThunderboltDeviceType device_type;
|
|
gboolean safe_mode;
|
|
gboolean is_native;
|
|
guint16 gen;
|
|
gchar *devpath;
|
|
const gchar *auth_method;
|
|
};
|
|
|
|
#define TBT_NVM_RETRY_TIMEOUT 200 /* ms */
|
|
#define FU_PLUGIN_THUNDERBOLT_UPDATE_TIMEOUT 60000 /* ms */
|
|
|
|
G_DEFINE_TYPE (FuThunderboltDevice, fu_thunderbolt_device, FU_TYPE_UDEV_DEVICE)
|
|
|
|
static GFile *
|
|
fu_thunderbolt_device_find_nvmem (FuThunderboltDevice *self,
|
|
gboolean active,
|
|
GError **error)
|
|
{
|
|
|
|
const gchar *nvmem_dir = active ? "nvm_active" : "nvm_non_active";
|
|
const gchar *name;
|
|
g_autoptr(GDir) d = NULL;
|
|
|
|
if (G_UNLIKELY (self->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 (self->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 (self->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_thunderbolt_device_read_status_block (FuThunderboltDevice *self, GError **error)
|
|
{
|
|
gsize nr_chunks;
|
|
g_autoptr(GFile) nvmem = NULL;
|
|
g_autoptr(GBytes) controller_fw = NULL;
|
|
g_autoptr(GInputStream) istr = NULL;
|
|
g_autoptr(FuThunderboltFirmware) firmware = fu_thunderbolt_firmware_new ();
|
|
|
|
nvmem = fu_thunderbolt_device_find_nvmem (self, 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;
|
|
if (!fu_firmware_parse (FU_FIRMWARE (firmware),
|
|
controller_fw,
|
|
FWUPD_INSTALL_FLAG_NONE,
|
|
error))
|
|
return FALSE;
|
|
self->is_native = fu_thunderbolt_firmware_is_native (firmware);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_thunderbolt_device_check_authorized (FuThunderboltDevice *self, GError **error)
|
|
{
|
|
guint64 status;
|
|
g_autofree gchar *attribute = NULL;
|
|
const gchar *update_error = NULL;
|
|
/* read directly from file to prevent udev caching */
|
|
g_autofree gchar *safe_path = g_build_path ("/", self->devpath, "authorized", NULL);
|
|
|
|
if (!g_file_test (safe_path, G_FILE_TEST_EXISTS)) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"missing authorized attribute");
|
|
return FALSE;
|
|
}
|
|
|
|
if (!g_file_get_contents (safe_path, &attribute, NULL, error))
|
|
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 'authorized: %s",
|
|
g_strerror (errno));
|
|
return FALSE;
|
|
}
|
|
if (status == 1 || status == 2)
|
|
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE);
|
|
else
|
|
update_error = "Not authorized";
|
|
fu_device_set_update_error (FU_DEVICE (self), update_error);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_thunderbolt_device_can_update (FuThunderboltDevice *self)
|
|
{
|
|
g_autoptr(GError) nvmem_error = NULL;
|
|
g_autoptr(GFile) non_active_nvmem = NULL;
|
|
|
|
non_active_nvmem = fu_thunderbolt_device_find_nvmem (self, FALSE,
|
|
&nvmem_error);
|
|
if (non_active_nvmem == NULL) {
|
|
g_debug ("%s", nvmem_error->message);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_thunderbolt_device_get_version (FuThunderboltDevice *self, GError **error)
|
|
{
|
|
g_auto(GStrv) split = NULL;
|
|
g_autofree gchar *version_raw = NULL;
|
|
g_autofree gchar *version = NULL;
|
|
/* read directly from file to prevent udev caching */
|
|
g_autofree gchar *safe_path = g_build_path ("/", self->devpath, "nvm_version", NULL);
|
|
|
|
if (!g_file_test (safe_path, G_FILE_TEST_EXISTS)) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"missing nvm_version attribute");
|
|
return FALSE;
|
|
}
|
|
|
|
for (guint i = 0; i < 50; i++) {
|
|
g_autoptr(GError) error_local = NULL;
|
|
/* glib can't return a properly mapped -ENODATA but the
|
|
* kernel only returns -ENODATA or -EAGAIN */
|
|
if (g_file_get_contents (safe_path, &version_raw, NULL, &error_local))
|
|
break;
|
|
g_debug ("Attempt %u: Failed to read NVM version", i);
|
|
g_usleep (TBT_NVM_RETRY_TIMEOUT * 1000);
|
|
/* safe mode probably */
|
|
if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
|
|
break;
|
|
}
|
|
|
|
if (version_raw == NULL) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"failed to read NVM");
|
|
return FALSE;
|
|
}
|
|
split = g_strsplit (version_raw, ".", -1);
|
|
if (g_strv_length (split) != 2) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"invalid nvm_version format: %s", version_raw);
|
|
return FALSE;
|
|
}
|
|
|
|
version = g_strdup_printf ("%02x.%02x",
|
|
(guint) g_ascii_strtoull (split[0], NULL, 16),
|
|
(guint) g_ascii_strtoull (split[1], NULL, 16));
|
|
fu_device_set_version (FU_DEVICE (self), version);
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
fu_thunderbolt_device_check_safe_mode (FuThunderboltDevice *self)
|
|
{
|
|
/* failed to read, for host check for safe mode */
|
|
if (self->device_type != FU_THUNDERBOLT_DEVICE_TYPE_DEVICE_CONTROLLER)
|
|
return;
|
|
g_warning ("%s is in safe mode -- VID/DID will "
|
|
"need to be set by another plugin",
|
|
self->devpath);
|
|
self->safe_mode = TRUE;
|
|
fu_device_set_version (FU_DEVICE (self), "00.00");
|
|
fu_device_add_instance_id (FU_DEVICE (self), "TBT-safemode");
|
|
fu_device_set_metadata_boolean (FU_DEVICE (self), FU_DEVICE_METADATA_TBT_IS_SAFE_MODE, TRUE);
|
|
}
|
|
|
|
static const gchar*
|
|
fu_thunderbolt_device_type_to_string (FuThunderboltDevice *self)
|
|
{
|
|
if (self->device_type == FU_THUNDERBOLT_DEVICE_TYPE_HOST_CONTROLLER) {
|
|
if (self->gen >= 4)
|
|
return "USB4 host controller";
|
|
else
|
|
return "Thunderbolt host controller";
|
|
}
|
|
if (self->device_type == FU_THUNDERBOLT_DEVICE_TYPE_DEVICE_CONTROLLER) {
|
|
if (self->gen >= 4)
|
|
return "USB4 device controller";
|
|
else
|
|
return "Thunderbolt device controller";
|
|
}
|
|
if (self->device_type == FU_THUNDERBOLT_DEVICE_TYPE_RETIMER)
|
|
return "USB4 Retimer";
|
|
return "Unknown";
|
|
}
|
|
|
|
static void
|
|
fu_thunderbolt_device_to_string (FuUdevDevice *device, guint idt, GString *str)
|
|
{
|
|
FuThunderboltDevice *self = FU_THUNDERBOLT_DEVICE (device);
|
|
fu_common_string_append_kv (str, idt, "Device Type", fu_thunderbolt_device_type_to_string (self));
|
|
fu_common_string_append_kb (str, idt, "Safe Mode", self->safe_mode);
|
|
fu_common_string_append_kb (str, idt, "Native mode", self->is_native);
|
|
fu_common_string_append_ku (str, idt, "Generation", self->gen);
|
|
fu_common_string_append_kv (str, idt, "AuthAttribute", self->auth_method);
|
|
}
|
|
|
|
static gboolean
|
|
fu_thunderbolt_device_probe (FuUdevDevice *device, GError **error)
|
|
{
|
|
FuThunderboltDevice *self = FU_THUNDERBOLT_DEVICE (device);
|
|
const gchar *tmp = fu_udev_device_get_devtype (device);
|
|
|
|
/* device */
|
|
if (g_strcmp0 (tmp, "thunderbolt_device") == 0) {
|
|
tmp = fu_udev_device_get_sysfs_attr (device, "unique_id", NULL);
|
|
if (tmp != NULL)
|
|
fu_device_set_physical_id (FU_DEVICE (device), tmp);
|
|
/* retimer */
|
|
} else if (g_strcmp0 (tmp, "thunderbolt_retimer") == 0) {
|
|
self->device_type = FU_THUNDERBOLT_DEVICE_TYPE_RETIMER;
|
|
tmp = g_path_get_basename (fu_udev_device_get_sysfs_path (FU_UDEV_DEVICE (device)));
|
|
if (tmp != NULL)
|
|
fu_device_set_physical_id (FU_DEVICE (device), tmp);
|
|
/* domain or unsupported */
|
|
} else {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"%s not used", tmp);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static guint16
|
|
fu_thunderbolt_device_get_attr_uint16 (FuThunderboltDevice *self,
|
|
const gchar *name,
|
|
GError **error)
|
|
{
|
|
const gchar *str;
|
|
guint64 val;
|
|
|
|
str = fu_udev_device_get_sysfs_attr (FU_UDEV_DEVICE (self), name, error);
|
|
if (str == NULL)
|
|
return 0x0;
|
|
|
|
val = g_ascii_strtoull (str, NULL, 16);
|
|
if (val == 0x0) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"failed to parse %s", str);
|
|
return 0;
|
|
}
|
|
if (val > G_MAXUINT16) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"%s overflows",
|
|
name);
|
|
return 0x0;
|
|
}
|
|
return (guint16) val;
|
|
}
|
|
|
|
static gboolean
|
|
fu_thunderbolt_device_setup_controller (FuDevice *device, GError **error)
|
|
{
|
|
FuThunderboltDevice *self = FU_THUNDERBOLT_DEVICE (device);
|
|
const gchar *tmp = NULL;
|
|
guint16 did;
|
|
guint16 vid;
|
|
g_autoptr(GError) error_gen = NULL;
|
|
g_autofree gchar *parent_name = fu_udev_device_get_parent_name (FU_UDEV_DEVICE (self));
|
|
|
|
/* these may be missing on ICL or later */
|
|
vid = fu_udev_device_get_vendor (FU_UDEV_DEVICE (self));
|
|
if (vid == 0x0)
|
|
g_debug ("failed to get Vendor ID");
|
|
|
|
did = fu_udev_device_get_model (FU_UDEV_DEVICE (self));
|
|
if (did == 0x0)
|
|
g_debug ("failed to get Device ID");
|
|
|
|
/* requires kernel 5.5 or later, non-fatal if not available */
|
|
self->gen = fu_thunderbolt_device_get_attr_uint16 (self, "generation", &error_gen);
|
|
if (self->gen == 0)
|
|
g_debug ("Unable to read generation: %s", error_gen->message);
|
|
|
|
/* read the first block of firmware to get the is-native attribute */
|
|
if (!fu_thunderbolt_device_read_status_block (self, error))
|
|
return FALSE;
|
|
|
|
/* determine if host controller or not */
|
|
if (parent_name != NULL && g_str_has_prefix (parent_name, "domain")) {
|
|
self->device_type = FU_THUNDERBOLT_DEVICE_TYPE_HOST_CONTROLLER;
|
|
fu_device_add_flag (device, FWUPD_DEVICE_FLAG_INTERNAL);
|
|
fu_device_set_summary (device, "Unmatched performance for high-speed I/O");
|
|
} else {
|
|
tmp = fu_udev_device_get_sysfs_attr (FU_UDEV_DEVICE (self), "device_name", NULL);
|
|
}
|
|
|
|
/* set the controller name */
|
|
if (tmp == NULL)
|
|
tmp = fu_thunderbolt_device_type_to_string (self);
|
|
fu_device_set_name (device, tmp);
|
|
|
|
/* set vendor string */
|
|
tmp = fu_udev_device_get_sysfs_attr (FU_UDEV_DEVICE (self), "vendor_name", error);
|
|
if (tmp == NULL)
|
|
return FALSE;
|
|
fu_device_set_vendor (device, tmp);
|
|
|
|
if (fu_device_get_version (device) == NULL)
|
|
fu_thunderbolt_device_check_safe_mode (self);
|
|
|
|
if (self->safe_mode) {
|
|
fu_device_set_update_error (device, "Device is in safe mode");
|
|
} else {
|
|
g_autofree gchar *device_id = NULL;
|
|
g_autofree gchar *domain_id = NULL;
|
|
if (fu_thunderbolt_device_can_update (self)) {
|
|
g_autofree gchar *vendor_id = NULL;
|
|
g_autofree gchar *domain = g_path_get_basename (self->devpath);
|
|
/* USB4 controllers don't have a concept of legacy vs native
|
|
* so don't try to read a native attribute from their NVM */
|
|
if (self->device_type == FU_THUNDERBOLT_DEVICE_TYPE_HOST_CONTROLLER
|
|
&& self->gen < 4) {
|
|
domain_id = g_strdup_printf ("TBT-%04x%04x%s-controller%s",
|
|
(guint) vid,
|
|
(guint) did,
|
|
self->is_native ? "-native" : "",
|
|
domain);
|
|
}
|
|
vendor_id = g_strdup_printf ("TBT:0x%04X", (guint) vid);
|
|
fu_device_add_vendor_id (device, vendor_id);
|
|
device_id = g_strdup_printf ("TBT-%04x%04x%s",
|
|
(guint) vid,
|
|
(guint) did,
|
|
self->is_native ? "-native" : "");
|
|
fu_device_add_flag (device, FWUPD_DEVICE_FLAG_DUAL_IMAGE);
|
|
|
|
/* check if device is authorized */
|
|
if (!fu_thunderbolt_device_check_authorized (self, error))
|
|
return FALSE;
|
|
|
|
} else {
|
|
device_id = g_strdup ("TBT-fixed");
|
|
}
|
|
fu_device_add_instance_id (device, device_id);
|
|
if (domain_id != NULL)
|
|
fu_device_add_instance_id (device, domain_id);
|
|
}
|
|
|
|
/* determine if we can update on unplug */
|
|
if (fu_udev_device_get_sysfs_attr (FU_UDEV_DEVICE (device),
|
|
"nvm_authenticate_on_disconnect",
|
|
NULL) != NULL) {
|
|
self->auth_method = "nvm_authenticate_on_disconnect";
|
|
/* flushes image */
|
|
fu_device_add_flag (device, FWUPD_DEVICE_FLAG_USABLE_DURING_UPDATE);
|
|
/* forces the device to write to authenticate on disconnect attribute */
|
|
fu_device_remove_flag (device, FWUPD_DEVICE_FLAG_SKIPS_RESTART);
|
|
/* control the order of activation (less relevant; install too though) */
|
|
fu_device_add_flag (device, FWUPD_DEVICE_FLAG_INSTALL_PARENT_FIRST);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_thunderbolt_device_setup_retimer (FuDevice *device, GError **error)
|
|
{
|
|
FuThunderboltDevice *self = FU_THUNDERBOLT_DEVICE (device);
|
|
guint16 did;
|
|
guint16 vid;
|
|
g_autofree gchar *instance = NULL;
|
|
|
|
/* as defined in PCIe 4.0 spec */
|
|
fu_device_set_summary (device, "A physical layer protocol-aware, software-transparent extension device "
|
|
"that forms two separate electrical link segments");
|
|
fu_device_set_name (device, fu_thunderbolt_device_type_to_string (self));
|
|
fu_device_add_flag (device, FWUPD_DEVICE_FLAG_UPDATABLE);
|
|
fu_device_add_flag (device, FWUPD_DEVICE_FLAG_DUAL_IMAGE);
|
|
fu_device_add_flag (device, FWUPD_DEVICE_FLAG_INTERNAL);
|
|
vid = fu_udev_device_get_vendor (FU_UDEV_DEVICE (self));
|
|
if (vid == 0x0) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"missing vendor id");
|
|
return FALSE;
|
|
}
|
|
|
|
did = fu_udev_device_get_model (FU_UDEV_DEVICE (self));
|
|
if (did == 0x0) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"missing device id");
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
instance = g_strdup_printf ("TBT-%04x%04x-retimer%s",
|
|
(guint) vid,
|
|
(guint) did,
|
|
fu_device_get_physical_id (device));
|
|
fu_device_add_instance_id (device, instance);
|
|
|
|
/* hardcoded for now:
|
|
* 1. unsure if ID_VENDOR_FROM_DATABASE works in this instance
|
|
* 2. we don't recognize anyone else yet
|
|
*/
|
|
if (fu_device_get_vendor (device) == NULL)
|
|
fu_device_set_vendor (device, "Intel");
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_thunderbolt_device_setup (FuDevice *device, GError **error)
|
|
{
|
|
FuThunderboltDevice *self = FU_THUNDERBOLT_DEVICE (device);
|
|
g_autoptr(GError) error_version = NULL;
|
|
|
|
self->devpath = g_strdup (fu_udev_device_get_sysfs_path (FU_UDEV_DEVICE (device)));
|
|
|
|
/* try to read the version */
|
|
if (!fu_thunderbolt_device_get_version (self, &error_version)) {
|
|
if (g_error_matches (error_version, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) {
|
|
g_propagate_error (error, g_steal_pointer (&error_version));
|
|
return FALSE;
|
|
}
|
|
g_debug ("%s", error_version->message);
|
|
}
|
|
|
|
/* default behavior */
|
|
self->auth_method = "nvm_authenticate";
|
|
|
|
/* configure differences between retimer and controller */
|
|
if (self->device_type == FU_THUNDERBOLT_DEVICE_TYPE_RETIMER)
|
|
return fu_thunderbolt_device_setup_retimer (device, error);
|
|
return fu_thunderbolt_device_setup_controller (device, error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_thunderbolt_device_activate (FuDevice *device, GError **error)
|
|
{
|
|
FuUdevDevice *udev = FU_UDEV_DEVICE (device);
|
|
|
|
return fu_udev_device_write_sysfs (udev, "nvm_authenticate", "1", error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_thunderbolt_device_authenticate (FuDevice *device, GError **error)
|
|
{
|
|
FuThunderboltDevice *self = FU_THUNDERBOLT_DEVICE (device);
|
|
FuUdevDevice *udev = FU_UDEV_DEVICE (device);
|
|
|
|
return fu_udev_device_write_sysfs (udev, self->auth_method, "1", error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_thunderbolt_device_flush_update (FuDevice *device, GError **error)
|
|
{
|
|
FuThunderboltDevice *self = FU_THUNDERBOLT_DEVICE (device);
|
|
FuUdevDevice *udev = FU_UDEV_DEVICE (device);
|
|
|
|
return fu_udev_device_write_sysfs (udev, self->auth_method, "2", error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_thunderbolt_device_attach (FuDevice *device, GError **error)
|
|
{
|
|
const gchar *attribute;
|
|
guint64 status;
|
|
|
|
/* now check if the update actually worked */
|
|
attribute = fu_udev_device_get_sysfs_attr (FU_UDEV_DEVICE (device),
|
|
"nvm_authenticate",
|
|
error);
|
|
if (attribute == NULL)
|
|
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;
|
|
}
|
|
|
|
static gboolean
|
|
fu_thunderbolt_device_rescan (FuDevice *device, GError **error)
|
|
{
|
|
FuThunderboltDevice *self = FU_THUNDERBOLT_DEVICE (device);
|
|
|
|
/* refresh updatability */
|
|
if (!fu_thunderbolt_device_check_authorized (self, error))
|
|
return FALSE;
|
|
|
|
/* refresh the version */
|
|
return fu_thunderbolt_device_get_version (self, error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_thunderbolt_device_write_data (FuThunderboltDevice *self,
|
|
GBytes *blob_fw,
|
|
GError **error)
|
|
{
|
|
gsize fw_size;
|
|
gsize nwritten;
|
|
gssize n;
|
|
g_autoptr(GFile) nvmem = NULL;
|
|
g_autoptr(GOutputStream) os = NULL;
|
|
|
|
nvmem = fu_thunderbolt_device_find_nvmem (self, 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 (FU_DEVICE (self), nwritten, fw_size);
|
|
|
|
do {
|
|
g_autoptr(GBytes) fw_data = NULL;
|
|
|
|
fw_data = fu_common_bytes_new_offset (blob_fw,
|
|
nwritten,
|
|
fw_size - nwritten,
|
|
error);
|
|
if (fw_data == NULL)
|
|
return FALSE;
|
|
|
|
n = g_output_stream_write_bytes (os,
|
|
fw_data,
|
|
NULL,
|
|
error);
|
|
if (n < 0)
|
|
return FALSE;
|
|
|
|
nwritten += n;
|
|
fu_device_set_progress_full (FU_DEVICE (self), 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);
|
|
}
|
|
|
|
static FuFirmware *
|
|
fu_thunderbolt_device_prepare_firmware (FuDevice *device,
|
|
GBytes *fw,
|
|
FwupdInstallFlags flags,
|
|
GError **error)
|
|
{
|
|
FuThunderboltDevice *self = FU_THUNDERBOLT_DEVICE (device);
|
|
g_autoptr(FuThunderboltFirmwareUpdate) firmware = fu_thunderbolt_firmware_update_new ();
|
|
g_autoptr(FuThunderboltFirmware) firmware_old = fu_thunderbolt_firmware_new ();
|
|
g_autoptr(GBytes) controller_fw = NULL;
|
|
g_autoptr(GFile) nvmem = NULL;
|
|
|
|
/* parse */
|
|
if (!fu_firmware_parse (FU_FIRMWARE (firmware), fw, flags, error))
|
|
return NULL;
|
|
|
|
/* get current NVMEM */
|
|
nvmem = fu_thunderbolt_device_find_nvmem (self, TRUE, error);
|
|
if (nvmem == NULL)
|
|
return NULL;
|
|
controller_fw = g_file_load_bytes (nvmem, NULL, NULL, error);
|
|
if (!fu_firmware_parse (FU_FIRMWARE (firmware_old), controller_fw, flags, error))
|
|
return NULL;
|
|
if (fu_thunderbolt_firmware_is_host (FU_THUNDERBOLT_FIRMWARE (firmware)) !=
|
|
fu_thunderbolt_firmware_is_host (firmware_old)) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_FILE,
|
|
"incorrect firmware mode, got %s, expected %s",
|
|
fu_thunderbolt_firmware_is_host (FU_THUNDERBOLT_FIRMWARE (firmware)) ? "host" : "device",
|
|
fu_thunderbolt_firmware_is_host (firmware_old) ? "host" : "device");
|
|
return NULL;
|
|
}
|
|
if (fu_thunderbolt_firmware_get_vendor_id (FU_THUNDERBOLT_FIRMWARE (firmware)) !=
|
|
fu_thunderbolt_firmware_get_vendor_id (firmware_old)) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_FILE,
|
|
"incorrect device vendor, got 0x%04x, expected 0x%04x",
|
|
fu_thunderbolt_firmware_get_vendor_id (FU_THUNDERBOLT_FIRMWARE (firmware)),
|
|
fu_thunderbolt_firmware_get_vendor_id (firmware_old));
|
|
return NULL;
|
|
}
|
|
if (fu_thunderbolt_firmware_get_device_id (FU_THUNDERBOLT_FIRMWARE (firmware)) !=
|
|
fu_thunderbolt_firmware_get_device_id (firmware_old)) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_FILE,
|
|
"incorrect device type, got 0x%04x, expected 0x%04x",
|
|
fu_thunderbolt_firmware_get_device_id (FU_THUNDERBOLT_FIRMWARE (firmware)),
|
|
fu_thunderbolt_firmware_get_device_id (firmware_old));
|
|
return NULL;
|
|
}
|
|
if ((flags & FWUPD_INSTALL_FLAG_IGNORE_VID_PID) == 0) {
|
|
if (fu_thunderbolt_firmware_get_model_id (FU_THUNDERBOLT_FIRMWARE (firmware)) !=
|
|
fu_thunderbolt_firmware_get_model_id (firmware_old)) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_FILE,
|
|
"incorrect device model, got 0x%04x, expected 0x%04x",
|
|
fu_thunderbolt_firmware_get_model_id (FU_THUNDERBOLT_FIRMWARE (firmware)),
|
|
fu_thunderbolt_firmware_get_model_id (firmware_old));
|
|
return NULL;
|
|
}
|
|
/* old firmware has PD but new doesn't (we don't care about other way around) */
|
|
if (fu_thunderbolt_firmware_get_has_pd (firmware_old) &&
|
|
!fu_thunderbolt_firmware_get_has_pd (FU_THUNDERBOLT_FIRMWARE (firmware))) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_FILE,
|
|
"incorrect PD section");
|
|
return NULL;
|
|
}
|
|
if (fu_thunderbolt_firmware_get_flash_size (FU_THUNDERBOLT_FIRMWARE (firmware)) !=
|
|
fu_thunderbolt_firmware_get_flash_size (firmware_old)) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_FILE,
|
|
"incorrect flash size");
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/* success */
|
|
return FU_FIRMWARE (g_steal_pointer (&firmware));
|
|
}
|
|
|
|
static gboolean
|
|
fu_thunderbolt_device_write_firmware (FuDevice *device,
|
|
FuFirmware *firmware,
|
|
FwupdInstallFlags flags,
|
|
GError **error)
|
|
{
|
|
FuThunderboltDevice *self = FU_THUNDERBOLT_DEVICE (device);
|
|
g_autoptr(GBytes) blob_fw = NULL;
|
|
|
|
/* get default image */
|
|
blob_fw = fu_firmware_get_image_default_bytes (firmware, error);
|
|
if (blob_fw == NULL)
|
|
return FALSE;
|
|
|
|
fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE);
|
|
if (!fu_thunderbolt_device_write_data (self, blob_fw, error)) {
|
|
g_prefix_error (error,
|
|
"could not write firmware to thunderbolt device at %s: ",
|
|
self->devpath);
|
|
return FALSE;
|
|
}
|
|
|
|
/* flush the image if supported by kernel and/or device */
|
|
if (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_USABLE_DURING_UPDATE)) {
|
|
if (!fu_thunderbolt_device_flush_update (device, error))
|
|
return FALSE;
|
|
fu_device_add_flag (device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION);
|
|
}
|
|
|
|
/* using an active delayed activation flow later (either shutdown or another plugin) */
|
|
if (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_SKIPS_RESTART)) {
|
|
g_debug ("Skipping Thunderbolt reset per quirk request");
|
|
fu_device_add_flag (device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION);
|
|
return TRUE;
|
|
}
|
|
|
|
/* authenticate (possibly on unplug if device supports it) */
|
|
if (!fu_thunderbolt_device_authenticate (FU_DEVICE (self), error)) {
|
|
g_prefix_error (error, "could not start thunderbolt device upgrade: ");
|
|
return FALSE;
|
|
}
|
|
|
|
/* whether to wait for a device replug or not */
|
|
if (!fu_device_has_flag (device, FWUPD_DEVICE_FLAG_USABLE_DURING_UPDATE)) {
|
|
fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART);
|
|
fu_device_set_remove_delay (device, FU_PLUGIN_THUNDERBOLT_UPDATE_TIMEOUT);
|
|
fu_device_add_flag (device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
fu_thunderbolt_device_init (FuThunderboltDevice *self)
|
|
{
|
|
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_REQUIRE_AC);
|
|
fu_device_add_icon (FU_DEVICE (self), "thunderbolt");
|
|
fu_device_set_protocol (FU_DEVICE (self), "com.intel.thunderbolt");
|
|
fu_device_set_version_format (FU_DEVICE (self), FWUPD_VERSION_FORMAT_PAIR);
|
|
}
|
|
|
|
static void
|
|
fu_thunderbolt_device_finalize (GObject *object)
|
|
{
|
|
FuThunderboltDevice *self = FU_THUNDERBOLT_DEVICE (object);
|
|
G_OBJECT_CLASS (fu_thunderbolt_device_parent_class)->finalize (object);
|
|
g_free (self->devpath);
|
|
}
|
|
|
|
static void
|
|
fu_thunderbolt_device_class_init (FuThunderboltDeviceClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass);
|
|
FuUdevDeviceClass *klass_udev_device = FU_UDEV_DEVICE_CLASS (klass);
|
|
object_class->finalize = fu_thunderbolt_device_finalize;
|
|
klass_device->activate = fu_thunderbolt_device_activate;
|
|
klass_udev_device->to_string = fu_thunderbolt_device_to_string;
|
|
klass_device->setup = fu_thunderbolt_device_setup;
|
|
klass_device->prepare_firmware = fu_thunderbolt_device_prepare_firmware;
|
|
klass_device->write_firmware = fu_thunderbolt_device_write_firmware;
|
|
klass_device->attach = fu_thunderbolt_device_attach;
|
|
klass_device->rescan = fu_thunderbolt_device_rescan;
|
|
klass_udev_device->probe = fu_thunderbolt_device_probe;
|
|
}
|