/* * Copyright (C) 2017 Christian J. Kellner * Copyright (C) 2020 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #include #include #include #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 (FuDevice *device, guint idt, GString *str) { FuThunderboltDevice *self = FU_THUNDERBOLT_DEVICE (device); /* FuUdevDevice->to_string */ FU_DEVICE_CLASS (fu_thunderbolt_device_parent_class)->to_string (device, idt, str); 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 (FuDevice *device, GError **error) { FuThunderboltDevice *self = FU_THUNDERBOLT_DEVICE (device); const gchar *tmp = fu_udev_device_get_devtype (FU_UDEV_DEVICE (device)); /* FuUdevDevice->probe */ if (!FU_DEVICE_CLASS (fu_thunderbolt_device_parent_class)->probe (device, error)) return FALSE; /* device */ if (g_strcmp0 (tmp, "thunderbolt_device") == 0) { tmp = fu_udev_device_get_sysfs_attr (FU_UDEV_DEVICE (device), "unique_id", NULL); if (tmp != NULL) fu_device_set_physical_id (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 (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); } else { fu_device_add_internal_flag (device, FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID); } 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 (controller_fw == NULL) return NULL; 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_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_add_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); object_class->finalize = fu_thunderbolt_device_finalize; klass_device->activate = fu_thunderbolt_device_activate; klass_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_device->probe = fu_thunderbolt_device_probe; }