mirror of
https://git.proxmox.com/git/fwupd
synced 2026-03-27 23:05:48 +00:00
Although they do a lot of the same things, trying to deal with both kinds of device in the same GObject type was making it very hard to understand and maintain. Split out two GObjects that both derive from FuThunderboltDevice.
345 lines
11 KiB
C
345 lines
11 KiB
C
/*
|
|
* Copyright (C) 2021 Richard Hughes <richard@hughsie.com>
|
|
* 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 <fwupdplugin.h>
|
|
|
|
#include "fu-thunderbolt-common.h"
|
|
#include "fu-thunderbolt-controller.h"
|
|
#include "fu-thunderbolt-firmware.h"
|
|
|
|
typedef enum {
|
|
FU_THUNDERBOLT_CONTROLLER_KIND_DEVICE,
|
|
FU_THUNDERBOLT_CONTROLLER_KIND_HOST,
|
|
} FuThunderboltControllerKind;
|
|
|
|
struct _FuThunderboltController {
|
|
FuThunderboltDevice parent_instance;
|
|
FuThunderboltControllerKind controller_kind;
|
|
gboolean safe_mode;
|
|
gboolean is_native;
|
|
guint16 gen;
|
|
guint host_online_timer_id;
|
|
};
|
|
|
|
G_DEFINE_TYPE(FuThunderboltController, fu_thunderbolt_controller, FU_TYPE_THUNDERBOLT_DEVICE)
|
|
|
|
static void
|
|
fu_thunderbolt_controller_check_safe_mode(FuThunderboltController *self)
|
|
{
|
|
const gchar *devpath = fu_udev_device_get_sysfs_path(FU_UDEV_DEVICE(self));
|
|
/* failed to read, for host check for safe mode */
|
|
if (self->controller_kind != FU_THUNDERBOLT_CONTROLLER_KIND_DEVICE)
|
|
return;
|
|
g_warning("%s is in safe mode -- VID/DID will "
|
|
"need to be set by another plugin",
|
|
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_controller_kind_to_string(FuThunderboltController *self)
|
|
{
|
|
if (self->controller_kind == FU_THUNDERBOLT_CONTROLLER_KIND_HOST) {
|
|
if (self->gen >= 4)
|
|
return "USB4 host controller";
|
|
else
|
|
return "Thunderbolt host controller";
|
|
}
|
|
if (self->controller_kind == FU_THUNDERBOLT_CONTROLLER_KIND_DEVICE) {
|
|
if (self->gen >= 4)
|
|
return "USB4 device controller";
|
|
else
|
|
return "Thunderbolt device controller";
|
|
}
|
|
return "Unknown";
|
|
}
|
|
|
|
static void
|
|
fu_thunderbolt_controller_to_string(FuDevice *device, guint idt, GString *str)
|
|
{
|
|
FuThunderboltController *self = FU_THUNDERBOLT_CONTROLLER(device);
|
|
|
|
/* FuThunderboltDevice->to_string */
|
|
FU_DEVICE_CLASS(fu_thunderbolt_controller_parent_class)->to_string(device, idt, str);
|
|
|
|
fu_common_string_append_kv(str,
|
|
idt,
|
|
"Device Type",
|
|
fu_thunderbolt_controller_kind_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);
|
|
}
|
|
|
|
static gboolean
|
|
fu_thunderbolt_controller_probe(FuDevice *device, GError **error)
|
|
{
|
|
FuThunderboltController *self = FU_THUNDERBOLT_CONTROLLER(device);
|
|
const gchar *unique_id;
|
|
g_autofree gchar *parent_name = NULL;
|
|
|
|
/* FuUdevDevice->probe */
|
|
if (!FU_DEVICE_CLASS(fu_thunderbolt_controller_parent_class)->probe(device, error))
|
|
return FALSE;
|
|
|
|
/* determine if host controller or not */
|
|
parent_name = fu_udev_device_get_parent_name(FU_UDEV_DEVICE(self));
|
|
if (parent_name != NULL && g_str_has_prefix(parent_name, "domain"))
|
|
self->controller_kind = FU_THUNDERBOLT_CONTROLLER_KIND_HOST;
|
|
unique_id = fu_udev_device_get_sysfs_attr(FU_UDEV_DEVICE(device), "unique_id", NULL);
|
|
if (unique_id != NULL)
|
|
fu_device_set_physical_id(device, unique_id);
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_thunderbolt_controller_read_status_block(FuThunderboltController *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(FU_THUNDERBOLT_DEVICE(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_controller_can_update(FuThunderboltController *self)
|
|
{
|
|
g_autoptr(GError) nvmem_error = NULL;
|
|
g_autoptr(GFile) non_active_nvmem = NULL;
|
|
|
|
non_active_nvmem =
|
|
fu_thunderbolt_device_find_nvmem(FU_THUNDERBOLT_DEVICE(self), FALSE, &nvmem_error);
|
|
if (non_active_nvmem == NULL) {
|
|
g_debug("%s", nvmem_error->message);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_thunderbolt_controller_set_port_offline_cb(gpointer user_data)
|
|
{
|
|
FuThunderboltController *self = FU_THUNDERBOLT_CONTROLLER(user_data);
|
|
g_autoptr(GError) error_local = NULL;
|
|
|
|
if (!fu_thunderbolt_udev_set_port_online(FU_UDEV_DEVICE(self), &error_local))
|
|
g_warning("failed to set online after initial delay: %s", error_local->message);
|
|
|
|
/* no longer valid */
|
|
self->host_online_timer_id = 0;
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_thunderbolt_controller_setup_usb4(FuThunderboltController *self, GError **error)
|
|
{
|
|
if (!fu_thunderbolt_udev_set_port_offline(FU_UDEV_DEVICE(self), error))
|
|
return FALSE;
|
|
self->host_online_timer_id =
|
|
g_timeout_add_seconds(5, fu_thunderbolt_controller_set_port_offline_cb, self);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_thunderbolt_controller_setup(FuDevice *device, GError **error)
|
|
{
|
|
FuThunderboltController *self = FU_THUNDERBOLT_CONTROLLER(device);
|
|
const gchar *tmp = NULL;
|
|
guint16 did;
|
|
guint16 vid;
|
|
g_autoptr(GError) error_gen = NULL;
|
|
g_autoptr(GError) error_version = NULL;
|
|
|
|
/* try to read the version */
|
|
if (!fu_thunderbolt_device_get_version(FU_THUNDERBOLT_DEVICE(self), &error_version)) {
|
|
if (self->controller_kind != FU_THUNDERBOLT_CONTROLLER_KIND_HOST &&
|
|
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);
|
|
}
|
|
|
|
/* 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_udev_get_attr_uint16(FU_UDEV_DEVICE(self), "generation", &error_gen);
|
|
if (self->gen == 0)
|
|
g_debug("Unable to read generation: %s", error_gen->message);
|
|
|
|
if (self->controller_kind == FU_THUNDERBOLT_CONTROLLER_KIND_HOST) {
|
|
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_controller_kind_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", NULL);
|
|
if (tmp != NULL)
|
|
fu_device_set_vendor(device, tmp);
|
|
|
|
if (fu_device_get_version(device) == NULL)
|
|
fu_thunderbolt_controller_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_controller_can_update(self)) {
|
|
const gchar *devpath = fu_udev_device_get_sysfs_path(FU_UDEV_DEVICE(self));
|
|
g_autofree gchar *vendor_id = NULL;
|
|
g_autofree gchar *domain = g_path_get_basename(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->controller_kind == FU_THUNDERBOLT_CONTROLLER_KIND_HOST &&
|
|
self->gen < 4) {
|
|
/* read first block of firmware to get the is-native attribute */
|
|
if (!fu_thunderbolt_controller_read_status_block(self, error))
|
|
return FALSE;
|
|
} else {
|
|
self->is_native = FALSE;
|
|
}
|
|
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(FU_THUNDERBOLT_DEVICE(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) {
|
|
fu_thunderbolt_device_set_auth_method(FU_THUNDERBOLT_DEVICE(self),
|
|
"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);
|
|
}
|
|
if (self->controller_kind == FU_THUNDERBOLT_CONTROLLER_KIND_HOST) {
|
|
g_autoptr(GError) error_local = NULL;
|
|
if (!fu_thunderbolt_controller_setup_usb4(self, &error_local))
|
|
g_warning("failed to setup host: %s", error_local->message);
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_thunderbolt_controller_write_firmware(FuDevice *device,
|
|
FuFirmware *firmware,
|
|
FuProgress *progress,
|
|
FwupdInstallFlags flags,
|
|
GError **error)
|
|
{
|
|
/* FuThunderboltDevice->write_firmware */
|
|
if (!FU_DEVICE_CLASS(fu_thunderbolt_controller_parent_class)
|
|
->write_firmware(device, firmware, progress, flags, error))
|
|
return FALSE;
|
|
fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG);
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
fu_thunderbolt_controller_init(FuThunderboltController *self)
|
|
{
|
|
fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_REQUIRE_AC);
|
|
}
|
|
|
|
static void
|
|
fu_thunderbolt_controller_finalize(GObject *object)
|
|
{
|
|
FuThunderboltController *self = FU_THUNDERBOLT_CONTROLLER(object);
|
|
|
|
if (self->host_online_timer_id != 0)
|
|
g_source_remove(self->host_online_timer_id);
|
|
|
|
G_OBJECT_CLASS(fu_thunderbolt_controller_parent_class)->finalize(object);
|
|
}
|
|
|
|
static void
|
|
fu_thunderbolt_controller_class_init(FuThunderboltControllerClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS(klass);
|
|
FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass);
|
|
object_class->finalize = fu_thunderbolt_controller_finalize;
|
|
klass_device->setup = fu_thunderbolt_controller_setup;
|
|
klass_device->probe = fu_thunderbolt_controller_probe;
|
|
klass_device->to_string = fu_thunderbolt_controller_to_string;
|
|
klass_device->write_firmware = fu_thunderbolt_controller_write_firmware;
|
|
}
|