mirror of
https://git.proxmox.com/git/fwupd
synced 2025-07-05 18:44:04 +00:00
493 lines
14 KiB
C
493 lines
14 KiB
C
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
|
|
*
|
|
* Copyright (C) 2016-2017 Intel Corporation <thunderbolt-software@lists.01.org>
|
|
* Copyright (C) 2016 Richard Hughes <richard@hughsie.com>
|
|
*
|
|
* Licensed under the GNU General Public License Version 2
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <appstream-glib.h>
|
|
#include <gudev/gudev.h>
|
|
#include <tbt/tbt_fwu.h>
|
|
|
|
#include "fu-plugin.h"
|
|
#include "fu-plugin-vfuncs.h"
|
|
|
|
#define FU_PLUGIN_THUNDERBOLT_MAX_ID_LEN 255
|
|
#define FU_PLUGIN_THUNDERBOLT_DAEMON_DELAY 3000 /* ms */
|
|
|
|
#ifdef HAVE_DELL
|
|
#include "fu-dell-common.h"
|
|
#include <smbios_c/system_info.h>
|
|
#endif /* HAVE_DELL */
|
|
|
|
struct FuPluginData {
|
|
/* A handle on a list of libtbtfwu controller objects. These must
|
|
* eventually be freed.
|
|
*/
|
|
struct tbt_fwu_Controller **controllers;
|
|
|
|
/* The number of controller objects in controllers and in
|
|
* controller_info.
|
|
*/
|
|
gsize controllers_len;
|
|
|
|
/* the FuThunderboltInfo objects found using the last rescan */
|
|
GPtrArray *infos;
|
|
|
|
/* the array of sysfs paths */
|
|
GPtrArray *devpaths;
|
|
|
|
/* A handle on some state for dealing with our registration
|
|
* for udev events.
|
|
*/
|
|
GUdevClient *gudev_client;
|
|
|
|
/* The idle timeout for refresh */
|
|
guint refresh_id;
|
|
};
|
|
|
|
typedef struct {
|
|
struct tbt_fwu_Controller *controller;
|
|
gchar *id;
|
|
guint16 model_id;
|
|
guint16 vendor_id;
|
|
guint32 version_major;
|
|
guint32 version_minor;
|
|
FuDevice *dev;
|
|
} FuThunderboltInfo;
|
|
|
|
static void
|
|
fu_plugin_thunderbolt_info_free (FuThunderboltInfo *info)
|
|
{
|
|
g_free (info->id);
|
|
if (info->dev != NULL)
|
|
g_object_unref (info->dev);
|
|
g_slice_free (FuThunderboltInfo, info);
|
|
}
|
|
|
|
static FuThunderboltInfo *
|
|
fu_plugin_thunderbolt_get_info_by_id (FuPlugin *plugin, const gchar *id)
|
|
{
|
|
FuPluginData *data = fu_plugin_get_data (plugin);
|
|
for (guint i = 0; i < data->infos->len; i++) {
|
|
FuThunderboltInfo *info = g_ptr_array_index (data->infos, i);
|
|
if (g_strcmp0 (info->id, id) == 0)
|
|
return info;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static gboolean
|
|
fu_plugin_thunderbolt_rescan (FuPlugin *plugin, GError **error)
|
|
{
|
|
FuPluginData *data = fu_plugin_get_data (plugin);
|
|
gint rc;
|
|
g_autoptr(GPtrArray) infos_remove = NULL;
|
|
|
|
/* get the new list */
|
|
if (data->controllers != NULL) {
|
|
tbt_fwu_freeControllerList (data->controllers,
|
|
data->controllers_len);
|
|
}
|
|
data->controllers = NULL;
|
|
rc = tbt_fwu_getControllerList (&data->controllers,
|
|
&data->controllers_len);
|
|
if (rc != TBT_OK) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"failed to retrieve TBT controller list: %s",
|
|
tbt_strerror (rc));
|
|
return FALSE;
|
|
}
|
|
g_debug ("found %" G_GSIZE_FORMAT " thunderbolt controllers",
|
|
data->controllers_len);
|
|
|
|
/* no longer valid */
|
|
for (guint i = 0; i < data->infos->len; i++) {
|
|
FuThunderboltInfo *info = g_ptr_array_index (data->infos, i);
|
|
info->controller = NULL;
|
|
}
|
|
|
|
/* go through each device in results */
|
|
for (guint i = 0; i < data->controllers_len; i++) {
|
|
FuThunderboltInfo *info;
|
|
gchar tdbid[FU_PLUGIN_THUNDERBOLT_MAX_ID_LEN];
|
|
gsize tdbid_sz = sizeof (tdbid);
|
|
g_autofree gchar *guid_id = NULL;
|
|
g_autofree gchar *version = NULL;
|
|
gint safe_mode = 0;
|
|
|
|
/* get the ID */
|
|
rc = tbt_fwu_Controller_getID (data->controllers[i],
|
|
tdbid, &tdbid_sz);
|
|
if (rc != TBT_OK) {
|
|
g_warning ("failed to get tbd ID: %s",
|
|
tbt_strerror (rc));
|
|
continue;
|
|
}
|
|
|
|
/* find any existing info struct */
|
|
info = fu_plugin_thunderbolt_get_info_by_id (plugin, tdbid);
|
|
if (info != NULL) {
|
|
info->controller = data->controllers[i];
|
|
continue;
|
|
}
|
|
|
|
/* create a new info struct */
|
|
info = g_slice_new0 (FuThunderboltInfo);
|
|
info->controller = data->controllers[i];
|
|
info->id = g_strdup (tdbid);
|
|
g_ptr_array_add (data->infos, info);
|
|
|
|
rc = tbt_fwu_Controller_isInSafeMode (data->controllers[i], &safe_mode);
|
|
if (rc != TBT_OK) {
|
|
g_warning ("failed to get controller status: %s",
|
|
tbt_strerror (rc));
|
|
continue;
|
|
}
|
|
|
|
if (safe_mode != 0) {
|
|
info->vendor_id = 0;
|
|
info->model_id = 0;
|
|
info->version_major = 0;
|
|
info->version_minor = 0;
|
|
g_warning ("Thunderbolt controller %s is in Safe Mode. "
|
|
"Please visit https://github.com/01org/tbtfwupd/wiki "
|
|
"for information on how to restore normal operation.",
|
|
info->id);
|
|
|
|
/* Dell systems are known to have the system ID as the model_id
|
|
when in safe mode, they can be flashed */
|
|
#ifdef HAVE_DELL
|
|
if (fu_dell_supported ()) {
|
|
info->vendor_id = 0x00d4;
|
|
info->model_id = sysinfo_get_dell_system_id ();
|
|
safe_mode = 0;
|
|
}
|
|
#endif /* HAVE_DELL */
|
|
} else {
|
|
/* get the vendor ID */
|
|
rc = tbt_fwu_Controller_getVendorID (data->controllers[i],
|
|
&info->vendor_id);
|
|
if (rc != TBT_OK) {
|
|
g_warning ("failed to get tbd vendor ID: %s",
|
|
tbt_strerror (rc));
|
|
continue;
|
|
}
|
|
|
|
/* get the model ID */
|
|
rc = tbt_fwu_Controller_getModelID (data->controllers[i],
|
|
&info->model_id);
|
|
if (rc != TBT_OK) {
|
|
g_warning ("failed to get tbd model ID: %s",
|
|
tbt_strerror (rc));
|
|
continue;
|
|
}
|
|
|
|
/* get the controller info */
|
|
rc = tbt_fwu_Controller_getNVMVersion (data->controllers[i],
|
|
&info->version_major,
|
|
&info->version_minor);
|
|
if (rc != TBT_OK) {
|
|
g_warning ("failed to get tbd firmware version: %s",
|
|
tbt_strerror (rc));
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* add FuDevice attributes */
|
|
info->dev = fu_device_new ();
|
|
fu_device_set_vendor (info->dev, "Intel");
|
|
fu_device_set_name (info->dev, "Thunderbolt Controller");
|
|
fu_device_add_flag (info->dev, FWUPD_DEVICE_FLAG_INTERNAL);
|
|
if (safe_mode == 0)
|
|
fu_device_add_flag (info->dev, FWUPD_DEVICE_FLAG_ALLOW_ONLINE);
|
|
fu_device_set_id (info->dev, info->id);
|
|
|
|
/* add GUID that the info firmware uses */
|
|
guid_id = g_strdup_printf ("TBT-%04x%04x",
|
|
info->vendor_id, info->model_id);
|
|
fu_device_add_guid (info->dev, guid_id);
|
|
|
|
/* format version */
|
|
version = g_strdup_printf ("%" G_GINT32_MODIFIER "x.%02" G_GINT32_MODIFIER "x",
|
|
info->version_major,
|
|
info->version_minor);
|
|
fu_device_set_version (info->dev, version);
|
|
|
|
/* add to daemon */
|
|
fu_plugin_device_add (plugin, info->dev);
|
|
}
|
|
|
|
/* any devices were removed */
|
|
infos_remove = g_ptr_array_new ();
|
|
for (guint i = 0; i < data->infos->len; i++) {
|
|
FuThunderboltInfo *info = g_ptr_array_index (data->infos, i);
|
|
if (info->controller == NULL) {
|
|
if (info->dev != NULL)
|
|
fu_plugin_device_remove (plugin, info->dev);
|
|
g_ptr_array_add (infos_remove, info);
|
|
}
|
|
}
|
|
for (guint i = 0; i < infos_remove->len; i++) {
|
|
FuThunderboltInfo *info = g_ptr_array_index (infos_remove, i);
|
|
g_ptr_array_remove (data->infos, info);
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_plugin_thunderbolt_schedule_rescan_cb (gpointer user_data)
|
|
{
|
|
FuPlugin *plugin = FU_PLUGIN (user_data);
|
|
FuPluginData *data = fu_plugin_get_data (plugin);
|
|
g_autoptr(GError) error = NULL;
|
|
|
|
/* no longer valid */
|
|
data->refresh_id = 0;
|
|
|
|
/* rescan */
|
|
if (!fu_plugin_thunderbolt_rescan (plugin, &error))
|
|
g_warning ("%s", error->message);
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
fu_plugin_thunderbolt_schedule_rescan (FuPlugin *plugin)
|
|
{
|
|
FuPluginData *data = fu_plugin_get_data (plugin);
|
|
|
|
/* this delay is a work-around for potential race conditions in which
|
|
* the udev event arrives to fwupd prior to the daemon refreshing */
|
|
if (data->refresh_id != 0)
|
|
g_source_remove (data->refresh_id);
|
|
data->refresh_id = g_timeout_add (FU_PLUGIN_THUNDERBOLT_DAEMON_DELAY,
|
|
fu_plugin_thunderbolt_schedule_rescan_cb,
|
|
plugin);
|
|
}
|
|
|
|
static gboolean
|
|
fu_plugin_thunderbolt_device_matches (GUdevDevice *device)
|
|
{
|
|
guint16 device_id;
|
|
guint16 vendor_id;
|
|
|
|
/* check vendor ID */
|
|
vendor_id = g_udev_device_get_sysfs_attr_as_int (device, "vendor");
|
|
if (vendor_id != 0x8086)
|
|
return FALSE;
|
|
|
|
/* check device ID */
|
|
device_id = g_udev_device_get_sysfs_attr_as_int (device, "device");
|
|
if (device_id != 0x1577 &&
|
|
device_id != 0x1575 &&
|
|
device_id != 0x15BF &&
|
|
device_id != 0x15D2 &&
|
|
device_id != 0x15D9)
|
|
return FALSE;
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
fu_plugin_thunderbolt_percentage_changed_cb (guint percentage, gpointer user_data)
|
|
{
|
|
FuPlugin *plugin = FU_PLUGIN (user_data);
|
|
fu_plugin_set_percentage (plugin, percentage);
|
|
}
|
|
|
|
gboolean
|
|
fu_plugin_update_online (FuPlugin *plugin,
|
|
FuDevice *dev,
|
|
GBytes *blob_fw,
|
|
FwupdInstallFlags flags,
|
|
GError **error)
|
|
{
|
|
FuThunderboltInfo *info;
|
|
const guint8 *blob;
|
|
gint rc;
|
|
gsize blob_sz;
|
|
|
|
/* find controller */
|
|
info = fu_plugin_thunderbolt_get_info_by_id (plugin, fu_device_get_id (dev));
|
|
if (info == NULL) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_FOUND,
|
|
"no TBT device with ID %s found",
|
|
fu_device_get_id (dev));
|
|
return FALSE;
|
|
}
|
|
|
|
/* validate the image */
|
|
blob = (const guint8 *) g_bytes_get_data (blob_fw, &blob_sz);
|
|
rc = tbt_fwu_Controller_validateFWImage (info->controller, blob, blob_sz);
|
|
if (rc != TBT_OK) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"TBT firmware validation failed: %s",
|
|
tbt_strerror (rc));
|
|
return FALSE;
|
|
}
|
|
|
|
/* update the device */
|
|
rc = tbt_fwu_Controller_updateFW (info->controller,
|
|
blob,
|
|
blob_sz,
|
|
fu_plugin_thunderbolt_percentage_changed_cb,
|
|
plugin);
|
|
if (rc != TBT_OK) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"TBT firmware update failed: %s",
|
|
tbt_strerror (rc));
|
|
return FALSE;
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static const gchar *
|
|
fu_plugin_thunderbolt_find_devpath (FuPlugin *plugin, GUdevDevice *udev_device)
|
|
{
|
|
FuPluginData *data = fu_plugin_get_data (plugin);
|
|
const gchar *devpath = g_udev_device_get_sysfs_path (udev_device);
|
|
for (guint i = 0; i < data->devpaths->len; i++) {
|
|
const gchar *devpath_tmp = g_ptr_array_index (data->devpaths, i);
|
|
if (g_strcmp0 (devpath_tmp, devpath) == 0)
|
|
return devpath_tmp;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
fu_plugin_thunderbolt_add_devpath (FuPlugin *plugin, GUdevDevice *udev_device)
|
|
{
|
|
FuPluginData *data = fu_plugin_get_data (plugin);
|
|
|
|
/* already exists */
|
|
if (fu_plugin_thunderbolt_find_devpath (plugin, udev_device) != NULL)
|
|
return;
|
|
|
|
/* add new sysfs-path */
|
|
g_ptr_array_add (data->devpaths,
|
|
g_strdup (g_udev_device_get_sysfs_path (udev_device)));
|
|
}
|
|
|
|
static void
|
|
fu_plugin_thunderbolt_uevent_cb (GUdevClient *gudev_client,
|
|
const gchar *action,
|
|
GUdevDevice *udev_device,
|
|
FuPlugin *plugin)
|
|
{
|
|
FuPluginData *data = fu_plugin_get_data (plugin);
|
|
if (g_strcmp0 (action, "remove") == 0) {
|
|
const gchar *devpath = NULL;
|
|
devpath = fu_plugin_thunderbolt_find_devpath (plugin, udev_device);
|
|
if (devpath != NULL) {
|
|
g_debug ("potentially removing tbt device");
|
|
g_ptr_array_remove (data->devpaths, (gpointer) devpath);
|
|
fu_plugin_thunderbolt_schedule_rescan (plugin);
|
|
}
|
|
return;
|
|
}
|
|
if (g_strcmp0 (action, "add") == 0) {
|
|
if (fu_plugin_thunderbolt_device_matches (udev_device)) {
|
|
g_debug ("potentially adding tbt device");
|
|
fu_plugin_thunderbolt_add_devpath (plugin, udev_device);
|
|
fu_plugin_thunderbolt_schedule_rescan (plugin);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
void
|
|
fu_plugin_init (FuPlugin *plugin)
|
|
{
|
|
FuPluginData *data = fu_plugin_alloc_data (plugin, sizeof (FuPluginData));
|
|
const gchar *subsystems[] = { "pci", NULL };
|
|
data->infos = g_ptr_array_new_with_free_func ((GDestroyNotify) fu_plugin_thunderbolt_info_free);
|
|
data->devpaths = g_ptr_array_new_with_free_func (g_free);
|
|
data->gudev_client = g_udev_client_new (subsystems);
|
|
g_signal_connect (data->gudev_client, "uevent",
|
|
G_CALLBACK (fu_plugin_thunderbolt_uevent_cb), plugin);
|
|
}
|
|
|
|
void
|
|
fu_plugin_destroy (FuPlugin *plugin)
|
|
{
|
|
FuPluginData *data = fu_plugin_get_data (plugin);
|
|
if (data->refresh_id != 0)
|
|
g_source_remove (data->refresh_id);
|
|
g_ptr_array_unref (data->infos);
|
|
g_ptr_array_unref (data->devpaths);
|
|
tbt_fwu_freeControllerList (data->controllers, data->controllers_len);
|
|
tbt_fwu_shutdown ();
|
|
g_object_unref (data->gudev_client);
|
|
}
|
|
|
|
gboolean
|
|
fu_plugin_startup (FuPlugin *plugin, GError **error)
|
|
{
|
|
gint rc = tbt_fwu_init ();
|
|
if (rc != TBT_OK) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"TBT initialization failed: %s",
|
|
tbt_strerror (rc));
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
fu_plugin_coldplug (FuPlugin *plugin, GError **error)
|
|
{
|
|
FuPluginData *data = fu_plugin_get_data (plugin);
|
|
gboolean found = FALSE;
|
|
g_autoptr(GList) devices = NULL;
|
|
|
|
/* get all devices of class */
|
|
devices = g_udev_client_query_by_subsystem (data->gudev_client, "pci");
|
|
for (GList *l = devices; l != NULL; l = l->next) {
|
|
GUdevDevice *udev_device = l->data;
|
|
if (fu_plugin_thunderbolt_device_matches (udev_device)) {
|
|
fu_plugin_thunderbolt_add_devpath (plugin, udev_device);
|
|
found = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
if (found) {
|
|
g_debug ("found thunderbolt PCI device on coldplug");
|
|
if (!fu_plugin_thunderbolt_rescan (plugin, error))
|
|
return FALSE;
|
|
}
|
|
g_list_foreach (devices, (GFunc) g_object_unref, NULL);
|
|
return TRUE;
|
|
}
|