/* * Copyright (C) 2017 Richard Hughes * Copyright (C) 2019 9elements Agency GmbH * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include "fu-flashrom-device.h" #define SELFCHECK_TRUE 1 struct FuPluginData { struct flashrom_flashctx *flashctx; struct flashrom_programmer *flashprog; gchar *guid; /* GUID from quirks that activated this plugin */ }; typedef FuPluginData FuFlashromPlugin; #define FU_FLASHROM_PLUGIN(o) fu_plugin_get_data(FU_PLUGIN(o)) static void fu_flashrom_plugin_to_string(FuPlugin *plugin, guint idt, GString *str) { FuFlashromPlugin *self = FU_FLASHROM_PLUGIN(plugin); if (self->guid != NULL) fu_string_append(str, idt, "Guid", self->guid); } static int fu_flashrom_plugin_debug_cb(enum flashrom_log_level lvl, const char *fmt, va_list args) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wformat-nonliteral" g_autofree gchar *tmp = g_strdup_vprintf(fmt, args); #pragma clang diagnostic pop g_autofree gchar *str = fu_strstrip(tmp); if (g_strcmp0(str, "OK.") == 0 || g_strcmp0(str, ".") == 0) return 0; switch (lvl) { case FLASHROM_MSG_ERROR: case FLASHROM_MSG_WARN: g_warning("%s", str); break; case FLASHROM_MSG_INFO: g_debug("%s", str); break; case FLASHROM_MSG_DEBUG: case FLASHROM_MSG_DEBUG2: if (g_getenv("FWUPD_FLASHROM_VERBOSE") != NULL) g_debug("%s", str); break; case FLASHROM_MSG_SPEW: default: break; } return 0; } static void fu_flashrom_plugin_device_set_version(FuPlugin *plugin, FuDevice *device) { FuContext *ctx = fu_plugin_get_context(plugin); const gchar *version; const gchar *version_major; const gchar *version_minor; /* as-is */ version = fu_context_get_hwid_value(ctx, FU_HWIDS_KEY_BIOS_VERSION); if (version != NULL) { /* some Lenovo hardware requires a specific prefix for the EC, * so strip it before we use ensure-semver */ if (strlen(version) > 9 && g_str_has_prefix(version, "CBET")) version += 9; /* this may not "stick" if there are no numeric chars */ fu_device_set_version(device, version); if (fu_device_get_version(device) != NULL) return; } /* component parts only */ version_major = fu_context_get_hwid_value(ctx, FU_HWIDS_KEY_BIOS_MAJOR_RELEASE); version_minor = fu_context_get_hwid_value(ctx, FU_HWIDS_KEY_BIOS_MINOR_RELEASE); if (version_major != NULL && version_minor != NULL) { g_autofree gchar *tmp = g_strdup_printf("%s.%s.0", version_major, version_minor); fu_device_set_version(device, tmp); return; } } static gboolean fu_flashrom_plugin_device_set_bios_info(FuPlugin *plugin, FuDevice *device, GError **error) { FuContext *ctx = fu_plugin_get_context(plugin); const guint8 *buf; gsize bufsz; guint32 bios_char = 0x0; g_autoptr(GBytes) bios_table = NULL; /* get SMBIOS info */ bios_table = fu_context_get_smbios_data(ctx, FU_SMBIOS_STRUCTURE_TYPE_BIOS, error); if (bios_table == NULL) return FALSE; /* ROM size if not already been quirked */ buf = g_bytes_get_data(bios_table, &bufsz); if (fu_device_get_firmware_size_max(device) == 0) { guint8 bios_sz = 0x0; if (fu_memread_uint8_safe(buf, bufsz, 0x9, &bios_sz, NULL)) { guint64 firmware_size = (bios_sz + 1) * 64 * 1024; fu_device_set_firmware_size_max(device, firmware_size); } } /* BIOS characteristics */ if (fu_memread_uint32_safe(buf, bufsz, 0xa, &bios_char, G_LITTLE_ENDIAN, NULL)) { if ((bios_char & (1 << 11)) == 0) { fu_device_inhibit(device, "bios-characteristics", "Not supported from SMBIOS"); } } return TRUE; } static void fu_flashrom_plugin_device_set_hwids(FuPlugin *plugin, FuDevice *device) { FuContext *ctx = fu_plugin_get_context(plugin); static const gchar *hwids[] = { "HardwareID-3", "HardwareID-4", "HardwareID-5", "HardwareID-6", "HardwareID-10", /* a more useful one for coreboot branch detection */ FU_HWIDS_KEY_MANUFACTURER "&" FU_HWIDS_KEY_FAMILY "&" FU_HWIDS_KEY_PRODUCT_NAME "&" FU_HWIDS_KEY_PRODUCT_SKU "&" FU_HWIDS_KEY_BIOS_VENDOR, }; /* don't include FU_HWIDS_KEY_BIOS_VERSION */ for (guint i = 0; i < G_N_ELEMENTS(hwids); i++) { g_autofree gchar *str = NULL; str = fu_context_get_hwid_replace_value(ctx, hwids[i], NULL); if (str != NULL) fu_device_add_instance_id(device, str); } } static FuDevice * fu_flashrom_plugin_add_device(FuPlugin *plugin, const gchar *guid, FuIfdRegion region, GError **error) { FuContext *ctx = fu_plugin_get_context(plugin); FuFlashromPlugin *self = FU_FLASHROM_PLUGIN(plugin); const gchar *dmi_vendor; const gchar *product = fu_context_get_hwid_value(ctx, FU_HWIDS_KEY_PRODUCT_NAME); const gchar *vendor = fu_context_get_hwid_value(ctx, FU_HWIDS_KEY_MANUFACTURER); const gchar *region_str = fu_ifd_region_to_string(region); g_autofree gchar *name = g_strdup_printf("%s (%s)", product, region_str); g_autoptr(FuDevice) device = fu_flashrom_device_new(ctx, self->flashctx, region); g_autoptr(GError) error_local = NULL; fu_device_set_name(device, name); fu_device_set_vendor(device, vendor); fu_device_add_instance_str(device, "VENDOR", vendor); fu_device_add_instance_str(device, "PRODUCT", product); fu_device_add_instance_strup(device, "REGION", region_str); if (!fu_device_build_instance_id(device, error, "FLASHROM", "VENDOR", "PRODUCT", "REGION", NULL)) return NULL; /* add this so we can attach board-specific quirks */ fu_device_add_instance_str(FU_DEVICE(device), "GUID", guid); if (!fu_device_build_instance_id(FU_DEVICE(device), error, "FLASHROM", "GUID", NULL)) return NULL; /* use same VendorID logic as with UEFI */ dmi_vendor = fu_context_get_hwid_value(ctx, FU_HWIDS_KEY_BIOS_VENDOR); if (dmi_vendor != NULL) { g_autofree gchar *vendor_id = g_strdup_printf("DMI:%s", dmi_vendor); fu_device_add_vendor_id(FU_DEVICE(device), vendor_id); } fu_flashrom_plugin_device_set_version(plugin, device); fu_flashrom_plugin_device_set_hwids(plugin, device); if (!fu_flashrom_plugin_device_set_bios_info(plugin, device, &error_local)) g_warning("failed to set bios info: %s", error_local->message); if (!fu_device_setup(device, error)) return NULL; /* success */ fu_plugin_device_add(plugin, device); return g_steal_pointer(&device); } static void fu_flashrom_plugin_device_registered(FuPlugin *plugin, FuDevice *device) { g_autoptr(FuDevice) me_device = NULL; FuFlashromPlugin *self = FU_FLASHROM_PLUGIN(plugin); const gchar *me_region_str = fu_ifd_region_to_string(FU_IFD_REGION_ME); /* we're only interested in a device from intel-spi plugin that corresponds to ME * region of IFD */ if (g_strcmp0(fu_device_get_plugin(device), "intel_spi") != 0) return; if (g_strcmp0(fu_device_get_logical_id(device), me_region_str) != 0) return; me_device = fu_flashrom_plugin_add_device(plugin, self->guid, FU_IFD_REGION_ME, NULL); if (me_device == NULL) return; /* unlock operation requires device to be locked */ if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_LOCKED)) fu_device_add_flag(me_device, FWUPD_DEVICE_FLAG_LOCKED); } static gboolean fu_flashrom_plugin_coldplug(FuPlugin *plugin, FuProgress *progress, GError **error) { FuFlashromPlugin *self = FU_FLASHROM_PLUGIN(plugin); g_autoptr(FuDevice) device = fu_flashrom_plugin_add_device(plugin, self->guid, FU_IFD_REGION_BIOS, error); return (device != NULL); } /* finds GUID that activated this plugin */ static const gchar * fu_flashrom_plugin_find_guid(FuPlugin *plugin, GError **error) { FuContext *ctx = fu_plugin_get_context(plugin); GPtrArray *hwids = fu_context_get_hwid_guids(ctx); for (guint i = 0; i < hwids->len; i++) { const gchar *guid = g_ptr_array_index(hwids, i); const gchar *plugin_name = fu_context_lookup_quirk_by_id(ctx, guid, FU_QUIRKS_PLUGIN); if (g_strcmp0(plugin_name, "flashrom") == 0) return guid; } g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no HwIDs found"); return NULL; } static gboolean fu_flashrom_plugin_startup(FuPlugin *plugin, FuProgress *progress, GError **error) { gint rc; const gchar *guid; FuFlashromPlugin *self = FU_FLASHROM_PLUGIN(plugin); /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 5, "find-guid"); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 90, "init"); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 5, "probe"); guid = fu_flashrom_plugin_find_guid(plugin, error); if (guid == NULL) return FALSE; fu_progress_step_done(progress); self->guid = g_strdup(guid); if (flashrom_init(SELFCHECK_TRUE)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "flashrom initialization error"); return FALSE; } flashrom_set_log_callback(fu_flashrom_plugin_debug_cb); fu_progress_step_done(progress); if (flashrom_programmer_init(&self->flashprog, "internal", NULL)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "programmer initialization failed"); return FALSE; } rc = flashrom_flash_probe(&self->flashctx, self->flashprog, NULL); if (rc == 3) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "flash probe failed: multiple chips were found"); return FALSE; } if (rc == 2) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "flash probe failed: no chip was found"); return FALSE; } if (rc != 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "flash probe failed: unknown error"); return FALSE; } fu_progress_step_done(progress); return TRUE; } static gboolean fu_flashrom_plugin_unlock(FuPlugin *self, FuDevice *device, GError **error) { return fu_flashrom_device_unlock(FU_FLASHROM_DEVICE(device), error); } static void fu_flashrom_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); (void)fu_plugin_alloc_data(plugin, sizeof(FuFlashromPlugin)); fu_plugin_add_rule(plugin, FU_PLUGIN_RULE_METADATA_SOURCE, "linux_lockdown"); fu_plugin_add_rule(plugin, FU_PLUGIN_RULE_CONFLICTS, "coreboot"); /* obsoleted */ fu_plugin_add_flag(plugin, FWUPD_PLUGIN_FLAG_REQUIRE_HWID); fu_plugin_add_flag(plugin, FWUPD_PLUGIN_FLAG_MEASURE_SYSTEM_INTEGRITY); } static void fu_flashrom_plugin_finalize(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); FuFlashromPlugin *self = FU_FLASHROM_PLUGIN(plugin); if (self->flashctx != NULL) flashrom_flash_release(self->flashctx); if (self->flashprog != NULL) flashrom_programmer_shutdown(self->flashprog); g_free(self->guid); /* G_OBJECT_CLASS(fu_flashrom_plugin_parent_class)->finalize() not required as modular */ } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->constructed = fu_flashrom_plugin_constructed; vfuncs->finalize = fu_flashrom_plugin_finalize; vfuncs->to_string = fu_flashrom_plugin_to_string; vfuncs->device_registered = fu_flashrom_plugin_device_registered; vfuncs->startup = fu_flashrom_plugin_startup; vfuncs->coldplug = fu_flashrom_plugin_coldplug; vfuncs->unlock = fu_flashrom_plugin_unlock; }