summaryrefslogtreecommitdiff
path: root/futility/updater_quirks.c
diff options
context:
space:
mode:
Diffstat (limited to 'futility/updater_quirks.c')
-rw-r--r--futility/updater_quirks.c357
1 files changed, 357 insertions, 0 deletions
diff --git a/futility/updater_quirks.c b/futility/updater_quirks.c
new file mode 100644
index 00000000..8c924a37
--- /dev/null
+++ b/futility/updater_quirks.c
@@ -0,0 +1,357 @@
+/*
+ * Copyright 2018 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ *
+ * The board-specific quirks needed by firmware updater.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "futility.h"
+#include "host_misc.h"
+#include "updater.h"
+
+struct quirks_record {
+ const char * const match;
+ const char * const quirks;
+};
+
+static const struct quirks_record quirks_records[] = {
+ { .match = "Google_Whirlwind.", .quirks = "enlarge_image" },
+ { .match = "Google_Arkham.", .quirks = "enlarge_image" },
+ { .match = "Google_Storm.", .quirks = "enlarge_image" },
+ { .match = "Google_Gale.", .quirks = "enlarge_image" },
+
+ { .match = "Google_Chell.", .quirks = "unlock_me_for_update" },
+ { .match = "Google_Lars.", .quirks = "unlock_me_for_update" },
+ { .match = "Google_Sentry.", .quirks = "unlock_me_for_update" },
+ { .match = "Google_Asuka.", .quirks = "unlock_me_for_update" },
+ { .match = "Google_Caroline.", .quirks = "unlock_me_for_update" },
+ { .match = "Google_Cave.", .quirks = "unlock_me_for_update" },
+
+ { .match = "Google_Eve.",
+ .quirks = "unlock_me_for_update,eve_smm_store" },
+
+ { .match = "Google_Poppy.", .quirks = "min_platform_version=6" },
+ { .match = "Google_Scarlet.", .quirks = "min_platform_version=1" },
+
+ { .match = "Google_Snow.", .quirks = "daisy_snow_dual_model" },
+};
+
+/*
+ * Helper function to write a firmware image into file on disk.
+ * Returns the result from vb2_write_file.
+ */
+static int write_image(const char *file_path, struct firmware_image *image)
+{
+ return vb2_write_file(file_path, image->data, image->size);
+}
+
+/* Preserves meta data and reload image contents from given file path. */
+static int reload_firmware_image(const char *file_path, struct firmware_image *image)
+{
+ const char *programmer = image->programmer;
+ free_firmware_image(image);
+ image->programmer = programmer;
+ return load_firmware_image(image, file_path, NULL);
+}
+
+/*
+ * Quirk to enlarge a firmware image to match flash size. This is needed by
+ * devices using multiple SPI flash with different sizes, for example 8M and
+ * 16M. The image_to will be padded with 0xFF using the size of image_from.
+ * Returns 0 on success, otherwise failure.
+ */
+static int quirk_enlarge_image(struct updater_config *cfg)
+{
+ struct firmware_image *image_from = &cfg->image_current,
+ *image_to = &cfg->image;
+ const char *tmp_path;
+ size_t to_write;
+ FILE *fp;
+
+ if (image_from->size <= image_to->size)
+ return 0;
+
+ tmp_path = updater_create_temp_file(cfg);
+ if (!tmp_path)
+ return -1;
+
+ DEBUG("Resize image from %u to %u.", image_to->size, image_from->size);
+ to_write = image_from->size - image_to->size;
+ write_image(tmp_path, image_to);
+ fp = fopen(tmp_path, "ab");
+ if (!fp) {
+ ERROR("Cannot open temporary file %s.", tmp_path);
+ return -1;
+ }
+ while (to_write-- > 0)
+ fputc('\xff', fp);
+ fclose(fp);
+ return reload_firmware_image(tmp_path, image_to);
+}
+
+/*
+ * Quirk to unlock a firmware image with SI_ME (management engine) when updating
+ * so the system has a chance to make sure SI_ME won't be corrupted on next boot
+ * before locking the Flash Master values in SI_DESC.
+ * Returns 0 on success, otherwise failure.
+ */
+static int quirk_unlock_me_for_update(struct updater_config *cfg)
+{
+ struct firmware_section section;
+ struct firmware_image *image_to = &cfg->image;
+ const int flash_master_offset = 128;
+ const uint8_t flash_master[] = {
+ 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff,
+ 0xff, 0xff
+ };
+
+ find_firmware_section(&section, image_to, FMAP_SI_DESC);
+ if (section.size < flash_master_offset + ARRAY_SIZE(flash_master))
+ return 0;
+ if (memcmp(section.data + flash_master_offset, flash_master,
+ ARRAY_SIZE(flash_master)) == 0) {
+ DEBUG("Target ME not locked.");
+ return 0;
+ }
+ /*
+ * b/35568719: We should only update with unlocked ME and let
+ * board-postinst lock it.
+ */
+ printf("%s: Changed Flash Master Values to unlocked.\n", __FUNCTION__);
+ memcpy(section.data + flash_master_offset, flash_master,
+ ARRAY_SIZE(flash_master));
+ return 0;
+}
+
+/*
+ * Checks and returns 0 if the platform version of current system is larger
+ * or equal to given number, otherwise non-zero.
+ */
+static int quirk_min_platform_version(struct updater_config *cfg)
+{
+ int min_version = get_config_quirk(QUIRK_MIN_PLATFORM_VERSION, cfg);
+ int platform_version = get_system_property(SYS_PROP_PLATFORM_VER, cfg);
+
+ DEBUG("Minimum required version=%d, current platform version=%d",
+ min_version, platform_version);
+
+ if (platform_version >= min_version)
+ return 0;
+ ERROR("Need platform version >= %d (current is %d). "
+ "This firmware will only run on newer systems.",
+ min_version, platform_version);
+ return -1;
+}
+
+/*
+ * Adjust firmware image according to running platform version.
+ * Returns 0 if success, non-zero if error.
+ */
+static int quirk_daisy_snow_dual_model(struct updater_config *cfg)
+{
+ /*
+ * The daisy-snow firmware should be packed as RO, RW_A=x16, RW_B=x8.
+ * RO update for x8 and RO EC update for all are no longer supported.
+ */
+ struct firmware_section a, b;
+ int i, is_x8 = 0, is_x16 = 0;
+ const char * const x8_versions[] = {
+ "DVT",
+ "PVT",
+ "PVT2",
+ "MP",
+ };
+ const char * const x16_versions[] = {
+ "MPx16", /* Rev 4 */
+ "MP2", /* Rev 5 */
+ };
+ char *platform_version = host_shell("mosys platform version");
+
+ for (i = 0; i < ARRAY_SIZE(x8_versions) && !is_x8; i++) {
+ if (strcmp(x8_versions[i], platform_version) == 0)
+ is_x8 = 1;
+ }
+ for (i = 0; i < ARRAY_SIZE(x16_versions) && !is_x8 && !is_x16; i++) {
+ if (strcmp(x16_versions[i], platform_version) == 0)
+ is_x16 = 1;
+ }
+ printf("%s: Platform version: %s (original value: %s)\n", __FUNCTION__,
+ is_x8 ? "x8" : is_x16 ? "x16": "unknown", platform_version);
+ free(platform_version);
+
+ find_firmware_section(&a, &cfg->image, FMAP_RW_SECTION_A);
+ find_firmware_section(&b, &cfg->image, FMAP_RW_SECTION_B);
+
+ if (cfg->ec_image.data) {
+ ERROR("EC RO update is not supported with this quirk.");
+ return -1;
+ }
+ if (!a.data || !b.data || a.size != b.size) {
+ ERROR("Invalid firmware image: %s", cfg->image.file_name);
+ return -1;
+ }
+ if (memcmp(a.data, b.data, a.size) == 0) {
+ ERROR("Input image must have both x8 and x16 firmware.");
+ return -1;
+ }
+
+ if (is_x16) {
+ memmove(b.data, a.data, a.size);
+ free(cfg->image.rw_version_b);
+ cfg->image.rw_version_b = strdup(cfg->image.rw_version_a);
+ } else if (is_x8) {
+ memmove(a.data, b.data, b.size);
+ free(cfg->image.rw_version_a);
+ cfg->image.rw_version_a = strdup(cfg->image.rw_version_b);
+ /* Need to use RO from current system. */
+ if (!cfg->image_current.data &&
+ load_system_firmware(cfg, &cfg->image_current) != 0) {
+ ERROR("Cannot get system RO contents");
+ return -1;
+ }
+ preserve_firmware_section(&cfg->image_current, &cfg->image,
+ FMAP_RO_SECTION);
+ } else {
+ ERROR("Unknown platform, cannot update.");
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * Extracts files from a CBFS on given region (section) of image_file.
+ * Returns the path to a temporary file on success, otherwise NULL.
+ */
+static const char *extract_cbfs_file(struct updater_config *cfg,
+ const char *image_file,
+ const char *cbfs_region,
+ const char *cbfs_name)
+{
+ const char *output = updater_create_temp_file(cfg);
+ char *command, *result;
+
+ ASPRINTF(&command, "cbfstool \"%s\" extract -r %s -n \"%s\" "
+ "-f \"%s\" 2>&1", image_file, cbfs_region,
+ cbfs_name, output);
+
+ result = host_shell(command);
+ free(command);
+
+ if (!*result)
+ output = NULL;
+
+ free(result);
+ return output;
+}
+
+/*
+ * Quirk to help preserving SMM store on devices without a dedicated "SMMSTORE"
+ * FMAP section. These devices will store "smm store" file in same CBFS where
+ * the legacy boot loader lives (i.e, FMAP RW_LEGACY).
+ * Note this currently has dependency on external program "cbstool".
+ * Returns 0 if the SMM store is properly preserved, or if the system is not
+ * available to do that (problem in cbfstool, or no "smm store" in current
+ * system firmware). Otherwise non-zero as failure.
+ */
+static int quirk_eve_smm_store(struct updater_config *cfg)
+{
+ const char *smm_store_name = "smm store";
+ const char *temp_image = updater_create_temp_file(cfg);
+ const char *old_store;
+ char *command;
+
+ if (write_image(temp_image, &cfg->image_current) != VBERROR_SUCCESS)
+ return -1;
+
+ old_store = extract_cbfs_file(cfg, temp_image, FMAP_RW_LEGACY,
+ smm_store_name);
+ if (!old_store) {
+ DEBUG("cbfstool failure or SMM store not available. "
+ "Don't preserve.");
+ return 0;
+ }
+
+ /* Reuse temp_image. */
+ if (write_image(temp_image, &cfg->image) != VBERROR_SUCCESS)
+ return -1;
+
+ /* crosreview.com/1165109: The offset is fixed at 0x1bf000. */
+ ASPRINTF(&command,
+ "cbfstool \"%s\" remove -r %s -n \"%s\" 2>/dev/null; "
+ "cbfstool \"%s\" add -r %s -n \"%s\" -f \"%s\" "
+ " -t raw -b 0x1bf000", temp_image, FMAP_RW_LEGACY,
+ smm_store_name, temp_image, FMAP_RW_LEGACY,
+ smm_store_name, old_store);
+ host_shell(command);
+ free(command);
+
+ return reload_firmware_image(temp_image, &cfg->image);
+}
+
+/*
+ * Registers known quirks to a updater_config object.
+ */
+void updater_register_quirks(struct updater_config *cfg)
+{
+ struct quirk_entry *quirks;
+
+ assert(ARRAY_SIZE(cfg->quirks) == QUIRK_MAX);
+ quirks = &cfg->quirks[QUIRK_ENLARGE_IMAGE];
+ quirks->name = "enlarge_image";
+ quirks->help = "Enlarge firmware image by flash size.";
+ quirks->apply = quirk_enlarge_image;
+
+ quirks = &cfg->quirks[QUIRK_MIN_PLATFORM_VERSION];
+ quirks->name = "min_platform_version";
+ quirks->help = "Minimum compatible platform version "
+ "(also known as Board ID version).";
+ quirks->apply = quirk_min_platform_version;
+
+ quirks = &cfg->quirks[QUIRK_UNLOCK_ME_FOR_UPDATE];
+ quirks->name = "unlock_me_for_update";
+ quirks->help = "b/35568719; only lock management engine in "
+ "board-postinst.";
+ quirks->apply = quirk_unlock_me_for_update;
+
+ quirks = &cfg->quirks[QUIRK_DAISY_SNOW_DUAL_MODEL];
+ quirks->name = "daisy_snow_dual_model";
+ quirks->help = "b/35525858; needs an image RW A=[model x16], B=x8.";
+ quirks->apply = quirk_daisy_snow_dual_model;
+
+ quirks = &cfg->quirks[QUIRK_EVE_SMM_STORE];
+ quirks->name = "eve_smm_store";
+ quirks->help = "b/70682365; preserve UEFI SMM store without "
+ "dedicated FMAP section.";
+ quirks->apply = quirk_eve_smm_store;
+}
+
+/*
+ * Gets the default quirk config string for target image.
+ * Returns a string (in same format as --quirks) to load or NULL if no quirks.
+ */
+const char * const updater_get_default_quirks(struct updater_config *cfg)
+{
+ const char *pattern = cfg->image.ro_version;
+ int i;
+
+ if (!pattern) {
+ DEBUG("Cannot identify system for default quirks.");
+ return NULL;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(quirks_records); i++) {
+ const struct quirks_record *r = &quirks_records[i];
+ if (strncmp(r->match, pattern, strlen(r->match)) != 0)
+ continue;
+ DEBUG("Found system default quirks: %s", r->quirks);
+ return r->quirks;
+ }
+ return NULL;
+}