fwupd/plugins/dell/fu-plugin-dell.c
Richard Hughes e48351e260 Set the alternate device ID, not the object itself
If the daemon either de-duplicates or replaces the object passed emitted from
device-added then the object set as the alternate may not be the same instance
as the daemon version. This causes weird things to happen.

To make this less fragile, specify the *ID* of the object that should be the
alternate device, which allows the daemon to do clever things, and then assign
the object from the ID as the last step.

Although fixing no bug, this makes implementing future functionality easier.
2018-06-22 13:28:27 +01:00

1136 lines
33 KiB
C

/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
*
* Copyright (C) 2016 Richard Hughes <richard@hughsie.com>
* Copyright (C) 2016 Mario Limonciello <mario_limonciello@dell.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include <fwup.h>
#include <appstream-glib.h>
#include <glib/gstdio.h>
#include <smbios_c/system_info.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#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 = "Unknown";
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_id (dev_alt, fu_device_get_id (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 == NULL) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"No alternate device for %s",
fu_device_get_name (device));
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);
}