/* * 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-thunderbolt-device.h" #include "fu-thunderbolt-firmware-update.h" #include "fu-thunderbolt-firmware.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; }