diff --git a/Makefile.am b/Makefile.am index 73f139f25..70f0003b2 100644 --- a/Makefile.am +++ b/Makefile.am @@ -54,6 +54,7 @@ MAINTAINERCLEANFILES = \ DISTCHECK_CONFIGURE_FLAGS = \ --enable-colorhug \ --enable-uefi \ + --enable-dell \ --with-udevrulesdir=$$dc_install_base/$(udevrulesdir) \ --with-systemdunitdir=$$dc_install_base/$(systemdunitdir) diff --git a/README.md b/README.md index 7c5baa608..bfcc874cd 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,18 @@ and compiling it with libsmbios support. When fwupd and fwupdate have been compiled with this support you will be able to enable UEFI support on the device by using the `unlock` command. +Dell Support +---------------- + +This allows installing Dell capsules that are not part of the ESRT table. + +For Dell support you will need libsmbios_c version 2.3.0 or later and +efivar. +* source: http://linux.dell.com/cgi-bin/cgit.cgi/libsmbios.git/ +* rpms: https://apps.fedoraproject.org/packages/libsmbios +* debs (Debian): http://tracker.debian.org/pkg/libsmbios +* debs (Ubuntu): http://launchpad.net/ubuntu/+source/libsmbios + Raspberry Pi support -------------------- diff --git a/configure.ac b/configure.ac index 2f83eb069..bcbf91975 100644 --- a/configure.ac +++ b/configure.ac @@ -181,6 +181,26 @@ if test x$enable_uefi != xno; then fi AM_CONDITIONAL(HAVE_UEFI, test x$enable_uefi = xyes) +# Dell Non ESRT capsule support +AC_ARG_ENABLE(dell, AS_HELP_STRING([--enable-dell],[Enable Dell non-ESRT capsule support]), + enable_dell=$enableval, enable_dell=yes) +if test x$enable_dell != xno; then + PKG_CHECK_MODULES(UEFI, fwup >= 0.5) + PKG_CHECK_MODULES(LIBSMBIOS, libsmbios_c >= 2.3.0) + PKG_CHECK_MODULES(EFIVAR, efivar) + AC_DEFINE(HAVE_DELL,1,[Use Dell non-ESRT capsule support]) + + # check for ability to change GUID + PKG_CHECK_MODULES(UEFI_GUID, fwup >= 0.6, + has_uefi_guid=yes, + has_uefi_guid=no) + if test x$has_uefi_guid = xyes; then + AC_DEFINE(HAVE_UEFI_GUID,1,[Use UEFI GUID override]) + fi + +fi +AM_CONDITIONAL(HAVE_DELL, test x$enable_dell = xyes) + # systemd support AC_ARG_WITH([systemdunitdir], AS_HELP_STRING([--with-systemdunitdir=DIR], [Directory for systemd service files]), diff --git a/src/Makefile.am b/src/Makefile.am index 0519409a6..394b25e1d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -198,6 +198,26 @@ fu_self_test_CFLAGS = \ -DLOCALSTATEDIR=\"/tmp/fwupd-self-test/var\" \ $(WARNINGFLAGS_C) +if HAVE_DELL +fwupd_SOURCES += \ + fu-provider-dell.c \ + fu-provider-dell.h +AM_CPPFLAGS += \ + $(LIBSMBIOS_CFLAGS) \ + $(EFIVAR_CFLAGS) +fwupd_LDADD += \ + $(LIBSMBIOS_LIBS) \ + $(EFIVAR_LIBS) +fu_self_test_LDADD += \ + $(GUSB_LIBS) \ + $(UEFI_LIBS) \ + $(LIBSMBIOS_LIBS) \ + $(EFIVAR_LIBS) +fu_self_test_SOURCES += \ + fu-provider-dell.c \ + fu-provider-dell.h +endif + install-data-hook: if test -w $(DESTDIR)$(prefix)/; then \ mkdir -p $(DESTDIR)$(localstatedir)/lib/fwupd; \ diff --git a/src/fu-main.c b/src/fu-main.c index a86f5b1d1..33f0c1fa1 100644 --- a/src/fu-main.c +++ b/src/fu-main.c @@ -54,6 +54,9 @@ #ifdef HAVE_UEFI #include "fu-provider-uefi.h" #endif +#ifdef HAVE_DELL + #include "fu-provider-dell.h" +#endif #ifndef PolkitAuthorizationResult_autoptr G_DEFINE_AUTOPTR_CLEANUP_FUNC(PolkitAuthorizationResult, g_object_unref) @@ -2380,6 +2383,9 @@ main (int argc, char *argv[]) #ifdef HAVE_UEFI fu_main_add_provider (priv, fu_provider_uefi_new ()); #endif +#ifdef HAVE_DELL + fu_main_add_provider (priv, fu_provider_dell_new ()); +#endif /* last as least priority */ fu_main_add_provider (priv, fu_provider_usb_new ()); diff --git a/src/fu-provider-dell.c b/src/fu-provider-dell.c new file mode 100644 index 000000000..610c0407c --- /dev/null +++ b/src/fu-provider-dell.c @@ -0,0 +1,1246 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * 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 +#include +#include +#include +#include + +#include "fu-quirks.h" +#include "fu-device.h" +#include "fu-provider-dell.h" + +static void fu_provider_dell_finalize (GObject *object); + +typedef struct dell_smi_obj fu_dell_smi_obj; + +/* SMI return values used */ +#define SMI_SUCCESS 0 +#define SMI_INVALID_BUFFER -6 + +/* 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 + +/* These are nodes that will indicate information about + * the TPM status + */ +struct tpm_status { + guint32 ret; + guint32 fw_version; + guint32 status; + guint32 flashes_left; +}; +#define TPM_EN_MASK 0x0001 +#define TPM_OWN_MASK 0x0004 +#define TPM_TYPE_MASK 0x0F00 +#define TPM_1_2_MODE 0x0001 +#define TPM_2_0_MODE 0x0002 + +/* These are DACI class/select needed for + * flash capability queries + */ +#define DACI_FLASH_INTERFACE_CLASS 7 +#define DACI_FLASH_INTERFACE_SELECT 3 +#define DACI_FLASH_ARG_TPM 2 + +/* DACI class/select for dock capabilities */ +#define DACI_DOCK_CLASS 17 +#define DACI_DOCK_SELECT 22 +#define DACI_DOCK_ARG_COUNT 0 +#define DACI_DOCK_ARG_INFO 1 +#define DACI_DOCK_ARG_MODE 2 +#define DACI_DOCK_ARG_MODE_USER 0 +#define DACI_DOCK_ARG_MODE_FLASH 1 + +/* VID/PID of ethernet controller on dock */ +#define DOCK_NIC_VID 0x0bda +#define DOCK_NIC_PID 0x8153 + +/* These are for dock query capabilities */ +struct dock_count_in { + guint32 argument; + guint32 reserved1; + guint32 reserved2; + guint32 reserved3; +}; + +struct dock_count_out { + guint32 ret; + guint32 count; + guint32 location; + guint32 reserved; +}; + +/* Dock Info version 1 */ +#pragma pack(1) +#define MAX_COMPONENTS 5 +typedef struct _DOCK_DESCRIPTION +{ + const efi_guid_t guid; + const gchar * query; + const gchar * desc; +} DOCK_DESCRIPTION; + +typedef struct _COMPONENTS { + gchar description[80]; + guint32 fw_version; /* BCD format: 0x00XXYYZZ */ +} COMPONENTS; + +typedef struct _DOCK_INFO { + gchar dock_description[80]; + guint32 flash_pkg_version; /* BCD format: 0x00XXYYZZ */ + guint32 cable_type; /* bit0-7 cable type, bit7-31 set to 0 */ + guint8 location; /* Location of the dock */ + guint8 reserved; + guint8 component_count; + COMPONENTS components[MAX_COMPONENTS]; /* number of component_count */ +} DOCK_INFO; + +typedef struct _DOCK_INFO_HEADER { + guint8 dir_version; /* version 1, 2 … */ + guint8 dock_type; + guint16 reserved; +} DOCK_INFO_HEADER; + +typedef struct _DOCK_INFO_RECORD { + DOCK_INFO_HEADER dock_info_header; /* dock version specific definition */ + DOCK_INFO dock_info; +} DOCK_INFO_RECORD; + +typedef union _INFO_UNION{ + guint8 *buf; + DOCK_INFO_RECORD *record; +} INFO_UNION; +#pragma pack() + +typedef enum _DOCK_TYPE +{ + DOCK_TYPE_NONE, + DOCK_TYPE_TB15, + DOCK_TYPE_WD15 +} DOCK_TYPE; + +typedef enum _CABLE_TYPE +{ + CABLE_TYPE_NONE, + CABLE_TYPE_LEGACY, + CABLE_TYPE_UNIV, + CABLE_TYPE_TBT +} CABLE_TYPE; + +/* These are for matching the components */ +#define WD15_EC_STR "2 0 2 2 0" +#define TB15_EC_STR "2 0 2 1 0" +#define TB15_PC2_STR "2 1 0 1 1" +#define TB15_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 EFI_GUID (0xE7CA1F36, 0xBF73, 0x4574, 0xAFE6, 0xA4, 0xCC, 0xAC, 0xAB, 0xF4, 0x79) +#define WD15_EC_GUID EFI_GUID (0xE8445370, 0x0211, 0x449D, 0x9FAA, 0x10, 0x79, 0x06, 0xAB, 0x18, 0x9F) +#define TB15_EC_GUID EFI_GUID (0x33CC8870, 0xB1FC, 0x4EC7, 0x948A, 0xC0, 0x74, 0x96, 0x87, 0x4F, 0xAF) +#define TB15_PC2_GUID EFI_GUID (0x1B52C630, 0x86F6, 0x4AEE, 0x9F0C, 0x47, 0x4D, 0xC6, 0xBE, 0x49, 0xB6) +#define TB15_PC1_GUID EFI_GUID (0x8FE183DA, 0xC94E, 0x4804, 0xB319, 0x0F, 0x1B, 0xA5, 0x45, 0x7A, 0x69) +#define WD15_PC1_GUID EFI_GUID (0x8BA2B709, 0x6F97, 0x47FC, 0xB7E7, 0x6A, 0x87, 0xB5, 0x78, 0xFE, 0x25) +#define LEGACY_CBL_GUID EFI_GUID (0xFECE1537, 0xD683, 0x4EA8, 0xB968, 0x15, 0x45, 0x30, 0xBB, 0x6F, 0x73) +#define UNIV_CBL_GUID EFI_GUID (0xE2BF3AAD, 0x61A3, 0x44BF, 0x91EF, 0x34, 0x9B, 0x39, 0x51, 0x5D, 0x29) +#define TBT_CBL_GUID EFI_GUID (0x6DC832FC, 0x5BB0, 0x4E63, 0xA2FF, 0x02, 0xAA, 0xBA, 0x5B, 0xC1, 0xDC) +#define DOCK_MST_GUID EFI_GUID (0x7BEE2A28, 0xB909, 0x540D, 0x9FA9, 0x6A, 0x4C, 0x96, 0x11, 0xD9, 0x92) +#define CBL_NVM_GUID EFI_GUID (0x269dDC59, 0xE1ED, 0x519D, 0x8FF2, 0x6E, 0x49, 0xFF, 0x1D, 0xD8, 0xD7) +#define TB15_NVM_GUID EFI_GUID (0x05824E11, 0x0925, 0x572F, 0xAF03, 0x31, 0x0E, 0x89, 0x81, 0x0D, 0x80) + +#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" +#define MST_DESC "MST Hub" +#define TB15_NVM_DESC "Thunderbolt NVM" +#define CBL_NVM_DESC "Cable Thunderbolt NVM" + +/** + * Devices that should explicitly disable modeswitching + */ +static guint16 tpm_switch_blacklist[] = {0x06D6, 0x06E6, 0x06E7, 0x06EB, 0x06EA, + 0x07A4}; + +typedef struct { + GHashTable *devices; /* DeviceKey:FuProviderDellDockItem */ + GUsbContext *usb_ctx; + gboolean fake_smbios; + guint32 fake_output[4]; + guint16 fake_vid; + guint16 fake_pid; + guint8 *fake_buffer; +} FuProviderDellPrivate; + +typedef struct { + FuDevice *device; + FuProviderDell *provider_dell; +} FuProviderDellDockItem; + +G_DEFINE_TYPE_WITH_PRIVATE (FuProviderDell, fu_provider_dell, FU_TYPE_PROVIDER) +#define GET_PRIVATE(o) (fu_provider_dell_get_instance_private (o)) + +static void +_dell_smi_obj_free (fu_dell_smi_obj *smi) +{ + dell_smi_obj_free (smi); +} + +static void +_fwup_resource_iter_free (fwup_resource_iter *iter) +{ + fwup_resource_iter_destroy (&iter); +} + +G_DEFINE_AUTOPTR_CLEANUP_FUNC(fwup_resource_iter, _fwup_resource_iter_free); +G_DEFINE_AUTOPTR_CLEANUP_FUNC(fu_dell_smi_obj, _dell_smi_obj_free); + +static const gchar * +fu_provider_dell_get_name (FuProvider *provider) +{ + return "Dell"; +} + +static gboolean +fu_provider_dell_match_dock_component(const gchar *query_str, + const efi_guid_t **guid_out, + const gchar **name_out) +{ + guint i; + const DOCK_DESCRIPTION list[] = { + {WD15_EC_GUID, WD15_EC_STR, EC_DESC}, + {TB15_EC_GUID, TB15_EC_STR, EC_DESC}, + {WD15_PC1_GUID, WD15_PC1_STR, PC1_DESC}, + {TB15_PC1_GUID, TB15_PC1_STR, PC1_DESC}, + {TB15_PC2_GUID, TB15_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 (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_provider_dell_inject_fake_data (FuProviderDell *provider_dell, + guint32 *output, guint16 vid, guint16 pid, + guint8 *buf) +{ + FuProviderDellPrivate *priv = GET_PRIVATE (provider_dell); + guint i; + + if (!priv->fake_smbios) + return; + for (i = 0; i < 4; i++) + priv->fake_output[i] = output[i]; + priv->fake_vid = vid; + priv->fake_pid = pid; + priv->fake_buffer = buf; +} + +static gboolean +fu_provider_dell_execute_smi (FuProviderDell *provider_dell, fu_dell_smi_obj *smi) +{ + FuProviderDellPrivate *priv = GET_PRIVATE (provider_dell); + guint ret; + + if (priv->fake_smbios) + return TRUE; + + ret = dell_smi_obj_execute (smi); + if (ret != 0) { + g_debug ("Dell: SMI execution failed: %d", ret); + return FALSE; + } + return TRUE; +} + +static gboolean +fu_provider_dell_execute_simple_smi (FuProviderDell *provider_dell, + guint32 class, guint32 select, + guint32 *args, guint32 *out) +{ + FuProviderDellPrivate *priv = GET_PRIVATE (provider_dell); + guint i; + + if (priv->fake_smbios) { + for (i = 0; i < 4; i++) + out[i] = priv->fake_output[i]; + return TRUE; + } + if (dell_simple_ci_smi (class, select, args, out)) { + g_debug ("Dell: failed to run query %d/%d", class, select); + return FALSE; + } + return TRUE; +} + +static guint32 +fu_provider_dell_get_res (FuProviderDell *provider_dell, + fu_dell_smi_obj *smi, guint32 arg) +{ + FuProviderDellPrivate *priv = GET_PRIVATE (provider_dell); + + if (priv->fake_smbios) + return priv->fake_output[arg]; + + return dell_smi_obj_get_res (smi, arg); +} + +static gboolean +fu_provider_dell_detect_dock (FuProviderDell *provider_dell, guint32 *location) +{ + g_autofree struct dock_count_in *count_args; + g_autofree struct dock_count_out *count_out; + + /* look up dock count */ + count_args = g_malloc0 (sizeof(struct dock_count_in)); + count_out = g_malloc0 (sizeof(struct dock_count_out)); + count_args->argument = DACI_DOCK_ARG_COUNT; + if (!fu_provider_dell_execute_simple_smi (provider_dell, + DACI_DOCK_CLASS, + DACI_DOCK_SELECT, + (guint32 *) count_args, + (guint32 *) count_out)) + return FALSE; + if (count_out->ret != 0) { + g_debug ("Dell: Failed to query system for dock count: " + "(%d)", count_out->ret); + return FALSE; + } + if (count_out->count < 1) { + g_debug ("Dell: no dock plugged in"); + return FALSE; + } + *location = count_out->location; + g_debug ("Dell: Dock count %u, location %u.", + count_out->count, *location); + return TRUE; + +} + +static void +fu_provider_dell_device_free (FuProviderDellDockItem *item) +{ + g_object_unref (item->device); + g_object_unref (item->provider_dell); +} + +static AsVersionParseFlag +fu_provider_dell_get_version_format (void) +{ + guint i; + g_autofree gchar *content = NULL; + + /* any vendors match */ + if (!g_file_get_contents ("/sys/class/dmi/id/sys_vendor", + &content, NULL, NULL)) + return AS_VERSION_PARSE_FLAG_USE_TRIPLET; + g_strchomp (content); + for (i = 0; quirk_table[i].sys_vendor != NULL; i++) { + if (g_strcmp0 (content, quirk_table[i].sys_vendor) == 0) + return quirk_table[i].flags; + } + + /* fall back */ + return AS_VERSION_PARSE_FLAG_USE_TRIPLET; +} + +static gchar * +fu_provider_dell_get_dock_key (FuProviderDell *provider_dell, + GUsbDevice *device, const gchar *guid) +{ + FuProviderDellPrivate *priv = GET_PRIVATE (provider_dell); + const gchar* platform_id; + + if (priv->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_provider_dell_dock_node (FuProviderDell *provider_dell, GUsbDevice *device, + guint8 type, const efi_guid_t *guid_raw, + const gchar *component_desc, + const gchar *version) +{ + FuProviderDellPrivate *priv = GET_PRIVATE (provider_dell); + FuProviderDellDockItem *item; + const gchar *dock_type; + g_autofree gchar *dock_id = NULL; + g_autofree gchar *guid_str = NULL; + g_autofree gchar *dock_key = NULL; + g_autofree gchar *dock_name = NULL; + + switch (type) { + case DOCK_TYPE_TB15: + dock_type = "TB15"; + break; + case DOCK_TYPE_WD15: + dock_type = "WD15"; + break; + default: + g_debug ("Dell: Dock type %d unknown", + type); + return FALSE; + } + + guid_str = g_strdup ("00000000-0000-0000-0000-000000000000"); + if (efi_guid_to_str (guid_raw, &guid_str) < 0) { + g_debug ("Dell: Failed to convert GUID."); + return FALSE; + } + + dock_key = fu_provider_dell_get_dock_key (provider_dell, device, + guid_str); + item = g_hash_table_lookup (priv->devices, dock_key); + if (item != NULL) { + g_debug ("Dell: Item %s is already registered.", dock_key); + return FALSE; + } + + item = g_new0 (FuProviderDellDockItem, 1); + item->provider_dell = g_object_ref (provider_dell); + item->device = fu_device_new(); + dock_id = g_strdup_printf ("DELL-%s" G_GUINT64_FORMAT, guid_str); + dock_name = g_strdup_printf ("Dell %s %s", dock_type, + component_desc); + fu_device_set_id (item->device, dock_id); + fu_device_set_name (item->device, dock_name); + fu_device_add_guid (item->device, guid_str); + fu_device_add_flag (item->device, FU_DEVICE_FLAG_REQUIRE_AC); + if (version != NULL) { + fu_device_set_version (item->device, version); + fu_device_add_flag (item->device, FU_DEVICE_FLAG_ALLOW_OFFLINE); + } + + g_hash_table_insert (priv->devices, g_strdup (dock_key), item); + fu_provider_device_add (FU_PROVIDER (provider_dell), item->device); + return TRUE; +} + +void +fu_provider_dell_device_added_cb (GUsbContext *ctx, + GUsbDevice *device, + FuProviderDell *provider_dell) +{ + FuProviderDellPrivate *priv = GET_PRIVATE (provider_dell); + AsVersionParseFlag parse_flags; + guint16 pid; + guint16 vid; + const gchar *query_str; + const gchar *component_name = NULL; + INFO_UNION buf; + DOCK_INFO *dock_info; + guint buf_size; + g_autoptr(fu_dell_smi_obj) smi = NULL; + gint result; + gint i; + guint32 location; + const efi_guid_t *guid_raw = NULL; + efi_guid_t tmpguid; + gboolean old_ec = FALSE; + + g_autofree gchar *fw_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 (!priv->fake_smbios) { + vid = g_usb_device_get_vid (device); + pid = g_usb_device_get_pid (device); + } else { + vid = priv->fake_vid; + pid = priv->fake_pid; + } + + /* we're going to match on the Realtek NIC in the dock */ + if (vid != DOCK_NIC_VID || pid != DOCK_NIC_PID) + return; + if (!fu_provider_dell_detect_dock (provider_dell, &location)) + return; + + /* look up more information on dock */ + if (!priv->fake_smbios) { + smi = dell_smi_factory (DELL_SMI_DEFAULTS); + if (!smi) { + g_debug ("Dell: failure initializing SMI"); + return; + } + dell_smi_obj_set_class (smi, DACI_DOCK_CLASS); + dell_smi_obj_set_select (smi, DACI_DOCK_SELECT); + dell_smi_obj_set_arg (smi, cbARG1, DACI_DOCK_ARG_INFO); + dell_smi_obj_set_arg (smi, cbARG2, location); + buf_size = sizeof(DOCK_INFO_RECORD); + buf.buf = dell_smi_obj_make_buffer_frombios_auto (smi, cbARG3, buf_size); + if (!buf.buf) { + g_debug ("Dell: failed to initialize buffer"); + return; + } + } else { + buf.buf = priv->fake_buffer; + } + if (!fu_provider_dell_execute_smi (provider_dell, smi)) + return; + result = fu_provider_dell_get_res (provider_dell, smi, cbARG1); + if (result != SMI_SUCCESS) { + if (result == SMI_INVALID_BUFFER) { + g_debug ("Dell: Invalid buffer size, sent %d, needed %d", + buf_size, + fu_provider_dell_get_res (provider_dell, smi, cbARG2)); + } else { + g_debug ("Dell: SMI execution returned error: %d", + result); + } + return; + } + + if (buf.record->dock_info_header.dir_version != 1) { + g_debug ("Dell: Dock info header version unknown: %d", + buf.record->dock_info_header.dir_version); + return; + } + + dock_info = &buf.record->dock_info; + g_debug ("Dell: dock description: %s", dock_info->dock_description); + /* Note: fw package version is deprecated, look at components instead */ + g_debug ("Dell: dock flash pkg ver: 0x%x", dock_info->flash_pkg_version); + if (dock_info->flash_pkg_version == 0x00ffffff) + g_debug ("Dell: WARNING: dock flash package version invalid"); + g_debug ("Dell: dock cable type: %d", dock_info->cable_type); + g_debug ("Dell: dock location: %d", dock_info->location); + g_debug ("Dell: dock component count: %d", dock_info->component_count); + parse_flags = fu_provider_dell_get_version_format (); + + for (i = 0; i < dock_info->component_count; i++) { + if (i > MAX_COMPONENTS) { + g_debug ("Dell: Too many components. Invalid: #%d", i); + break; + } + g_debug ("Dell: dock component %d: %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 ") + 6; + if (!fu_provider_dell_match_dock_component (query_str, &guid_raw, + &component_name)) { + g_debug ("Dell: 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_provider_dell_dock_node (provider_dell, + device, + buf.record->dock_info_header.dock_type, + guid_raw, + component_name, + fw_str)) { + g_debug ("Dell: failed to create %s", component_name); + return; + } + } + + /* Create devices that we don't get in the dock manifest + * These are currently not updatable. + * Querying the version is also not available at this time + */ + /* TB15 NVM */ + if (buf.record->dock_info_header.dock_type == DOCK_TYPE_TB15) { + tmpguid = TB15_NVM_GUID; + if (!fu_provider_dell_dock_node (provider_dell, + device, + buf.record->dock_info_header.dock_type, + &tmpguid, + TB15_NVM_DESC, + NULL)) { + g_debug ("Dell: failed to create %s", TB15_NVM_DESC); + return; + } + } + + /* Cable NVM */ + if (dock_info->cable_type == CABLE_TYPE_TBT || + dock_info->cable_type == CABLE_TYPE_UNIV) { + tmpguid = CBL_NVM_GUID; + if (!fu_provider_dell_dock_node (provider_dell, + device, + buf.record->dock_info_header.dock_type, + &tmpguid, + CBL_NVM_DESC, + NULL)) { + g_debug ("Dell: failed to create %s", CBL_NVM_DESC); + return; + } + } + + /* MST hub */ + tmpguid = DOCK_MST_GUID; + if (!fu_provider_dell_dock_node (provider_dell, + device, + buf.record->dock_info_header.dock_type, + &tmpguid, + MST_DESC, + NULL)) { + g_debug ("Dell: failed to create %s", MST_DESC); + return; + } + + /* if an old EC or invalid EC version found, create updatable parent */ + if (old_ec) { + tmpguid = DOCK_FLASH_GUID; + fw_str = as_utils_version_from_uint32 (dock_info->flash_pkg_version, + parse_flags); + if (!fu_provider_dell_dock_node (provider_dell, + device, + buf.record->dock_info_header.dock_type, + &tmpguid, + "", + fw_str)) { + g_debug ("Dell: failed to create top dock node"); + return; + } + } +} + +void +fu_provider_dell_device_removed_cb (GUsbContext *ctx, + GUsbDevice *device, + FuProviderDell *provider_dell) +{ + FuProviderDellPrivate *priv = GET_PRIVATE (provider_dell); + FuProviderDellDockItem *item; + g_autofree gchar *dock_key = NULL; + const efi_guid_t guids[] = { WD15_EC_GUID, TB15_EC_GUID, TB15_PC2_GUID, + TB15_PC1_GUID, WD15_PC1_GUID, + LEGACY_CBL_GUID, UNIV_CBL_GUID, + DOCK_MST_GUID, CBL_NVM_GUID, + TB15_NVM_GUID, TBT_CBL_GUID, + DOCK_FLASH_GUID}; + const efi_guid_t *guid_raw; + guint16 pid; + guint16 vid; + guint i; + + g_autofree gchar *guid_str = NULL; + + if (!priv->fake_smbios) { + vid = g_usb_device_get_vid (device); + pid = g_usb_device_get_pid (device); + } else { + vid = priv->fake_vid; + pid = priv->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 (i = 0; i < G_N_ELEMENTS(guids); i++) { + guid_raw = &guids[i]; + guid_str = g_strdup ("00000000-0000-0000-0000-000000000000"); + efi_guid_to_str (guid_raw, &guid_str); + dock_key = fu_provider_dell_get_dock_key (provider_dell, device, + guid_str); + item = g_hash_table_lookup (priv->devices, dock_key); + if (item != NULL) { + fu_provider_device_remove (FU_PROVIDER (provider_dell), + item->device); + g_hash_table_remove (priv->devices, dock_key); + } + } +} + +static gboolean +fu_provider_dell_get_results (FuProvider *provider, FuDevice *device, GError **error) +{ + struct smbios_struct *de_table; + guint16 completion_code = 0xFFFF; + const gchar *tmp = NULL; + + /* look at offset 0x06 for identifier meaning completion code */ + de_table = smbios_get_next_struct_by_type (0, 0xDE); + smbios_struct_get_data (de_table, &(completion_code), 0x06, sizeof(guint16)); + + if (completion_code == 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) { + 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_provider_dell_detect_tpm (FuProvider *provider, GError **error) +{ + FuProviderDell *provider_dell = FU_PROVIDER_DELL (provider); + FuProviderDellPrivate *priv = GET_PRIVATE (provider_dell); + const gchar *tpm_mode; + const gchar *tpm_mode_alt; + guint16 system_id = 0; + guint i; + gboolean can_switch_modes = TRUE; + g_autofree gchar *pretty_tpm_name_alt = NULL; + g_autofree gchar *pretty_tpm_name = NULL; + g_autofree gchar *product_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; + g_autofree guint32 *args; + g_autofree struct tpm_status *out; + g_autoptr(FuDevice) dev_alt = NULL; + g_autoptr(FuDevice) dev = NULL; + + args = g_malloc0 (sizeof(guint32) *4); + out = g_malloc0 (sizeof(struct tpm_status)); + + /* execute TPM Status Query */ + args[0] = DACI_FLASH_ARG_TPM; + if (!fu_provider_dell_execute_simple_smi (provider_dell, + DACI_FLASH_INTERFACE_CLASS, + DACI_FLASH_INTERFACE_SELECT, + args, + (guint32 *) out)) + return FALSE; + + if (out->ret != 0) { + g_debug ("Dell: Failed to query system for TPM information: " + "(%d)", out->ret); + return FALSE; + } + /* HW version is output in second /input/ arg + * it may be relevant as next gen TPM is enabled + */ + g_debug ("Dell: TPM HW version: 0x%x", args[1]); + g_debug ("Dell: TPM Status: 0x%x", out->status); + + /* test TPM enabled (Bit 0) */ + if (!(out->status & TPM_EN_MASK)) { + g_debug ("Dell: 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 ("Dell: Unable to determine TPM mode"); + return FALSE; + } + + if (!priv->fake_smbios) + system_id = sysinfo_get_dell_system_id (); + + for (i = 0; i < G_N_ELEMENTS(tpm_switch_blacklist); i++) { + if (tpm_switch_blacklist[i] == system_id) { + can_switch_modes = FALSE; + } + } + + 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 ("Dell: 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 (!g_file_get_contents ("/sys/class/dmi/id/product_name", + &product_name,NULL, NULL)) { + g_set_error_literal (error, + FWUPD_ERROR, + FWUPD_ERROR_NOT_SUPPORTED, + "Unable to read product information"); + return FALSE; + } + g_strchomp (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_name (dev, pretty_tpm_name); + fu_device_set_version (dev, version_str); + fu_device_add_flag (dev, FU_DEVICE_FLAG_INTERNAL); + fu_device_add_flag (dev, FU_DEVICE_FLAG_REQUIRE_AC); + if (out->flashes_left > 0) { + fu_device_add_flag (dev, FU_DEVICE_FLAG_ALLOW_OFFLINE); + fu_device_set_flashes_left (dev, out->flashes_left); + } + fu_provider_device_add (provider, 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_name (dev_alt, pretty_tpm_name_alt); + fu_device_add_flag (dev_alt, FU_DEVICE_FLAG_INTERNAL); + fu_device_add_flag (dev_alt, FU_DEVICE_FLAG_REQUIRE_AC); + fu_device_add_flag (dev_alt, FU_DEVICE_FLAG_LOCKED); + fu_device_set_alternate (dev_alt, dev); + + /* 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) && + out->flashes_left > 0) { + fu_device_set_flashes_left (dev_alt, out->flashes_left); + } else { + g_debug ("Dell: %s mode switch disabled due to TPM ownership", + pretty_tpm_name); + } + fu_provider_device_add (provider, dev_alt); + } + else + g_debug ("Dell: System %04x is on blacklist, disabling TPM modeswitch", + system_id); + + return TRUE; +} + +static gboolean +fu_provider_dell_coldplug (FuProvider *provider, GError **error) +{ + FuProviderDell *provider_dell = FU_PROVIDER_DELL (provider); + FuProviderDellPrivate *priv = GET_PRIVATE (provider_dell); + guint8 dell_supported = 0; + gint uefi_supported = 0; + struct smbios_struct *de_table; + + if (priv->fake_smbios) { + g_debug ("Dell: called with fake SMBIOS implementation. " + "We're ignoring test for SBMIOS table and ESRT. " + "Individual calls will need to be properly staged."); + return TRUE; + } + + /* look at offset 0x00 for identifier meaning DELL is supported */ + 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) { + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_NOT_SUPPORTED, + "Dell: firmware updating not supported (%x)", + dell_supported); + return FALSE; + } + /* Check and make sure that ESRT is supported as well. + * + * This will indicate capsule support on the system. + * + * If ESRT is not turned on, fwupd will have already created an + * unlock device (if compiled with support). + * + * Once unlocked, that will enable this provider too. + * + * that means we should only look for supported = 1 + */ + uefi_supported = fwup_supported (); + if (uefi_supported != 1) { + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_NOT_SUPPORTED, + "Dell: UEFI capsule firmware updating not supported (%x)", + uefi_supported); + return FALSE; + } + + + /* enumerate looking for a connected dock */ + g_usb_context_enumerate (priv->usb_ctx); + + /* look for switchable TPM */ + if (!fu_provider_dell_detect_tpm (provider, error)) + g_debug ("Dell: No switchable TPM detected"); + + return TRUE; +} + +static gboolean +fu_provider_dell_unlock(FuProvider *provider, + 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 ("Dell: 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 ("Dell: 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 & ~FU_DEVICE_FLAG_ALLOW_OFFLINE); + + /* make sure that this unlocked device can be updated */ + fu_device_set_version (device, "0.0.0.0"); + + return TRUE; +} + +static gboolean +fu_provider_dell_update (FuProvider *provider, + FuDevice *device, + GBytes *blob_fw, + FwupdInstallFlags flags, + GError **error) +{ + FuProviderDell *provider_dell = FU_PROVIDER_DELL (provider); + FuProviderDellPrivate *priv = GET_PRIVATE (provider_dell); + g_autoptr(fwup_resource_iter) iter = NULL; + fwup_resource *re = NULL; + const gchar *name = NULL; + gint rc; + guint flashes_left; +#ifdef HAVE_UEFI_GUID + const gchar *guidstr = NULL; + efi_guid_t guid; +#endif + + /* test the flash counter + * - devices with 0 left at coldplug 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 ("Dell: %s has %d 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 %d flashes left. " + "To update anyway please run the update with --force.", + name, flashes_left); + return FALSE; + } + } + + if (priv->fake_smbios) + return TRUE; + + /* perform the update */ + g_debug ("Dell: 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); +#ifdef HAVE_UEFI_GUID + 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; + } +#endif + /* 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_provider_set_status (provider, 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_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_NOT_SUPPORTED, + "DELL capsule update failed: %s", + strerror (rc)); + return FALSE; + } + + /* TODO: add support for AR (dock/cable) and MST flash. + * This is dependent upon OS interfaces for flashing these components. + * pseudo code below + */ +#if 0 + /* Put into mode to accept AR/MST */ + input[0] = DACI_DOCK_ARG_MODE; + input[1] = dock_location; + input[2] = DACI_DOCK_ARG_MODE_FLASH; + if (!fu_provider_dell_execute_simple_smi (provider_dell, + DACI_DOCK_CLASS, + DACI_DOCK_SELECT, + input, + output)) + return FALSE; + if (output[1] != 0) { + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_NOT_SUPPORTED, + "Dell: Failed to set dock flash mode: %d", + output[1]); + return FALSE; + } + + /* do the update */ + + /* take out of mode to accept AR/MST */ + input[0] = DACI_DOCK_ARG_MODE; + input[1] = dock_location; + input[2] = DACI_DOCK_ARG_MODE_USER; + if (!fu_provider_dell_execute_simple_smi (provider_dell, + DACI_DOCK_CLASS, + DACI_DOCK_SELECT, + input, + output)) + return FALSE; + if (output[1] != 0) { + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_NOT_SUPPORTED, + "Dell: failed to set dock user mode: %d", + output[1]); + return FALSE; + } +#endif + + return TRUE; +} + +static void +fu_provider_dell_class_init (FuProviderDellClass *klass) +{ + FuProviderClass *provider_class = FU_PROVIDER_CLASS (klass); + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + provider_class->get_name = fu_provider_dell_get_name; + provider_class->coldplug = fu_provider_dell_coldplug; + provider_class->unlock = fu_provider_dell_unlock; + provider_class->update_offline = fu_provider_dell_update; + provider_class->get_results = fu_provider_dell_get_results; + object_class->finalize = fu_provider_dell_finalize; +} + +static void +fu_provider_dell_init (FuProviderDell *provider_dell) +{ + FuProviderDellPrivate *priv = GET_PRIVATE (provider_dell); + + priv->devices = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, (GDestroyNotify) fu_provider_dell_device_free); + priv->usb_ctx = g_usb_context_new (NULL); + g_signal_connect (priv->usb_ctx, "device-added", + G_CALLBACK (fu_provider_dell_device_added_cb), + provider_dell); + g_signal_connect (priv->usb_ctx, "device-removed", + G_CALLBACK (fu_provider_dell_device_removed_cb), + provider_dell); + priv->fake_smbios = FALSE; + if (g_getenv ("FWUPD_DELL_FAKE_SMBIOS") != NULL) + priv->fake_smbios = TRUE; +} + +static void +fu_provider_dell_finalize (GObject *object) +{ + FuProviderDell *provider_dell = FU_PROVIDER_DELL (object); + FuProviderDellPrivate *priv = GET_PRIVATE (provider_dell); + + g_hash_table_unref (priv->devices); + g_object_unref (priv->usb_ctx); + + G_OBJECT_CLASS (fu_provider_dell_parent_class)->finalize (object); +} + +FuProvider * +fu_provider_dell_new (void) +{ + FuProviderDell *provider; + provider = g_object_new (FU_TYPE_PROVIDER_DELL , NULL); + return FU_PROVIDER (provider); +} diff --git a/src/fu-provider-dell.h b/src/fu-provider-dell.h new file mode 100644 index 000000000..de6e286b9 --- /dev/null +++ b/src/fu-provider-dell.h @@ -0,0 +1,59 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * 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. + */ + +#ifndef __FU_PROVIDER_DELL_H +#define __FU_PROVIDER_DELL_H + +#include +#include "fu-device.h" +#include "fu-provider.h" + +G_BEGIN_DECLS + +#define FU_TYPE_PROVIDER_DELL (fu_provider_dell_get_type ()) +G_DECLARE_DERIVABLE_TYPE (FuProviderDell, fu_provider_dell, FU, PROVIDER_DELL, FuProvider) + +struct _FuProviderDellClass +{ + FuProviderClass parent_class; +}; + +FuProvider *fu_provider_dell_new (void); +void +fu_provider_dell_inject_fake_data (FuProviderDell *provider_dell, + guint32 *output, guint16 vid, guint16 pid, + guint8 *buf); +gboolean +fu_provider_dell_detect_tpm (FuProvider *provider, GError **error); + +void +fu_provider_dell_device_added_cb (GUsbContext *ctx, + GUsbDevice *device, + FuProviderDell *provider_dell); + +void +fu_provider_dell_device_removed_cb (GUsbContext *ctx, + GUsbDevice *device, + FuProviderDell *provider_dell); + +G_END_DECLS + +#endif /* __FU_PROVIDER_DELL_H */ diff --git a/src/fu-self-test.c b/src/fu-self-test.c index 1ebfb8ea3..3e8557ab2 100644 --- a/src/fu-self-test.c +++ b/src/fu-self-test.c @@ -33,6 +33,10 @@ #include "fu-provider-rpi.h" #include "fu-rom.h" +#ifdef HAVE_DELL + #include "fu-provider-dell.h" +#endif + static gchar * fu_test_get_filename (const gchar *filename) { @@ -278,6 +282,399 @@ fu_provider_func (void) g_unlink (pending_cap); } +#ifdef HAVE_DELL +static void +fu_provider_dell_tpm_func (void) +{ + gboolean ret; + guint cnt = 0; + struct tpm_status tpm_out; + FwupdDeviceFlags flags = 0; + FuDevice *device_alt = NULL; + g_autoptr(GError) error = NULL; + g_autoptr(FuDevice) device = NULL; + g_autoptr(FuProvider) provider = NULL; + + g_setenv ("FWUPD_DELL_FAKE_SMBIOS", "1", FALSE); + provider = fu_provider_dell_new (); + ret = fu_provider_coldplug(provider, &error); + g_signal_connect (provider, "device-added", + G_CALLBACK (_provider_device_added_cb), + &device); + g_signal_connect (provider, "status-changed", + G_CALLBACK (_provider_status_changed_cb), + &cnt); + g_assert_no_error (error); + g_assert (ret); + + /* inject fake data (no TPM) */ + tpm_out.ret = -2; + fu_provider_dell_inject_fake_data (FU_PROVIDER_DELL(provider), + (guint32 *) &tpm_out, 0, 0, NULL); + ret = fu_provider_dell_detect_tpm (provider, &error); + g_assert_no_error (error); + g_assert (!ret); + + /* inject fake data: + * - that is out of flashes + * - no ownership + * - TPM 1.2 + * dev will be the locked 2.0, alt will be the orig 1.2 + */ + tpm_out.ret = 0; + tpm_out.fw_version = 0; + tpm_out.status = TPM_EN_MASK | (TPM_1_2_MODE << 8); + tpm_out.flashes_left = 0; + fu_provider_dell_inject_fake_data (FU_PROVIDER_DELL(provider), + (guint32 *) &tpm_out, 0, 0, NULL); + ret = fu_provider_dell_detect_tpm (provider, &error); + device_alt = fu_device_get_alternate (device); + g_assert_no_error (error); + g_assert (ret); + g_assert (device != NULL); + g_assert (device_alt != NULL); + + /* make sure 2.0 is locked */ + flags = fu_device_get_flags (device); + g_assert_cmpint (flags & FU_DEVICE_FLAG_LOCKED, >, 0); + + /* make sure not allowed to flash 1.2 */ + flags = fu_device_get_flags (device_alt); + g_assert_cmpint (flags & FU_DEVICE_FLAG_ALLOW_OFFLINE, !=, 1); + + /* try to unlock 2.0 */ + ret = fu_provider_unlock (provider, device, &error); + g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED); + g_assert (!ret); + g_clear_error (&error); + + /* cleanup */ + fu_provider_device_remove (provider, device_alt); + fu_provider_device_remove (provider, device); + + /* inject fake data: + * - that hasflashes + * - owned + * - TPM 1.2 + * dev will be the locked 2.0, alt will be the orig 1.2 + */ + tpm_out.status = TPM_EN_MASK | TPM_OWN_MASK | (TPM_1_2_MODE << 8); + tpm_out.flashes_left = 125; + fu_provider_dell_inject_fake_data (FU_PROVIDER_DELL(provider), + (guint32 *) &tpm_out, 0, 0, NULL); + ret = fu_provider_dell_detect_tpm (provider, &error); + device_alt = fu_device_get_alternate (device); + g_assert_no_error (error); + g_assert (ret); + g_assert (device != NULL); + g_assert (device_alt != NULL); + + /* make sure allowed to flash 1.2 */ + flags = fu_device_get_flags (device_alt); + g_assert_cmpint(flags & FU_DEVICE_FLAG_ALLOW_OFFLINE, >, 0); + + /* try to unlock 2.0 */ + ret = fu_provider_unlock (provider, device, &error); + g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED); + g_assert (!ret); + g_clear_error (&error); + + /* cleanup */ + fu_provider_device_remove (provider, device_alt); + fu_provider_device_remove (provider, device); + + /* inject fake data: + * - that has flashes + * - not owned + * - TPM 1.2 + * dev will be the locked 2.0, alt will be the orig 1.2 + */ + tpm_out.status = TPM_EN_MASK | (TPM_1_2_MODE << 8); + tpm_out.flashes_left = 125; + fu_provider_dell_inject_fake_data (FU_PROVIDER_DELL(provider), + (guint32 *) &tpm_out, 0, 0, NULL); + ret = fu_provider_dell_detect_tpm (provider, &error); + device_alt = fu_device_get_alternate (device); + g_assert_no_error (error); + g_assert (ret); + g_assert (device != NULL); + g_assert (device_alt != NULL); + + /* make sure allowed to flash 1.2 but not 2.0 */ + flags = fu_device_get_flags (device_alt); + g_assert_cmpint (flags & FU_DEVICE_FLAG_ALLOW_OFFLINE, >, 0); + flags = fu_device_get_flags (device); + g_assert_cmpint (flags & FU_DEVICE_FLAG_ALLOW_OFFLINE, ==, 0); + + /* try to unlock 2.0 */ + ret = fu_provider_unlock (provider, device, &error); + g_assert_no_error (error); + g_assert (ret); + + /* make sure no longer allowed to flash 1.2 but can flash 2.0 */ + flags = fu_device_get_flags (device_alt); + g_assert_cmpint (flags & FU_DEVICE_FLAG_ALLOW_OFFLINE, ==, 0); + flags = fu_device_get_flags (device); + g_assert_cmpint (flags & FU_DEVICE_FLAG_ALLOW_OFFLINE, >, 0); + + /* cleanup */ + fu_provider_device_remove (provider, device_alt); + fu_provider_device_remove (provider, device); + + /* inject fake data: + * - that has 1 flash left + * - not owned + * - TPM 2.0 + * dev will be the locked 1.2, alt will be the orig 2.0 + */ + tpm_out.status = TPM_EN_MASK | (TPM_2_0_MODE << 8); + tpm_out.flashes_left = 1; + fu_provider_dell_inject_fake_data (FU_PROVIDER_DELL(provider), + (guint32 *) &tpm_out, 0, 0, NULL); + ret = fu_provider_dell_detect_tpm (provider, &error); + device_alt = fu_device_get_alternate (device); + g_assert_no_error (error); + g_assert (ret); + g_assert (device != NULL); + g_assert (device_alt != NULL); + + /* make sure allowed to flash 2.0 but not 1.2 */ + flags = fu_device_get_flags (device_alt); + g_assert_cmpint (flags & FU_DEVICE_FLAG_ALLOW_OFFLINE, >, 0); + flags = fu_device_get_flags (device); + g_assert_cmpint (flags & FU_DEVICE_FLAG_ALLOW_OFFLINE, ==, 0); + + /* With one flash left we need an override */ + ret = fu_provider_update (provider, device_alt, NULL, NULL, NULL, + FWUPD_INSTALL_FLAG_OFFLINE, &error); + g_assert (!ret); + g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED); + g_clear_error (&error); + + /* test override */ + ret = fu_provider_update (provider, device_alt, NULL, NULL, NULL, + FWUPD_INSTALL_FLAG_FORCE | + FWUPD_INSTALL_FLAG_OFFLINE, &error); + g_assert (ret); + g_assert_no_error (error); + + /* cleanup */ + fu_provider_device_remove (provider, device_alt); + fu_provider_device_remove (provider, device); +} + +static void +fu_provider_dell_dock_func (void) +{ + gboolean ret; + guint cnt = 0; + guint32 out[4]; + INFO_UNION buf; + DOCK_INFO *dock_info; + g_autoptr(GError) error = NULL; + g_autoptr(FuDevice) device = NULL; + g_autoptr(FuProvider) provider = NULL; + + g_setenv ("FWUPD_DELL_FAKE_SMBIOS", "1", FALSE); + provider = fu_provider_dell_new (); + ret = fu_provider_coldplug (provider, &error); + g_signal_connect (provider, "device-added", + G_CALLBACK (_provider_device_added_cb), + &device); + g_signal_connect (provider, "status-changed", + G_CALLBACK (_provider_status_changed_cb), + &cnt); + g_assert_no_error (error); + g_assert (ret); + + /* make sure bad device doesn't trigger this */ + fu_provider_dell_inject_fake_data (FU_PROVIDER_DELL(provider), + (guint32 *) &out, + 0x1234, 0x4321, NULL); + fu_provider_dell_device_added_cb (NULL, NULL, FU_PROVIDER_DELL(provider)); + g_assert (device == NULL); + + /* inject a USB dongle matching correct VID/PID */ + out[0] = 0; + out[1] = 0; + fu_provider_dell_inject_fake_data (FU_PROVIDER_DELL(provider), + (guint32 *) &out, + DOCK_NIC_VID, DOCK_NIC_PID, NULL); + fu_provider_dell_device_added_cb (NULL, NULL, FU_PROVIDER_DELL(provider)); + g_assert (device == NULL); + + /* inject valid TB15 dock w/ invalid flash pkg version */ + buf.record = g_malloc0(sizeof(DOCK_INFO_RECORD)); + dock_info = &buf.record->dock_info; + buf.record->dock_info_header.dir_version = 1; + buf.record->dock_info_header.dock_type = DOCK_TYPE_TB15; + memcpy (dock_info->dock_description, + "BME_Dock", 8); + dock_info->flash_pkg_version = 0x00ffffff; + dock_info->cable_type = CABLE_TYPE_TBT; + dock_info->location = 2; + dock_info->component_count = 4; + dock_info->components[0].fw_version = 0x00ffffff; + memcpy (dock_info->components[0].description, + "Dock1,EC,MIPS32,BME_Dock,0 :Query 2 0 2 1 0", 43); + dock_info->components[1].fw_version = 0x10201; + memcpy (dock_info->components[1].description, + "Dock1,PC,TI,BME_Dock,0 :Query 2 1 0 1 0", 39); + dock_info->components[2].fw_version = 0x10201; + memcpy (dock_info->components[2].description, + "Dock1,PC,TI,BME_Dock,1 :Query 2 1 0 1 1", 39); + dock_info->components[3].fw_version = 0x00ffffff; + memcpy (dock_info->components[3].description, + "Dock1,Cable,Cyp,TBT_Cable,0 :Query 2 2 2 3 0", 44); + out[0] = 0; + out[1] = 1; + fu_provider_dell_inject_fake_data (FU_PROVIDER_DELL(provider), + (guint32 *) &out, + DOCK_NIC_VID, DOCK_NIC_PID, + buf.buf); + fu_provider_dell_device_added_cb (NULL, NULL, + FU_PROVIDER_DELL(provider)); + g_assert (device != NULL); + device = NULL; + g_free (buf.record); + fu_provider_dell_device_removed_cb (NULL, NULL, + FU_PROVIDER_DELL(provider)); + + /* inject valid TB15 dock w/ older system EC */ + buf.record = g_malloc0(sizeof(DOCK_INFO_RECORD)); + dock_info = &buf.record->dock_info; + buf.record->dock_info_header.dir_version = 1; + buf.record->dock_info_header.dock_type = DOCK_TYPE_TB15; + memcpy (dock_info->dock_description, + "BME_Dock", 8); + dock_info->flash_pkg_version = 0x43; + dock_info->cable_type = CABLE_TYPE_TBT; + dock_info->location = 2; + dock_info->component_count = 4; + dock_info->components[0].fw_version = 0xffffffff; + memcpy (dock_info->components[0].description, + "Dock1,EC,MIPS32,BME_Dock,0 :Query 2 0 2 1 0", 43); + dock_info->components[1].fw_version = 0x10211; + memcpy (dock_info->components[1].description, + "Dock1,PC,TI,BME_Dock,0 :Query 2 1 0 1 0", 39); + dock_info->components[2].fw_version = 0x10212; + memcpy (dock_info->components[2].description, + "Dock1,PC,TI,BME_Dock,1 :Query 2 1 0 1 1", 39); + dock_info->components[3].fw_version = 0xffffffff; + memcpy (dock_info->components[3].description, + "Dock1,Cable,Cyp,TBT_Cable,0 :Query 2 2 2 3 0", 44); + out[0] = 0; + out[1] = 1; + fu_provider_dell_inject_fake_data (FU_PROVIDER_DELL(provider), + (guint32 *) &out, + DOCK_NIC_VID, DOCK_NIC_PID, + buf.buf); + fu_provider_dell_device_added_cb (NULL, NULL, + FU_PROVIDER_DELL(provider)); + g_assert (device != NULL); + device = NULL; + g_free (buf.record); + fu_provider_dell_device_removed_cb (NULL, NULL, + FU_PROVIDER_DELL(provider)); + + + /* inject valid WD15 dock w/ invalid flash pkg version */ + buf.record = g_malloc0(sizeof(DOCK_INFO_RECORD)); + dock_info = &buf.record->dock_info; + buf.record->dock_info_header.dir_version = 1; + buf.record->dock_info_header.dock_type = DOCK_TYPE_WD15; + memcpy (dock_info->dock_description, + "IE_Dock", 7); + dock_info->flash_pkg_version = 0x00ffffff; + dock_info->cable_type = CABLE_TYPE_LEGACY; + dock_info->location = 2; + dock_info->component_count = 3; + dock_info->components[0].fw_version = 0x00ffffff; + memcpy (dock_info->components[0].description, + "Dock1,EC,MIPS32,IE_Dock,0 :Query 2 0 2 2 0", 42); + dock_info->components[1].fw_version = 0x00ffffff; + memcpy (dock_info->components[1].description, + "Dock1,PC,TI,IE_Dock,0 :Query 2 1 0 2 0", 38); + dock_info->components[2].fw_version = 0x00ffffff; + memcpy (dock_info->components[2].description, + "Dock1,Cable,Cyp,IE_Cable,0 :Query 2 2 2 1 0", 43); + out[0] = 0; + out[1] = 1; + fu_provider_dell_inject_fake_data (FU_PROVIDER_DELL(provider), + (guint32 *) &out, + DOCK_NIC_VID, DOCK_NIC_PID, + buf.buf); + fu_provider_dell_device_added_cb (NULL, NULL, + FU_PROVIDER_DELL(provider)); + g_assert (device != NULL); + device = NULL; + g_free (buf.record); + fu_provider_dell_device_removed_cb (NULL, NULL, + FU_PROVIDER_DELL(provider)); + + + /* inject valid WD15 dock w/ older system EC */ + buf.record = g_malloc0(sizeof(DOCK_INFO_RECORD)); + dock_info = &buf.record->dock_info; + buf.record->dock_info_header.dir_version = 1; + buf.record->dock_info_header.dock_type = DOCK_TYPE_WD15; + memcpy (dock_info->dock_description, + "IE_Dock", 7); + dock_info->flash_pkg_version = 0x43; + dock_info->cable_type = CABLE_TYPE_LEGACY; + dock_info->location = 2; + dock_info->component_count = 3; + dock_info->components[0].fw_version = 0xffffffff; + memcpy (dock_info->components[0].description, + "Dock1,EC,MIPS32,IE_Dock,0 :Query 2 0 2 2 0", 42); + dock_info->components[1].fw_version = 0x10108; + memcpy (dock_info->components[1].description, + "Dock1,PC,TI,IE_Dock,0 :Query 2 1 0 2 0", 38); + dock_info->components[2].fw_version = 0xffffffff; + memcpy (dock_info->components[2].description, + "Dock1,Cable,Cyp,IE_Cable,0 :Query 2 2 2 1 0", 43); + out[0] = 0; + out[1] = 1; + fu_provider_dell_inject_fake_data (FU_PROVIDER_DELL(provider), + (guint32 *) &out, + DOCK_NIC_VID, DOCK_NIC_PID, + buf.buf); + fu_provider_dell_device_added_cb (NULL, NULL, + FU_PROVIDER_DELL(provider)); + g_assert (device != NULL); + device = NULL; + g_free (buf.record); + fu_provider_dell_device_removed_cb (NULL, NULL, + FU_PROVIDER_DELL(provider)); + + /* inject an invalid future dock */ + buf.record = g_malloc0(sizeof(DOCK_INFO_RECORD)); + dock_info = &buf.record->dock_info; + buf.record->dock_info_header.dir_version = 1; + buf.record->dock_info_header.dock_type = 50; + memcpy (dock_info->dock_description, + "Future!", 8); + dock_info->flash_pkg_version = 0x00ffffff; + dock_info->cable_type = CABLE_TYPE_UNIV; + dock_info->location = 2; + dock_info->component_count = 1; + dock_info->components[0].fw_version = 0x00ffffff; + memcpy (dock_info->components[0].description, + "Dock1,EC,MIPS32,FUT_Dock,0 :Query 2 0 2 2 0", 43); + out[0] = 0; + out[1] = 1; + fu_provider_dell_inject_fake_data (FU_PROVIDER_DELL(provider), + (guint32 *) &out, + DOCK_NIC_VID, DOCK_NIC_PID, + buf.buf); + fu_provider_dell_device_added_cb (NULL, NULL, + FU_PROVIDER_DELL(provider)); + g_assert (device == NULL); + g_free (buf.record); +} + +#endif + static void fu_provider_rpi_func (void) { @@ -482,7 +879,10 @@ main (int argc, char **argv) g_test_add_func ("/fwupd/pending", fu_pending_func); g_test_add_func ("/fwupd/provider", fu_provider_func); g_test_add_func ("/fwupd/provider{rpi}", fu_provider_rpi_func); +#ifdef HAVE_DELL + g_test_add_func ("/fwupd/provider{dell:tpm}", fu_provider_dell_tpm_func); + g_test_add_func ("/fwupd/provider{dell:dock}", fu_provider_dell_dock_func); +#endif g_test_add_func ("/fwupd/keyring", fu_keyring_func); return g_test_run (); } -