/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- * * Copyright (C) 2016-2017 Intel Corporation * Copyright (C) 2016 Richard Hughes * * 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 #include #include #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 #include #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; #ifdef HAVE_DELL guint8 dell_supported; struct smbios_struct *de_table; #endif /* HAVE_DELL */ /* 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 de_table = smbios_get_next_struct_by_type (0, 0xDE); smbios_struct_get_data (de_table, &dell_supported, 0x00, sizeof(guint8)); if (dell_supported == 0xDE) { 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, 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; }