/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- * * Copyright (C) 2016 Richard Hughes * Copyright (C) 2016 Mario Limonciello * * 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 #include #include #include #include "fu-plugin-dell.h" #include "fu-plugin-vfuncs.h" #include "fu-device-metadata.h" /* These are used to indicate the status of a previous DELL flash */ #define DELL_SUCCESS 0x0000 #define DELL_CONSISTENCY_FAIL 0x0001 #define DELL_FLASH_MEMORY_FAIL 0x0002 #define DELL_FLASH_NOT_READY 0x0003 #define DELL_FLASH_DISABLED 0x0004 #define DELL_BATTERY_MISSING 0x0005 #define DELL_BATTERY_DEAD 0x0006 #define DELL_AC_MISSING 0x0007 #define DELL_CANT_SET_12V 0x0008 #define DELL_CANT_UNSET_12V 0x0009 #define DELL_FAILURE_BLOCK_ERASE 0x000A #define DELL_GENERAL_FAILURE 0x000B #define DELL_DATA_MISCOMPARE 0x000C #define DELL_IMAGE_MISSING 0x000D #define DELL_DID_NOTHING 0xFFFF /* Delay for settling */ #define DELL_FLASH_MODE_DELAY 2 typedef struct _DOCK_DESCRIPTION { const gchar * guid; const gchar * query; const gchar * desc; } DOCK_DESCRIPTION; /* These are for matching the components */ #define WD15_EC_STR "2 0 2 2 0" #define TB16_EC_STR "2 0 2 1 0" #define TB16_PC2_STR "2 1 0 1 1" #define TB16_PC1_STR "2 1 0 1 0" #define WD15_PC1_STR "2 1 0 2 0" #define LEGACY_CBL_STR "2 2 2 1 0" #define UNIV_CBL_STR "2 2 2 2 0" #define TBT_CBL_STR "2 2 2 3 0" /* supported dock related GUIDs */ #define DOCK_FLASH_GUID "e7ca1f36-bf73-4574-afe6-a4ccacabf479" #define WD15_EC_GUID "e8445370-0211-449d-9faa-107906ab189f" #define TB16_EC_GUID "33cc8870-b1fc-4ec7-948a-c07496874faf" #define TB16_PC2_GUID "1b52c630-86f6-4aee-9f0c-474dc6be49b6" #define TB16_PC1_GUID "8fe183da-c94e-4804-b319-0f1ba5457a69" #define WD15_PC1_GUID "8ba2b709-6f97-47fc-b7e7-6a87b578fe25" #define LEGACY_CBL_GUID "fece1537-d683-4ea8-b968-154530bb6f73" #define UNIV_CBL_GUID "e2bf3aad-61a3-44bf-91ef-349b39515d29" #define TBT_CBL_GUID "6dc832fc-5bb0-4e63-a2ff-02aaba5bc1dc" #define EC_DESC "EC" #define PC1_DESC "Port Controller 1" #define PC2_DESC "Port Controller 2" #define LEGACY_CBL_DESC "Passive Cable" #define UNIV_CBL_DESC "Universal Cable" #define TBT_CBL_DESC "Thunderbolt Cable" /* supported host related GUIDs */ #define MST_GPIO_GUID EFI_GUID (0xF24F9bE4, 0x2a13, 0x4344, 0xBC05, 0x01, 0xCE, 0xF7, 0xDA, 0xEF, 0x92) /** * Devices that should allow modeswitching */ static guint16 tpm_switch_whitelist[] = {0x06F2, 0x06F3, 0x06DD, 0x06DE, 0x06DF, 0x06DB, 0x06DC, 0x06BB, 0x06C6, 0x06BA, 0x06B9, 0x05CA, 0x06C7, 0x06B7, 0x06E0, 0x06E5, 0x06D9, 0x06DA, 0x06E4, 0x0704, 0x0720, 0x0730, 0x0758, 0x0759, 0x075B, 0x07A0, 0x079F, 0x07A4, 0x07A5, 0x07A6, 0x07A7, 0x07A8, 0x07A9, 0x07AA, 0x07AB, 0x07B0, 0x07B1, 0x07B2, 0x07B4, 0x07B7, 0x07B8, 0x07B9, 0x07BE, 0x07BF, 0x077A, 0x07CF}; /** * Dell device types to run */ static guint8 enclosure_whitelist [] = { 0x03, /* desktop */ 0x04, /* low profile desktop */ 0x06, /* mini tower */ 0x07, /* tower */ 0x08, /* portable */ 0x09, /* laptop */ 0x0A, /* notebook */ 0x0D, /* AIO */ 0x1E, /* tablet */ 0x1F, /* convertible */ 0x21, /* IoT gateway */ 0x22, /* embedded PC */}; /** * System blacklist on older libsmbios */ static guint16 system_blacklist [] = { 0x071E, /* latitude 5414 */ 0x07A8, /* latitude 5580 */ 0x077A, /* xps 9365 */ }; /** * Systems containing host MST device */ static guint16 systems_host_mst [] = { 0x062d, /* Latitude E7250 */ 0x062e, /* Latitude E7450 */ 0x062a, /* Latitude E5250 */ 0x062b, /* Latitude E5450 */ 0x062c, /* Latitude E5550 */ 0x06db, /* Latitude E7270 */ 0x06dc, /* Latitude E7470 */ 0x06dd, /* Latitude E5270 */ 0x06de, /* Latitude E5470 */ 0x06df, /* Latitude E5570 */ 0x06e0, /* Precision 3510 */ 0x071d, /* Latitude Rugged 7214 */ 0x071e, /* Latitude Rugged 5414 */ 0x071c, /* Latitude Rugged 7414 */}; static void _fwup_resource_iter_free (fwup_resource_iter *iter) { fwup_resource_iter_destroy (&iter); } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC (fwup_resource_iter, _fwup_resource_iter_free); #pragma clang diagnostic pop static guint16 fu_dell_get_system_id (FuPlugin *plugin) { const gchar *system_id_str = NULL; guint16 system_id = 0; gchar *endptr = NULL; system_id_str = fu_plugin_get_dmi_value (plugin, FU_HWIDS_KEY_PRODUCT_SKU); if (system_id_str != NULL) system_id = g_ascii_strtoull (system_id_str, &endptr, 16); if (system_id == 0 || endptr == system_id_str) system_id = (guint16) sysinfo_get_dell_system_id (); return system_id; } static gboolean fu_dell_host_mst_supported (FuPlugin *plugin) { guint16 system_id; system_id = fu_dell_get_system_id (plugin); if (system_id == 0) return FALSE; for (guint i = 0; i < G_N_ELEMENTS (systems_host_mst); i++) if (systems_host_mst[i] == system_id) return TRUE; return FALSE; } static gboolean fu_dell_supported (FuPlugin *plugin) { FuPluginData *data = fu_plugin_get_data (plugin); GBytes *de_table = NULL; GBytes *enclosure = NULL; guint16 system_id = 0; const guint8 *value; gsize len; /* make sure that Dell SMBIOS methods are available */ de_table = fu_plugin_get_smbios_data (plugin, 0xDE); if (de_table == NULL) return FALSE; value = g_bytes_get_data (de_table, &len); if (len == 0) return FALSE; if (*value != 0xDE) return FALSE; /* skip blacklisted hw on libsmbios 2.3 or less */ if (data->libsmbios_major <= 2 && data->libsmbios_minor <= 3) { system_id = fu_dell_get_system_id (plugin); if (system_id == 0) return FALSE; for (guint i = 0; i < G_N_ELEMENTS (system_blacklist); i++) { if (system_blacklist[i] == system_id) return FALSE; } } /* only run on intended Dell hw types */ enclosure = fu_plugin_get_smbios_data (plugin, FU_SMBIOS_STRUCTURE_TYPE_CHASSIS); if (enclosure == NULL) return FALSE; value = g_bytes_get_data (enclosure, &len); if (len == 0) return FALSE; for (guint i = 0; i < G_N_ELEMENTS (enclosure_whitelist); i++) { if (enclosure_whitelist[i] == value[0]) return TRUE; } return FALSE; } static gboolean fu_plugin_dell_match_dock_component (const gchar *query_str, const gchar **guid_out, const gchar **name_out) { const DOCK_DESCRIPTION list[] = { {WD15_EC_GUID, WD15_EC_STR, EC_DESC}, {TB16_EC_GUID, TB16_EC_STR, EC_DESC}, {WD15_PC1_GUID, WD15_PC1_STR, PC1_DESC}, {TB16_PC1_GUID, TB16_PC1_STR, PC1_DESC}, {TB16_PC2_GUID, TB16_PC2_STR, PC2_DESC}, {TBT_CBL_GUID, TBT_CBL_STR, TBT_CBL_DESC}, {UNIV_CBL_GUID, UNIV_CBL_STR, UNIV_CBL_DESC}, {LEGACY_CBL_GUID, LEGACY_CBL_STR, LEGACY_CBL_DESC}, }; for (guint i = 0; i < G_N_ELEMENTS (list); i++) { if (g_strcmp0 (query_str, list[i].query) == 0) { *guid_out = list[i].guid; *name_out = list[i].desc; return TRUE; } } return FALSE; } void fu_plugin_dell_inject_fake_data (FuPlugin *plugin, guint32 *output, guint16 vid, guint16 pid, guint8 *buf, gboolean can_switch_modes) { FuPluginData *data = fu_plugin_get_data (plugin); if (!data->smi_obj->fake_smbios) return; for (guint i = 0; i < 4; i++) data->smi_obj->output[i] = output[i]; data->fake_vid = vid; data->fake_pid = pid; data->smi_obj->fake_buffer = buf; data->can_switch_modes = TRUE; } static AsVersionParseFlag fu_plugin_dell_get_version_format (FuPlugin *plugin) { const gchar *content; const gchar *quirk; content = fu_plugin_get_dmi_value (plugin, FU_HWIDS_KEY_MANUFACTURER); if (content == NULL) return AS_VERSION_PARSE_FLAG_USE_TRIPLET; /* any quirks match */ quirk = fu_plugin_lookup_quirk_by_id (plugin, FU_QUIRKS_UEFI_VERSION_FORMAT, content); if (g_strcmp0 (quirk, "none") == 0) return AS_VERSION_PARSE_FLAG_NONE; /* fall back */ return AS_VERSION_PARSE_FLAG_USE_TRIPLET; } static gchar * fu_plugin_get_dock_key (FuPlugin *plugin, GUsbDevice *device, const gchar *guid) { FuPluginData *data = fu_plugin_get_data (plugin); const gchar* platform_id; if (data->smi_obj->fake_smbios) platform_id = "fake"; else platform_id = g_usb_device_get_platform_id (device); return g_strdup_printf ("%s_%s", platform_id, guid); } static gboolean fu_plugin_dell_capsule_supported (FuPlugin *plugin) { FuPluginData *data = fu_plugin_get_data (plugin); return data->smi_obj->fake_smbios || data->capsule_supported; } static gboolean fu_plugin_dock_node (FuPlugin *plugin, GUsbDevice *device, guint8 type, const gchar *component_guid, const gchar *component_desc, const gchar *version) { const gchar *dock_type; g_autofree gchar *dock_id = NULL; g_autofree gchar *dock_key = NULL; g_autofree gchar *dock_name = NULL; g_autoptr(FuDevice) dev = NULL; dock_type = fu_dell_get_dock_type (type); if (dock_type == NULL) { g_debug ("Unknown dock type %d", type); return FALSE; } dock_key = fu_plugin_get_dock_key (plugin, device, component_guid); if (fu_plugin_cache_lookup (plugin, dock_key) != NULL) { g_debug ("%s is already registered.", dock_key); return FALSE; } dev = fu_device_new (); dock_id = g_strdup_printf ("DELL-%s" G_GUINT64_FORMAT, component_guid); if (component_desc != NULL) { dock_name = g_strdup_printf ("Dell %s %s", dock_type, component_desc); fu_device_add_parent_guid (dev, DOCK_FLASH_GUID); } else { dock_name = g_strdup_printf ("Dell %s", dock_type); } fu_device_set_id (dev, dock_id); fu_device_set_vendor (dev, "Dell Inc."); fu_device_set_name (dev, dock_name); fu_device_set_metadata (dev, FU_DEVICE_METADATA_DELL_DOCK_TYPE, dock_type); if (type == DOCK_TYPE_TB16) { fu_device_set_summary (dev, "A Thunderboltâ„¢ 3 docking station"); } else if (type == DOCK_TYPE_WD15) { fu_device_set_summary (dev, "A USB type-C docking station"); } fu_device_add_icon (dev, "computer"); fu_device_add_guid (dev, component_guid); fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_REQUIRE_AC); if (version != NULL) { fu_device_set_version (dev, version); if (fu_plugin_dell_capsule_supported (plugin)) { fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_NEEDS_REBOOT); } } fu_plugin_device_add (plugin, dev); fu_plugin_cache_add (plugin, dock_key, dev); return TRUE; } void fu_plugin_dell_device_added_cb (GUsbContext *ctx, GUsbDevice *device, FuPlugin *plugin) { FuPluginData *data = fu_plugin_get_data (plugin); AsVersionParseFlag parse_flags; guint16 pid; guint16 vid; const gchar *query_str; const gchar *component_guid = NULL; const gchar *component_name = NULL; DOCK_UNION buf; DOCK_INFO *dock_info; gboolean old_ec = FALSE; g_autofree gchar *flash_ver_str = NULL; /* don't look up immediately if a dock is connected as that would mean a SMI on every USB device that showed up on the system */ if (!data->smi_obj->fake_smbios) { vid = g_usb_device_get_vid (device); pid = g_usb_device_get_pid (device); } else { vid = data->fake_vid; pid = data->fake_pid; } /* we're going to match on the Realtek NIC in the dock */ if (vid != DOCK_NIC_VID || pid != DOCK_NIC_PID) return; buf.buf = NULL; if (!fu_dell_query_dock (data->smi_obj, &buf)) { g_debug ("No dock detected."); return; } if (buf.record->dock_info_header.dir_version != 1) { g_debug ("Dock info header version unknown: %d", buf.record->dock_info_header.dir_version); return; } dock_info = &buf.record->dock_info; g_debug ("Dock description: %s", dock_info->dock_description); /* Note: fw package version is deprecated, look at components instead */ g_debug ("Dock flash pkg ver: 0x%x", dock_info->flash_pkg_version); if (dock_info->flash_pkg_version == 0x00ffffff) g_debug ("WARNING: dock flash package version invalid"); g_debug ("Dock cable type: %" G_GUINT32_FORMAT, dock_info->cable_type); g_debug ("Dock location: %d", dock_info->location); g_debug ("Dock component count: %d", dock_info->component_count); parse_flags = fu_plugin_dell_get_version_format (plugin); for (guint i = 0; i < dock_info->component_count; i++) { g_autofree gchar *fw_str = NULL; if (i >= MAX_COMPONENTS) { g_debug ("Too many components. Invalid: #%u", i); break; } g_debug ("Dock component %u: %s (version 0x%x)", i, dock_info->components[i].description, dock_info->components[i].fw_version); query_str = g_strrstr (dock_info->components[i].description, "Query "); if (query_str == NULL) { g_debug ("Invalid dock component request"); return; } if (!fu_plugin_dell_match_dock_component (query_str + 6, &component_guid, &component_name)) { g_debug ("Unable to match dock component %s", query_str); return; } /* dock EC hasn't been updated for first time */ if (dock_info->flash_pkg_version == 0x00ffffff) { old_ec = TRUE; dock_info->flash_pkg_version = 0; continue; } /* if invalid version, don't mark device for updates */ else if (dock_info->components[i].fw_version == 0 || dock_info->components[i].fw_version == 0xffffffff) { old_ec = TRUE; continue; } fw_str = as_utils_version_from_uint32 (dock_info->components[i].fw_version, parse_flags); if (!fu_plugin_dock_node (plugin, device, buf.record->dock_info_header.dock_type, component_guid, component_name, fw_str)) { g_debug ("Failed to create %s", component_name); return; } } /* if an old EC or invalid EC version found, create updatable parent */ if (old_ec) flash_ver_str = as_utils_version_from_uint32 (dock_info->flash_pkg_version, parse_flags); if (!fu_plugin_dock_node (plugin, device, buf.record->dock_info_header.dock_type, DOCK_FLASH_GUID, NULL, flash_ver_str)) { g_debug ("Failed to create top dock node"); return; } #if defined (HAVE_SYNAPTICS) fu_plugin_request_recoldplug (plugin); #endif } void fu_plugin_dell_device_removed_cb (GUsbContext *ctx, GUsbDevice *device, FuPlugin *plugin) { FuPluginData *data = fu_plugin_get_data (plugin); const gchar *guids[] = { WD15_EC_GUID, TB16_EC_GUID, TB16_PC2_GUID, TB16_PC1_GUID, WD15_PC1_GUID, LEGACY_CBL_GUID, UNIV_CBL_GUID, TBT_CBL_GUID, DOCK_FLASH_GUID}; guint16 pid; guint16 vid; FuDevice *dev = NULL; if (!data->smi_obj->fake_smbios) { vid = g_usb_device_get_vid (device); pid = g_usb_device_get_pid (device); } else { vid = data->fake_vid; pid = data->fake_pid; } /* we're going to match on the Realtek NIC in the dock */ if (vid != DOCK_NIC_VID || pid != DOCK_NIC_PID) return; /* remove any components already in database? */ for (guint i = 0; i < G_N_ELEMENTS (guids); i++) { g_autofree gchar *dock_key = NULL; dock_key = fu_plugin_get_dock_key (plugin, device, guids[i]); dev = fu_plugin_cache_lookup (plugin, dock_key); if (dev != NULL) { fu_plugin_device_remove (plugin, dev); fu_plugin_cache_remove (plugin, dock_key); } } #if defined (HAVE_SYNAPTICS) fu_plugin_request_recoldplug (plugin); #endif } gboolean fu_plugin_get_results (FuPlugin *plugin, FuDevice *device, GError **error) { GBytes *de_table = NULL; const gchar *tmp = NULL; const guint16 *completion_code; gsize len; de_table = fu_plugin_get_smbios_data (plugin, 0xDE); completion_code = g_bytes_get_data (de_table, &len); if (len < 8) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "ERROR: Unable to read results of %s: %" G_GSIZE_FORMAT " < 8", fu_device_get_name (device), len); return FALSE; } /* look at byte offset 0x06 for identifier meaning completion code */ if (completion_code[3] == DELL_SUCCESS) { fu_device_set_update_state (device, FWUPD_UPDATE_STATE_SUCCESS); } else { fu_device_set_update_state (device, FWUPD_UPDATE_STATE_FAILED); switch (completion_code[3]) { case DELL_CONSISTENCY_FAIL: tmp = "The image failed one or more consistency checks."; break; case DELL_FLASH_MEMORY_FAIL: tmp = "The BIOS could not access the flash-memory device."; break; case DELL_FLASH_NOT_READY: tmp = "The flash-memory device was not ready when an erase was attempted."; break; case DELL_FLASH_DISABLED: tmp = "Flash programming is currently disabled on the system, or the voltage is low."; break; case DELL_BATTERY_MISSING: tmp = "A battery must be installed for the operation to complete."; break; case DELL_BATTERY_DEAD: tmp = "A fully-charged battery must be present for the operation to complete."; break; case DELL_AC_MISSING: tmp = "An external power adapter must be connected for the operation to complete."; break; case DELL_CANT_SET_12V: tmp = "The 12V required to program the flash-memory could not be set."; break; case DELL_CANT_UNSET_12V: tmp = "The 12V required to program the flash-memory could not be removed."; break; case DELL_FAILURE_BLOCK_ERASE : tmp = "A flash-memory failure occurred during a block-erase operation."; break; case DELL_GENERAL_FAILURE: tmp = "A general failure occurred during the flash programming."; break; case DELL_DATA_MISCOMPARE: tmp = "A data miscompare error occurred during the flash programming."; break; case DELL_IMAGE_MISSING: tmp = "The image could not be found in memory, i.e. the header could not be located."; break; case DELL_DID_NOTHING: tmp = "No update operation has been performed on the system."; break; default: break; } if (tmp != NULL) fu_device_set_update_error (device, tmp); } return TRUE; } gboolean fu_plugin_dell_detect_tpm (FuPlugin *plugin, GError **error) { FuPluginData *data = fu_plugin_get_data (plugin); const gchar *tpm_mode; const gchar *tpm_mode_alt; guint16 system_id = 0; gboolean can_switch_modes = FALSE; g_autofree gchar *pretty_tpm_name_alt = NULL; g_autofree gchar *pretty_tpm_name = NULL; g_autofree gchar *tpm_guid_raw_alt = NULL; g_autofree gchar *tpm_guid_alt = NULL; g_autofree gchar *tpm_guid = NULL; g_autofree gchar *tpm_guid_raw = NULL; g_autofree gchar *tpm_id_alt = NULL; g_autofree gchar *tpm_id = NULL; g_autofree gchar *version_str = NULL; struct tpm_status *out = NULL; g_autoptr (FuDevice) dev_alt = NULL; g_autoptr (FuDevice) dev = NULL; const gchar *product_name = NULL; fu_dell_clear_smi (data->smi_obj); out = (struct tpm_status *) data->smi_obj->output; /* execute TPM Status Query */ data->smi_obj->input[0] = DACI_FLASH_ARG_TPM; if (!fu_dell_execute_simple_smi (data->smi_obj, DACI_FLASH_INTERFACE_CLASS, DACI_FLASH_INTERFACE_SELECT)) return FALSE; if (out->ret != 0) { g_debug ("Failed to query system for TPM information: " "(%" G_GUINT32_FORMAT ")", out->ret); return FALSE; } /* HW version is output in second /input/ arg * it may be relevant as next gen TPM is enabled */ g_debug ("TPM HW version: 0x%x", data->smi_obj->input[1]); g_debug ("TPM Status: 0x%x", out->status); /* test TPM enabled (Bit 0) */ if (!(out->status & TPM_EN_MASK)) { g_debug ("TPM not enabled (%x)", out->status); return FALSE; } /* test TPM mode to determine current mode */ if (((out->status & TPM_TYPE_MASK) >> 8) == TPM_1_2_MODE) { tpm_mode = "1.2"; tpm_mode_alt = "2.0"; } else if (((out->status & TPM_TYPE_MASK) >> 8) == TPM_2_0_MODE) { tpm_mode = "2.0"; tpm_mode_alt = "1.2"; } else { g_debug ("Unable to determine TPM mode"); return FALSE; } system_id = fu_dell_get_system_id (plugin); if (data->smi_obj->fake_smbios) can_switch_modes = data->can_switch_modes; else if (system_id == 0) return FALSE; for (guint i = 0; i < G_N_ELEMENTS (tpm_switch_whitelist); i++) { if (tpm_switch_whitelist[i] == system_id) { can_switch_modes = TRUE; } } tpm_guid_raw = g_strdup_printf ("%04x-%s", system_id, tpm_mode); tpm_guid = as_utils_guid_from_string (tpm_guid_raw); tpm_id = g_strdup_printf ("DELL-%s" G_GUINT64_FORMAT, tpm_guid); tpm_guid_raw_alt = g_strdup_printf ("%04x-%s", system_id, tpm_mode_alt); tpm_guid_alt = as_utils_guid_from_string (tpm_guid_raw_alt); tpm_id_alt = g_strdup_printf ("DELL-%s" G_GUINT64_FORMAT, tpm_guid_alt); g_debug ("Creating primary TPM GUID %s and secondary TPM GUID %s", tpm_guid_raw, tpm_guid_raw_alt); version_str = as_utils_version_from_uint32 (out->fw_version, AS_VERSION_PARSE_FLAG_NONE); /* make it clear that the TPM is a discrete device of the product */ if (!data->smi_obj->fake_smbios) { product_name = fu_plugin_get_dmi_value (plugin, FU_HWIDS_KEY_PRODUCT_NAME); } pretty_tpm_name = g_strdup_printf ("%s TPM %s", product_name, tpm_mode); pretty_tpm_name_alt = g_strdup_printf ("%s TPM %s", product_name, tpm_mode_alt); /* build Standard device nodes */ dev = fu_device_new (); fu_device_set_id (dev, tpm_id); fu_device_add_guid (dev, tpm_guid); fu_device_set_vendor (dev, "Dell Inc."); fu_device_set_name (dev, pretty_tpm_name); fu_device_set_summary (dev, "Platform TPM device"); fu_device_set_version (dev, version_str); fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_INTERNAL); fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_REQUIRE_AC); fu_device_add_icon (dev, "computer"); if ((out->status & TPM_OWN_MASK) == 0 && out->flashes_left > 0) { if (fu_plugin_dell_capsule_supported (plugin)) { fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_NEEDS_REBOOT); } fu_device_set_flashes_left (dev, out->flashes_left); } else { g_debug ("%s updating disabled due to TPM ownership", pretty_tpm_name); } fu_plugin_device_add (plugin, dev); /* build alternate device node */ if (can_switch_modes) { dev_alt = fu_device_new (); fu_device_set_id (dev_alt, tpm_id_alt); fu_device_add_guid (dev_alt, tpm_guid_alt); fu_device_set_vendor (dev, "Dell Inc."); fu_device_set_name (dev_alt, pretty_tpm_name_alt); fu_device_set_summary (dev_alt, "Alternate mode for platform TPM device"); fu_device_add_flag (dev_alt, FWUPD_DEVICE_FLAG_INTERNAL); fu_device_add_flag (dev_alt, FWUPD_DEVICE_FLAG_REQUIRE_AC); fu_device_add_flag (dev_alt, FWUPD_DEVICE_FLAG_LOCKED); fu_device_add_icon (dev_alt, "computer"); fu_device_set_alternate (dev_alt, dev); fu_device_add_parent_guid (dev_alt, tpm_guid); /* If TPM is not owned and at least 1 flash left allow mode switching * * Mode switching is turned on by setting flashes left on alternate * device. */ if ((out->status & TPM_OWN_MASK) == 0 && out->flashes_left > 0) { fu_device_set_flashes_left (dev_alt, out->flashes_left); } else { g_debug ("%s mode switch disabled due to TPM ownership", pretty_tpm_name); } fu_plugin_device_add (plugin, dev_alt); } else g_debug ("System %04x does not offer TPM modeswitching", system_id); return TRUE; } gboolean fu_plugin_unlock (FuPlugin *plugin, FuDevice *device, GError **error) { FuDevice *device_alt = NULL; FwupdDeviceFlags device_flags_alt = 0; guint flashes_left = 0; guint flashes_left_alt = 0; /* for unlocking TPM1.2 <-> TPM2.0 switching */ g_debug ("Unlocking upgrades for: %s (%s)", fu_device_get_name (device), fu_device_get_id (device)); device_alt = fu_device_get_alternate (device); if (!device_alt) return FALSE; g_debug ("Preventing upgrades for: %s (%s)", fu_device_get_name (device_alt), fu_device_get_id (device_alt)); flashes_left = fu_device_get_flashes_left (device); flashes_left_alt = fu_device_get_flashes_left (device_alt); if (flashes_left == 0) { /* flashes left == 0 on both means no flashes left */ if (flashes_left_alt == 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "ERROR: %s has no flashes left.", fu_device_get_name (device)); /* flashes left == 0 on just unlocking device is ownership */ } else { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "ERROR: %s is currently OWNED. " "Ownership must be removed to switch modes.", fu_device_get_name (device_alt)); } return FALSE; } /* clone the info from real device but prevent it from being flashed */ device_flags_alt = fu_device_get_flags (device_alt); fu_device_set_flags (device, device_flags_alt); fu_device_set_flags (device_alt, device_flags_alt & ~FWUPD_DEVICE_FLAG_UPDATABLE); /* make sure that this unlocked device can be updated */ fu_device_set_version (device, "0.0.0.0"); return TRUE; } gboolean fu_plugin_update (FuPlugin *plugin, FuDevice *device, GBytes *blob_fw, FwupdInstallFlags flags, GError **error) { FuPluginData *data = fu_plugin_get_data (plugin); g_autoptr (fwup_resource_iter) iter = NULL; fwup_resource *re = NULL; const gchar *name = NULL; gint rc; guint flashes_left; const gchar *guidstr = NULL; efi_guid_t guid; /* test the flash counter * - devices with 0 left at setup aren't allowed offline updates * - devices greater than 0 should show a warning when near 0 */ flashes_left = fu_device_get_flashes_left (device); if (flashes_left > 0) { name = fu_device_get_name (device); g_debug ("%s has %u flashes left", name, flashes_left); if ((flags & FWUPD_INSTALL_FLAG_FORCE) == 0 && flashes_left <= 2) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "WARNING: %s only has %u flashes left. " "See https://github.com/hughsie/fwupd/wiki/Dell-TPM:-flashes-left for more information.", name, flashes_left); return FALSE; } } if (data->smi_obj->fake_smbios) return TRUE; /* perform the update */ g_debug ("Performing capsule update"); /* Stuff the payload into a different GUID * - with fwup 0.5 this uses the ESRT GUID * - with fwup 0.6 this uses the payload's GUID * it's preferable to use payload GUID to avoid * a corner case scenario of UEFI BIOS and non-ESRT * update happening at same time */ fwup_resource_iter_create (&iter); fwup_resource_iter_next (iter, &re); guidstr = fu_device_get_guid_default (device); rc = efi_str_to_guid (guidstr, &guid); if (rc < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to convert guid to string"); return FALSE; } rc = fwup_set_guid (iter, &re, &guid); if (rc < 0 || re == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to update GUID %s", strerror (rc)); return FALSE; } /* NOTE: if there are problems with this working, adjust the * GUID in the capsule header to match something in ESRT. * This won't actually cause any bad behavior because the real * payload GUID is extracted later on. */ fu_device_set_status (device, FWUPD_STATUS_SCHEDULING); rc = fwup_set_up_update_with_buf (re, 0, g_bytes_get_data (blob_fw, NULL), g_bytes_get_size (blob_fw)); if (rc < 0) { g_autoptr(GString) err_string = g_string_new ("Dell firmware update failed:\n"); rc = 1; for (int i = 0; rc > 0; i++) { char *filename = NULL; char *function = NULL; char *message = NULL; int line = 0; int err = 0; rc = efi_error_get (i, &filename, &function, &line, &message, &err); if (rc <= 0) break; g_string_append_printf (err_string, "{error #%d} %s:%d %s(): %s: %s \n", i, filename, line, function, message, strerror(err)); } g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "%s", err_string->str); return FALSE; } return TRUE; } void fu_plugin_device_registered (FuPlugin *plugin, FuDevice *device) { /* thunderbolt plugin */ if (g_strcmp0 (fu_device_get_plugin (device), "thunderbolt") == 0 && fu_device_has_flag (device, FWUPD_DEVICE_FLAG_INTERNAL)) { /* fix VID/DID of safe mode devices */ if (fu_device_get_metadata_boolean (device, FU_DEVICE_METADATA_TBT_IS_SAFE_MODE)) { g_autofree gchar *vendor_id = NULL; g_autofree gchar *device_id = NULL; guint16 system_id = 0; vendor_id = g_strdup ("TBT:0x00D4"); system_id = fu_dell_get_system_id (plugin); if (system_id == 0) return; /* the kernel returns lowercase in sysfs, need to match it */ device_id = g_strdup_printf ("TBT-%04x%04x", 0x00d4u, (unsigned) system_id); fu_device_set_vendor_id (device, vendor_id); fu_device_add_guid (device, device_id); fu_device_add_flag (device, FWUPD_DEVICE_FLAG_UPDATABLE); } } } static gboolean fu_dell_toggle_flash (FuPlugin *plugin, FuDevice *device, gboolean enable, GError **error) { FuPluginData *data = fu_plugin_get_data (plugin); gboolean has_host = fu_dell_host_mst_supported (plugin); gboolean has_dock; guint32 dock_location; const gchar *tmp; if (device) { if (!fu_device_has_flag (device, FWUPD_DEVICE_FLAG_UPDATABLE)) return TRUE; tmp = fu_device_get_plugin (device); if (g_strcmp0 (tmp, "synapticsmst") != 0) return TRUE; g_debug ("preparing/cleaning update for %s", tmp); } /* Dock MST Hub */ has_dock = fu_dell_detect_dock (data->smi_obj, &dock_location); if (has_dock) { if (!fu_dell_toggle_dock_mode (data->smi_obj, enable, dock_location, error)) g_debug ("unable to change dock to %d", enable); else g_debug ("Toggled dock mode to %d", enable); } /* System MST hub */ if (has_host) { if (!fu_dell_toggle_host_mode (data->smi_obj, MST_GPIO_GUID, enable)) g_debug ("Unable to toggle MST hub GPIO to %d", enable); else g_debug ("Toggled MST hub GPIO to %d", enable); } #if defined (HAVE_SYNAPTICS) /* set a delay to allow OS response to settling the GPIO change */ if (enable && device == NULL && (has_dock || has_host)) fu_plugin_set_coldplug_delay (plugin, DELL_FLASH_MODE_DELAY * 1000); #endif return TRUE; } gboolean fu_plugin_update_prepare (FuPlugin *plugin, FuDevice *device, GError **error) { return fu_dell_toggle_flash (plugin, device, TRUE, error); } gboolean fu_plugin_update_cleanup (FuPlugin *plugin, FuDevice *device, GError **error) { return fu_dell_toggle_flash (plugin, device , FALSE, error); } gboolean fu_plugin_coldplug_prepare (FuPlugin *plugin, GError **error) { return fu_dell_toggle_flash (plugin, NULL, TRUE, error); } gboolean fu_plugin_coldplug_cleanup (FuPlugin *plugin, GError **error) { return fu_dell_toggle_flash (plugin, NULL, FALSE, error); } void fu_plugin_init (FuPlugin *plugin) { FuPluginData *data = fu_plugin_alloc_data (plugin, sizeof (FuPluginData)); g_autofree gchar *tmp = NULL; data->libsmbios_major = smbios_get_library_version_major(); data->libsmbios_minor = smbios_get_library_version_minor(); g_debug ("Using libsmbios %u.%u", data->libsmbios_major, data->libsmbios_minor); tmp = g_strdup_printf ("%u.%u", data->libsmbios_major, data->libsmbios_minor); fu_plugin_add_runtime_version (plugin, "com.dell.libsmbios", tmp); data->smi_obj = g_malloc0 (sizeof (FuDellSmiObj)); if (g_getenv ("FWUPD_DELL_VERBOSE") != NULL) g_setenv ("LIBSMBIOS_C_DEBUG_OUTPUT_ALL", "1", TRUE); if (fu_dell_supported (plugin)) data->smi_obj->smi = dell_smi_factory (DELL_SMI_DEFAULTS); data->smi_obj->fake_smbios = FALSE; if (g_getenv ("FWUPD_DELL_FAKE_SMBIOS") != NULL) data->smi_obj->fake_smbios = TRUE; } void fu_plugin_destroy (FuPlugin *plugin) { FuPluginData *data = fu_plugin_get_data (plugin); if (data->smi_obj->smi) dell_smi_obj_free (data->smi_obj->smi); g_free(data->smi_obj); } gboolean fu_plugin_startup (FuPlugin *plugin, GError **error) { FuPluginData *data = fu_plugin_get_data (plugin); GUsbContext *usb_ctx = fu_plugin_get_usb_context (plugin); gint uefi_supported; if (data->smi_obj->fake_smbios) { g_debug ("Called with fake SMBIOS implementation. " "We're ignoring test for SBMIOS table and ESRT. " "Individual calls will need to be properly staged."); return TRUE; } if (!fu_dell_supported (plugin)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Firmware updating not supported"); return FALSE; } if (data->smi_obj->smi == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to initialize libsmbios library"); return FALSE; } /* If ESRT is not turned on, fwupd will have already created an * unlock device (if compiled with support). * * Once unlocked, that will enable flashing capsules here too. * * that means we should only look for supported = 1 */ uefi_supported = fwup_supported (); data->capsule_supported = (uefi_supported == 1); if (!data->capsule_supported) { g_debug ("UEFI capsule firmware updating not supported (%x)", (guint) uefi_supported); } if (usb_ctx != NULL) { g_signal_connect (usb_ctx, "device-added", G_CALLBACK (fu_plugin_dell_device_added_cb), plugin); g_signal_connect (usb_ctx, "device-removed", G_CALLBACK (fu_plugin_dell_device_removed_cb), plugin); } return TRUE; } static gboolean fu_plugin_dell_coldplug (FuPlugin *plugin, GError **error) { /* look for switchable TPM */ if (!fu_plugin_dell_detect_tpm (plugin, error)) g_debug ("No switchable TPM detected"); return TRUE; } gboolean fu_plugin_coldplug (FuPlugin *plugin, GError **error) { return fu_plugin_dell_coldplug (plugin, error); } gboolean fu_plugin_recoldplug (FuPlugin *plugin, GError **error) { return fu_plugin_dell_coldplug (plugin, error); }