fwupd/plugins/thunderbolt/fu-thunderbolt-device.c
Richard Hughes eddaed0c11 Allow specifying more than one VendorID for a device
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.
2021-01-04 22:30:20 +00:00

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;
}