/* * Copyright (C) 2018 Dell Inc. * All rights reserved. * * This software and associated documentation (if any) is furnished * under a license and may only be used or copied in accordance * with the terms of the license. * * This file is provided under a dual MIT/LGPLv2 license. When using or * redistributing this file, you may do so under either license. * Dell Chooses the MIT license part of Dual MIT/LGPLv2 license agreement. * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #include "config.h" #include #include "fu-common-version.h" #include "fu-usb-device.h" #include "fwupd-error.h" #include "fu-dell-dock-common.h" #define I2C_EC_ADDRESS 0xec #define EC_CMD_SET_DOCK_PKG 0x01 #define EC_CMD_GET_DOCK_INFO 0x02 #define EC_CMD_GET_DOCK_DATA 0x03 #define EC_CMD_GET_DOCK_TYPE 0x05 #define EC_CMD_MODIFY_LOCK 0x0a #define EC_CMD_RESET 0x0b #define EC_CMD_REBOOT 0x0c #define EC_CMD_PASSIVE 0x0d #define EC_GET_FW_UPDATE_STATUS 0x0f #define EXPECTED_DOCK_INFO_SIZE 0xb7 #define EXPECTED_DOCK_TYPE 0x04 #define TBT_MODE_MASK 0x01 #define BIT_SET(x,y) (x |= (1<data->board_id); summary = fu_device_get_metadata (device, board_type_str); if (summary != NULL) fu_device_set_summary (device, summary); } const gchar * fu_dell_dock_ec_get_module_type (FuDevice *device) { FuDellDockEc *self = FU_DELL_DOCK_EC (device); switch (self->data->module_type) { case MODULE_TYPE_SINGLE: return "WD19"; case MODULE_TYPE_DUAL: return "WD19DC"; case MODULE_TYPE_TBT: return "WD19TB"; default: return NULL; } } gboolean fu_dell_dock_ec_needs_tbt (FuDevice *device) { FuDellDockEc *self = FU_DELL_DOCK_EC (device); gboolean port0_tbt_mode = self->data->port0_dock_status & TBT_MODE_MASK; /* check for TBT module type */ if (self->data->module_type != MODULE_TYPE_TBT) return FALSE; g_debug ("found thunderbolt dock, port mode: %d", port0_tbt_mode); return !port0_tbt_mode; } gboolean fu_dell_dock_ec_tbt_passive (FuDevice *device) { FuDellDockEc *self = FU_DELL_DOCK_EC (device); if (self->passive_flow > 0) { self->passive_flow |= PASSIVE_TBT_MASK; return TRUE; } return FALSE; } static const gchar* fu_dell_dock_devicetype_to_str (guint device_type, guint sub_type) { switch (device_type) { case FU_DELL_DOCK_DEVICETYPE_MAIN_EC: return "EC"; case FU_DELL_DOCK_DEVICETYPE_MST: return "MST"; case FU_DELL_DOCK_DEVICETYPE_TBT: return "Thunderbolt"; case FU_DELL_DOCK_DEVICETYPE_HUB: if (sub_type == SUBTYPE_GEN2) return "USB 3.1 Gen2"; else if (sub_type == SUBTYPE_GEN1) return "USB 3.1 Gen1"; return NULL; case FU_DELL_DOCK_DEVICETYPE_PD: return "PD"; default: return NULL; } } static gboolean fu_dell_dock_ec_read (FuDevice *device, guint32 cmd, gsize length, GBytes **bytes, GError **error) { /* The first byte of result data will be the size of the return, hide this from callers */ guint8 result_length = length + 1; g_autoptr(GBytes) bytes_local = NULL; const guint8 *result; g_return_val_if_fail (device != NULL, FALSE); g_return_val_if_fail (fu_device_get_proxy (device) != NULL, FALSE); g_return_val_if_fail (bytes != NULL, FALSE); if (!fu_dell_dock_hid_i2c_read (fu_device_get_proxy (device), cmd, result_length, &bytes_local, &ec_base_settings, error)) { g_prefix_error (error, "read over HID-I2C failed: "); return FALSE; } result = g_bytes_get_data (bytes_local, NULL); /* first byte of result should be size of our data */ if (result[0] != length) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Invalid result data: %d expected %" G_GSIZE_FORMAT, result[0], length); return FALSE; } *bytes = g_bytes_new (result + 1, length); return TRUE; } static gboolean fu_dell_dock_ec_write (FuDevice *device, gsize length, guint8 *data, GError **error) { g_return_val_if_fail (device != NULL, FALSE); g_return_val_if_fail (fu_device_get_proxy (device) != NULL, FALSE); g_return_val_if_fail (length > 1, FALSE); if (!fu_dell_dock_hid_i2c_write (fu_device_get_proxy (device), data, length, &ec_base_settings, error)) { g_prefix_error (error, "write over HID-I2C failed: "); return FALSE; } return TRUE; } static gboolean fu_dell_dock_is_valid_dock (FuDevice *device, GError **error) { g_autoptr(GBytes) data = NULL; const guint8 *result = NULL; g_return_val_if_fail (device != NULL, FALSE); if (!fu_dell_dock_ec_read (device, EC_CMD_GET_DOCK_TYPE, 1, &data, error)) { g_prefix_error (error, "Failed to query dock type: "); return FALSE; } result = g_bytes_get_data (data, NULL); if (result == NULL || *result != EXPECTED_DOCK_TYPE) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "No valid dock was found"); return FALSE; } return TRUE; } static gboolean fu_dell_dock_ec_get_dock_info (FuDevice *device, GError **error) { FuDellDockEc *self = FU_DELL_DOCK_EC (device); const FuDellDockDockInfoHeader *header = NULL; const FuDellDockEcQueryEntry *device_entry = NULL; const FuDellDockEcAddrMap *map = NULL; const gchar *hub_version; guint32 oldest_base_pd = 0; g_autoptr(GBytes) data = NULL; g_return_val_if_fail (device != NULL, FALSE); if (!fu_dell_dock_ec_read (device, EC_CMD_GET_DOCK_INFO, EXPECTED_DOCK_INFO_SIZE, &data, error)) { g_prefix_error (error, "Failed to query dock info: "); return FALSE; } if (!g_bytes_get_data (data, NULL)) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "Failed to read dock info"); return FALSE; } header = (FuDellDockDockInfoHeader *) g_bytes_get_data (data, NULL); if (!header) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "Failed to parse dock info"); return FALSE; } /* guard against EC not yet ready and fail init */ if (header->total_devices == 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID, "No bridge devices detected, dock may be booting up"); return FALSE; } g_debug ("%u devices [%u->%u]", header->total_devices, header->first_index, header->last_index); device_entry = (FuDellDockEcQueryEntry *) ((guint8 *) header + sizeof(FuDellDockDockInfoHeader)); for (guint i = 0; i < header->total_devices; i++) { const gchar *type_str; map = &(device_entry[i].ec_addr_map); type_str = fu_dell_dock_devicetype_to_str (map->device_type, map->sub_type); if (type_str == NULL) continue; g_debug ("#%u: %s in %s (A: %u I: %u)", i, type_str, (map->location == LOCATION_BASE) ? "Base" : "Module", map->arg, map->instance); g_debug ("\tVersion32: %08x\tVersion8: %x %x %x %x", device_entry[i].version.version_32, device_entry[i].version.version_8[0], device_entry[i].version.version_8[1], device_entry[i].version.version_8[2], device_entry[i].version.version_8[3]); /* BCD but guint32 */ if (map->device_type == FU_DELL_DOCK_DEVICETYPE_MAIN_EC) { self->raw_versions->ec_version = device_entry[i].version.version_32; self->ec_version = g_strdup_printf ( "%02x.%02x.%02x.%02x", device_entry[i].version.version_8[0], device_entry[i].version.version_8[1], device_entry[i].version.version_8[2], device_entry[i].version.version_8[3]); g_debug ("\tParsed version %s", self->ec_version); fu_device_set_version_format (FU_DEVICE (self), FWUPD_VERSION_FORMAT_QUAD); fu_device_set_version (FU_DEVICE (self), self->ec_version); } else if (map->device_type == FU_DELL_DOCK_DEVICETYPE_MST) { self->raw_versions->mst_version = device_entry[i].version.version_32; /* guard against invalid MST version read from EC */ if (!fu_dell_dock_test_valid_byte (device_entry[i].version.version_8, 1)) { g_warning ("[EC Bug] EC read invalid MST version %08x", device_entry[i].version.version_32); continue; } self->mst_version = g_strdup_printf ("%02x.%02x.%02x", device_entry[i].version.version_8[1], device_entry[i].version.version_8[2], device_entry[i].version.version_8[3]); g_debug ("\tParsed version %s", self->mst_version); } else if (map->device_type == FU_DELL_DOCK_DEVICETYPE_TBT && self->data->module_type == MODULE_TYPE_TBT) { /* guard against invalid Thunderbolt version read from EC */ if (!fu_dell_dock_test_valid_byte (device_entry[i].version.version_8, 2)) { g_warning ("[EC bug] EC read invalid Thunderbolt version %08x", device_entry[i].version.version_32); continue; } self->raw_versions->tbt_version = device_entry[i].version.version_32; self->tbt_version = g_strdup_printf ("%02x.%02x", device_entry[i].version.version_8[2], device_entry[i].version.version_8[3]); g_debug ("\tParsed version %s", self->tbt_version); } else if (map->device_type == FU_DELL_DOCK_DEVICETYPE_HUB) { g_debug ("\thub subtype: %u", map->sub_type); if (map->sub_type == SUBTYPE_GEN2) self->raw_versions->hub2_version = device_entry[i].version.version_32; else if (map->sub_type == SUBTYPE_GEN1) self->raw_versions->hub1_version = device_entry[i].version.version_32; } else if (map->device_type == FU_DELL_DOCK_DEVICETYPE_PD && map->location == LOCATION_BASE && map->sub_type == 0) { if (oldest_base_pd == 0 || device_entry[i].version.version_32 < oldest_base_pd) oldest_base_pd = GUINT32_TO_BE (device_entry[i].version.version_32); g_debug ("\tParsed version: %02x.%02x.%02x.%02x", device_entry[i].version.version_8[0], device_entry[i].version.version_8[1], device_entry[i].version.version_8[2], device_entry[i].version.version_8[3]); } } /* Thunderbolt SKU takes a little longer */ if (self->data->module_type == MODULE_TYPE_TBT) { guint64 tmp = fu_device_get_install_duration (device); fu_device_set_install_duration (device, tmp + 20); } /* minimum EC version this code will support */ if (fu_common_vercmp_full (self->ec_version, self->ec_minimum_version, FWUPD_VERSION_FORMAT_QUAD) < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "dock containing EC version %s is not supported", self->ec_version); return FALSE; } fu_device_set_version_lowest (device, self->ec_minimum_version); /* Determine if the passive flow should be used when flashing */ hub_version = fu_device_get_version (fu_device_get_proxy (device)); if (fu_common_vercmp_full (hub_version, "1.42", FWUPD_VERSION_FORMAT_PAIR) >= 0) { g_debug ("using passive flow"); self->passive_flow = PASSIVE_REBOOT_MASK; fu_device_add_flag (device, FWUPD_DEVICE_FLAG_SKIPS_RESTART); } else { g_debug ("not using passive flow (EC: %s Hub2: %s)", self->ec_version, hub_version); } return TRUE; } static gboolean fu_dell_dock_ec_get_dock_data (FuDevice *device, GError **error) { FuDellDockEc *self = FU_DELL_DOCK_EC (device); g_autoptr(GBytes) data = NULL; g_autoptr(GString) name = NULL; gchar service_tag[8] = {0x00}; const guint8 *result; gsize length = sizeof(FuDellDockDockDataStructure); g_autofree gchar *bundled_serial = NULL; FuDellDockECFWUpdateStatus status; g_return_val_if_fail (device != NULL, FALSE); if (!fu_dell_dock_ec_read (device, EC_CMD_GET_DOCK_DATA, length, &data, error)) { g_prefix_error (error, "Failed to query dock info: "); return FALSE; } result = g_bytes_get_data (data, NULL); if (result == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "Failed to read dock data"); return FALSE; } if (g_bytes_get_size (data) != length) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Unexpected dock data size %" G_GSIZE_FORMAT, g_bytes_get_size (data)); return FALSE; } memcpy (self->data, result, length); /* guard against EC not yet ready and fail init */ name = g_string_new (self->data->marketing_name); if (name->len > 0) fu_device_set_name (device, name->str); else g_warning ("[EC bug] Invalid dock name detected"); if (self->data->module_type >= 0xfe) g_warning ("[EC bug] Invalid module type 0x%02x", self->data->module_type); /* set serial number */ memcpy (service_tag, self->data->service_tag, 7); bundled_serial = g_strdup_printf ("%s/%08" G_GUINT64_FORMAT, service_tag, self->data->module_serial); fu_device_set_serial (device, bundled_serial); /* copy this for being able to send in next commit transaction */ self->raw_versions->pkg_version = self->data->dock_firmware_pkg_ver; /* read if passive update pending */ if (!fu_dell_dock_get_ec_status (device, &status, error)) return FALSE; /* make sure this hardware spin matches our expecations */ if (self->data->board_id >= self->board_min) { if (status != FW_UPDATE_IN_PROGRESS) { fu_dell_dock_ec_set_board (device); fu_device_add_flag (device, FWUPD_DEVICE_FLAG_UPDATABLE); } else { fu_device_add_flag (device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION); fu_device_set_update_error (device, "An update is pending " "next time the dock is " "unplugged"); } } else { g_warning ("This utility does not support this board, disabling updates for %s", fu_device_get_name (device)); } return TRUE; } static void fu_dell_dock_ec_to_string (FuDevice *device, guint idt, GString *str) { FuDellDockEc *self = FU_DELL_DOCK_EC (device); gchar service_tag[8] = {0x00}; fu_common_string_append_ku (str, idt, "BoardId", self->data->board_id); fu_common_string_append_ku (str, idt, "PowerSupply", self->data->power_supply_wattage); fu_common_string_append_kx (str, idt, "StatusPort0", self->data->port0_dock_status); fu_common_string_append_kx (str, idt, "StatusPort1", self->data->port1_dock_status); memcpy (service_tag, self->data->service_tag, 7); fu_common_string_append_kv (str, idt, "ServiceTag", service_tag); fu_common_string_append_ku (str, idt, "Configuration", self->data->dock_configuration); fu_common_string_append_kx (str, idt, "PackageFirmwareVersion", self->data->dock_firmware_pkg_ver); fu_common_string_append_ku (str, idt, "ModuleSerial", self->data->module_serial); fu_common_string_append_ku (str, idt, "OriginalModuleSerial", self->data->original_module_serial); fu_common_string_append_ku (str, idt, "Type", self->data->dock_type); fu_common_string_append_kx (str, idt, "ModuleType", self->data->module_type); fu_common_string_append_kv (str, idt, "MinimumEc", self->ec_minimum_version); fu_common_string_append_ku (str, idt, "PassiveFlow", self->passive_flow); } gboolean fu_dell_dock_ec_modify_lock (FuDevice *device, guint8 target, gboolean unlocked, GError **error) { FuDellDockEc *self = FU_DELL_DOCK_EC (device); guint32 cmd; g_return_val_if_fail (device != NULL, FALSE); g_return_val_if_fail (target != 0, FALSE); cmd = EC_CMD_MODIFY_LOCK | /* cmd */ 2 << 8 | /* length of data arguments */ target << 16 | /* device to operate on */ unlocked << 24; /* unlock/lock */ if (!fu_dell_dock_ec_write (device, 4, (guint8 *) &cmd, error)) { g_prefix_error (error, "Failed to unlock device %d: ", target); return FALSE; } g_debug ("Modified lock for %d to %d through %s (%s)", target, unlocked, fu_device_get_name (device), fu_device_get_id (device)); if (unlocked) BIT_SET (self->dock_unlock_status, target); else BIT_CLEAR (self->dock_unlock_status, target); g_debug ("current overall unlock status: 0x%08x", self->dock_unlock_status); return TRUE; } static gboolean fu_dell_dock_ec_reset (FuDevice *device, GError **error) { guint16 cmd = EC_CMD_RESET; g_return_val_if_fail (device != NULL, FALSE); return fu_dell_dock_ec_write (device, 2, (guint8 *) &cmd, error); } static gboolean fu_dell_dock_ec_activate (FuDevice *device, GError **error) { FuDellDockECFWUpdateStatus status; /* read if passive update pending */ if (!fu_dell_dock_get_ec_status (device, &status, error)) return FALSE; if (status != FW_UPDATE_IN_PROGRESS) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "No firmware update pending for %s", fu_device_get_name (device)); return FALSE; } return fu_dell_dock_ec_reset (device, error); } gboolean fu_dell_dock_ec_reboot_dock (FuDevice *device, GError **error) { FuDellDockEc *self = FU_DELL_DOCK_EC (device); g_return_val_if_fail (device != NULL, FALSE); if (self->passive_flow > 0) { guint32 cmd = EC_CMD_PASSIVE | /* cmd */ 1 << 8 | /* length of data arguments */ self->passive_flow << 16; g_debug ("activating passive flow (%x) for %s", self->passive_flow, fu_device_get_name (device)); return fu_dell_dock_ec_write (device, 3, (guint8 *) &cmd, error); } else { guint16 cmd = EC_CMD_REBOOT; g_debug ("rebooting %s", fu_device_get_name (device)); return fu_dell_dock_ec_write (device, 2, (guint8 *) &cmd, error); } return TRUE; } static gboolean fu_dell_dock_get_ec_status (FuDevice *device, FuDellDockECFWUpdateStatus *status_out, GError **error) { g_autoptr(GBytes) data = NULL; const guint8 *result = NULL; g_return_val_if_fail (device != NULL, FALSE); g_return_val_if_fail (status_out != NULL, FALSE); if (!fu_dell_dock_ec_read (device, EC_GET_FW_UPDATE_STATUS, 1, &data, error)) { g_prefix_error (error, "Failed to read FW update status: "); return FALSE; } result = g_bytes_get_data (data, NULL); if (!result) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "Failed to read FW update status"); return FALSE; } *status_out = *result; return TRUE; } const gchar* fu_dell_dock_ec_get_tbt_version (FuDevice *device) { FuDellDockEc *self = FU_DELL_DOCK_EC (device); return self->tbt_version; } const gchar* fu_dell_dock_ec_get_mst_version (FuDevice *device) { FuDellDockEc *self = FU_DELL_DOCK_EC (device); return self->mst_version; } guint32 fu_dell_dock_ec_get_status_version (FuDevice *device) { FuDellDockEc *self = FU_DELL_DOCK_EC (device); return self->raw_versions->pkg_version; } gboolean fu_dell_dock_ec_commit_package (FuDevice *device, GBytes *blob_fw, GError **error) { FuDellDockEc *self = FU_DELL_DOCK_EC (device); gsize length = 0; const guint8 *data = g_bytes_get_data (blob_fw, &length); g_autofree guint8 *payload = g_malloc0 (length + 2); g_return_val_if_fail (device != NULL, FALSE); g_return_val_if_fail (blob_fw != NULL, FALSE); if (length != sizeof(FuDellDockDockPackageFWVersion)) { g_set_error (error, G_IO_ERR, G_IO_ERROR_INVALID_DATA, "Invalid package size %" G_GSIZE_FORMAT, length); return FALSE; } memcpy (self->raw_versions, data, length); g_debug ("Committing (%zu) bytes ", sizeof(FuDellDockDockPackageFWVersion)); g_debug ("\tec_version: %x", self->raw_versions->ec_version); g_debug ("\tmst_version: %x", self->raw_versions->mst_version); g_debug ("\thub1_version: %x", self->raw_versions->hub1_version); g_debug ("\thub2_version: %x", self->raw_versions->hub2_version); g_debug ("\ttbt_version: %x", self->raw_versions->tbt_version); g_debug ("\tpkg_version: %x", self->raw_versions->pkg_version); payload [0] = EC_CMD_SET_DOCK_PKG; payload [1] = length; memcpy (payload + 2, data, length); if (!fu_dell_dock_ec_write (device, length + 2, payload, error)) { g_prefix_error (error, "Failed to query dock info: "); return FALSE; } return TRUE; } static gboolean fu_dell_dock_ec_write_fw (FuDevice *device, FuFirmware *firmware, FwupdInstallFlags flags, GError **error) { FuDellDockEc *self = FU_DELL_DOCK_EC (device); FuDellDockECFWUpdateStatus status = FW_UPDATE_IN_PROGRESS; guint8 progress1 = 0, progress0 = 0; gsize fw_size = 0; const guint8 *data; gsize write_size = 0; gsize nwritten = 0; guint32 address = 0 | 0xff << 24; g_autofree gchar *dynamic_version = NULL; g_autoptr(GBytes) fw = NULL; g_return_val_if_fail (device != NULL, FALSE); g_return_val_if_fail (FU_IS_FIRMWARE (firmware), FALSE); /* get default image */ fw = fu_firmware_get_image_default_bytes (firmware, error); if (fw == NULL) return FALSE; data = g_bytes_get_data (fw, &fw_size); write_size = (fw_size / HIDI2C_MAX_WRITE) >= 1 ? HIDI2C_MAX_WRITE : fw_size; dynamic_version = g_strndup ((gchar *) data + self->blob_version_offset, 11); g_debug ("writing EC firmware version %s", dynamic_version); if (!fu_dell_dock_ec_modify_lock (device, self->unlock_target, TRUE, error)) return FALSE; if (!fu_dell_dock_hid_raise_mcu_clock (fu_device_get_proxy (device), TRUE, error)) return FALSE; fu_device_set_status (device, FWUPD_STATUS_DEVICE_ERASE); if (!fu_dell_dock_hid_erase_bank (fu_device_get_proxy (device), 0xff, error)) return FALSE; fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE); do { /* last packet */ if (fw_size - nwritten < write_size) write_size = fw_size - nwritten; if (!fu_dell_dock_hid_write_flash (fu_device_get_proxy (device), address, data, write_size, error)) { g_prefix_error (error, "write over HID failed: "); return FALSE; } fu_device_set_progress_full (device, nwritten, fw_size); nwritten += write_size; data += write_size; address += write_size; } while (nwritten < fw_size); if (!fu_dell_dock_hid_raise_mcu_clock (fu_device_get_proxy (device), FALSE, error)) return FALSE; /* dock will reboot to re-read; this is to appease the daemon */ fu_device_set_version_format (device, FWUPD_VERSION_FORMAT_QUAD); fu_device_set_version (device, dynamic_version); /* activate passive behavior */ if (self->passive_flow) self->passive_flow |= PASSIVE_RESET_MASK; if (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_SKIPS_RESTART)) { g_debug ("Skipping EC reset per quirk request"); fu_device_add_flag (device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION); return TRUE; } if (!fu_dell_dock_ec_reset (device, error)) return FALSE; /* notify daemon that this device will need to replug */ fu_dell_dock_will_replug (device); /* poll for completion status */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_BUSY); while (status != FW_UPDATE_COMPLETE) { g_autoptr(GError) error_local = NULL; if (!fu_dell_dock_hid_get_ec_status (fu_device_get_proxy (device), &progress1, &progress0, error)) { g_prefix_error (error, "Failed to read scratch: "); return FALSE; } g_debug ("Read %u and %u from scratch", progress1, progress0); if (progress0 > 100) progress0 = 100; fu_device_set_progress_full (device, progress0, 100); /* This is expected to fail until update is done */ if (!fu_dell_dock_get_ec_status (device, &status, &error_local)) { g_debug ("Flash EC Received result: %s (status %u)", error_local->message, status); return TRUE; } if (status == FW_UPDATE_AUTHENTICATION_FAILED) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "invalid EC firmware image"); return FALSE; } } fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART); return TRUE; } static gboolean fu_dell_dock_ec_set_quirk_kv (FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuDellDockEc *self = FU_DELL_DOCK_EC (device); if (g_strcmp0 (key, "DellDockUnlockTarget") == 0) { guint64 tmp = fu_common_strtoull (value); if (tmp < G_MAXUINT8) { self->unlock_target = tmp; return TRUE; } g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid DellDockUnlockTarget"); return FALSE; } if (g_strcmp0 (key, "DellDockBoardMin") == 0) { guint64 tmp = fu_common_strtoull (value); if (tmp < G_MAXUINT8) { self->board_min = tmp; return TRUE; } g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid DellDockBoardMin"); return FALSE; } if (g_strcmp0 (key, "DellDockVersionLowest") == 0) { self->ec_minimum_version = g_strdup (value); return TRUE; } if (g_str_has_prefix (key, "DellDockBoard")) { fu_device_set_metadata (device, key, value); return TRUE; } if (g_strcmp0 (key, "DellDockBlobVersionOffset") == 0) { self->blob_version_offset = fu_common_strtoull (value); return TRUE; } /* failed */ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "quirk key not supported"); return FALSE; } static gboolean fu_dell_dock_ec_probe (FuDevice *device, GError **error) { /* this will trigger setting up all the quirks */ fu_device_add_instance_id (device, DELL_DOCK_EC_INSTANCE_ID); return TRUE; } static gboolean fu_dell_dock_ec_query (FuDevice *device, GError **error) { if (!fu_dell_dock_ec_get_dock_data (device, error)) return FALSE; return fu_dell_dock_ec_get_dock_info (device, error); } static gboolean fu_dell_dock_ec_setup (FuDevice *device, GError **error) { g_autoptr(GError) error_local = NULL; GPtrArray *children; /* if query looks bad, wait a few seconds and retry */ if (!fu_dell_dock_ec_query (device, &error_local)) { if (g_error_matches (error_local, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID)) { g_warning ("%s", error_local->message); g_usleep (2 * G_USEC_PER_SEC); if (!fu_dell_dock_ec_query (device, error)) return FALSE; } else { g_propagate_error (error, g_steal_pointer (&error_local)); return FALSE; } } /* call setup on all the children we produced */ children = fu_device_get_children (device); for (guint i=0 ; i < children->len; i++) { FuDevice *child = g_ptr_array_index (children, i); g_autoptr(FuDeviceLocker) locker = NULL; g_debug ("setup %s", fu_device_get_name (child)); locker = fu_device_locker_new (child, error); if (locker == NULL) return FALSE; } return TRUE; } static gboolean fu_dell_dock_ec_open (FuDevice *device, GError **error) { if (!fu_device_open (fu_device_get_proxy (device), error)) return FALSE; return fu_dell_dock_is_valid_dock (device, error); } static gboolean fu_dell_dock_ec_close (FuDevice *device, GError **error) { return fu_device_close (fu_device_get_proxy (device), error); } static void fu_dell_dock_ec_finalize (GObject *object) { FuDellDockEc *self = FU_DELL_DOCK_EC (object); g_free (self->ec_version); g_free (self->mst_version); g_free (self->tbt_version); g_free (self->data); g_free (self->raw_versions); g_free (self->ec_minimum_version); G_OBJECT_CLASS (fu_dell_dock_ec_parent_class)->finalize (object); } static void fu_dell_dock_ec_init (FuDellDockEc *self) { self->data = g_new0 (FuDellDockDockDataStructure, 1); self->raw_versions = g_new0 (FuDellDockDockPackageFWVersion, 1); fu_device_set_protocol (FU_DEVICE (self), "com.dell.dock"); } static void fu_dell_dock_ec_class_init (FuDellDockEcClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); object_class->finalize = fu_dell_dock_ec_finalize; klass_device->activate = fu_dell_dock_ec_activate; klass_device->to_string = fu_dell_dock_ec_to_string; klass_device->probe = fu_dell_dock_ec_probe; klass_device->setup = fu_dell_dock_ec_setup; klass_device->open = fu_dell_dock_ec_open; klass_device->close = fu_dell_dock_ec_close; klass_device->write_firmware = fu_dell_dock_ec_write_fw; klass_device->set_quirk_kv = fu_dell_dock_ec_set_quirk_kv; } FuDellDockEc * fu_dell_dock_ec_new (FuDevice *proxy) { FuDellDockEc *self = NULL; self = g_object_new (FU_TYPE_DELL_DOCK_EC, NULL); fu_device_set_proxy (FU_DEVICE (self), proxy); fu_device_set_physical_id (FU_DEVICE (self), fu_device_get_physical_id (proxy)); fu_device_set_logical_id (FU_DEVICE (self), "ec"); return self; }