fwupd/plugins/thunderbolt/fu-thunderbolt-controller.c
Richard Hughes e03b8c46ed thunderbolt: Split the retimer and controller into two different objects
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.
2022-01-11 20:15:44 +00:00

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