/* 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 "crossystem.h"
#include "futility.h"
#include "host_misc.h"
#include "updater.h"

struct quirks_record {
	const char * const match;
	const char * const quirks;
};

/*
 * The 'match by firmware name' is now deprecated. Please do not add any
 * new records below. We now support reading quirks from CBFS, which is
 * easier and more reliable. To do that, create a text file 'updater_quirks'
 * and install to the CBFS.
 *
 * Examples: CL:*3365287, CL:*3351831, CL:*4441527
 */
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_Sarien.",
	  .quirks = "unlock_wilco_me_for_update" },
	{ .match = "Google_Arcada.",
	  .quirks = "unlock_wilco_me_for_update" },
	{ .match = "Google_Drallion.",
	  .quirks = "unlock_wilco_me_for_update" },

	{ .match = "Google_Poppy.", .quirks = "min_platform_version=6" },
	{ .match = "Google_Scarlet.", .quirks = "min_platform_version=1" },
	{ .match = "Google_Trogdor.", .quirks = "min_platform_version=2" },

        /* Legacy custom label units. */
	{ .match = "Google_Hana.", .quirks = "allow_empty_custom_label_tag" },
	{ .match = "Google_Reks.", .quirks = "allow_empty_custom_label_tag" },
	{ .match = "Google_Relm.", .quirks = "allow_empty_custom_label_tag" },
	{ .match = "Google_Wizpig.", .quirks = "allow_empty_custom_label_tag" },
	{ .match = "Google_Enguarde.",
		.quirks = "allow_empty_custom_label_tag" },
	{ .match = "Google_Expresso.",
		.quirks = "allow_empty_custom_label_tag" },
	{ .match = "Google_Veyron_Jaq.",
		.quirks = "allow_empty_custom_label_tag" },
	{ .match = "Google_Veyron_Jerry.",
		.quirks = "allow_empty_custom_label_tag" },
	{ .match = "Google_Veyron_Mighty.",
		.quirks = "allow_empty_custom_label_tag" },

	{ .match = "Google_Phaser.", .quirks = "override_signature_id" },
};

/* Preserves meta data and reload image contents from given file path. */
static int reload_firmware_image(const char *file_path,
				 struct firmware_image *image)
{
	free_firmware_image(image);
	return load_firmware_image(image, file_path, NULL);
}

/*
 * Returns True if the system has EC software sync enabled.
 */
static int is_ec_software_sync_enabled(struct updater_config *cfg)
{
	const struct vb2_gbb_header *gbb;

	/* Check if current system has disabled software sync or no support. */
	if (!(VbGetSystemPropertyInt("vdat_flags") & VBSD_EC_SOFTWARE_SYNC)) {
		INFO("EC Software Sync is not available.\n");
		return 0;
	}

	/* Check if the system has been updated to disable software sync. */
	gbb = find_gbb(&cfg->image);
	if (!gbb) {
		WARN("Invalid AP firmware image.\n");
		return 0;
	}
	if (gbb->flags & VB2_GBB_FLAG_DISABLE_EC_SOFTWARE_SYNC) {
		INFO("EC Software Sync will be disabled in next boot.\n");
		return 0;
	}
	return 1;
}

/*
 * Schedules an EC RO software sync (in next boot) if applicable.
 */
static int ec_ro_software_sync(struct updater_config *cfg)
{
	const char *ec_ro_path;
	uint8_t *ec_ro_data;
	uint32_t ec_ro_len;
	int is_same_ec_ro;
	struct firmware_section ec_ro_sec;
	const char *tmp_path = get_firmware_image_temp_file(
			&cfg->image, &cfg->tempfiles);

	if (!tmp_path)
		return 1;
	find_firmware_section(&ec_ro_sec, &cfg->ec_image, "EC_RO");
	if (!ec_ro_sec.data || !ec_ro_sec.size) {
		ERROR("EC image has invalid section '%s'.\n", "EC_RO");
		return 1;
	}
	ec_ro_path = cbfs_extract_file(tmp_path, FMAP_RO_CBFS, "ecro",
				       &cfg->tempfiles);
	if (!ec_ro_path ||
	    !cbfs_file_exists(tmp_path, FMAP_RO_CBFS, "ecro.hash")) {
		INFO("No valid EC RO for software sync in AP firmware.\n");
		return 1;
	}
	if (vb2_read_file(ec_ro_path, &ec_ro_data, &ec_ro_len) != VB2_SUCCESS) {
		ERROR("Failed to read EC RO.\n");
		return 1;
	}

	is_same_ec_ro = (ec_ro_len <= ec_ro_sec.size &&
			 memcmp(ec_ro_sec.data, ec_ro_data, ec_ro_len) == 0);
	free(ec_ro_data);

	if (!is_same_ec_ro) {
		/* TODO(hungte) If change AP RO is not a problem (hash will be
		 * different, which may be a problem to factory and HWID), or if
		 * we can be be sure this is for developers, extract EC RO and
		 * update AP RO CBFS to trigger EC RO sync with new EC.
		 */
		ERROR("The EC RO contents specified from AP (--image) and EC "
		      "(--ec_image) firmware images are different, cannot "
		      "update by EC RO software sync.\n");
		return 1;
	}
	VbSetSystemPropertyInt("try_ro_sync", 1);
	return 0;
}

/*
 * Returns True if EC is running in RW.
 */
static int is_ec_in_rw(void)
{
	char buf[VB_MAX_STRING_PROPERTY];
	return (VbGetSystemPropertyString("ecfw_act", buf, sizeof(buf)) &&
		strcasecmp(buf, "RW") == 0);
}

/*
 * 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 = get_firmware_image_temp_file(image_to, &cfg->tempfiles);
	if (!tmp_path)
		return -1;

	VB2_DEBUG("Resize image from %u to %u.\n",
		  image_to->size, image_from->size);
	to_write = image_from->size - image_to->size;
	fp = fopen(tmp_path, "ab");
	if (!fp) {
		ERROR("Cannot open temporary file %s.\n", 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) {
		VB2_DEBUG("Target ME not locked.\n");
		return 0;
	}
	/*
	 * b/35568719: We should only update with unlocked ME and let
	 * board-postinst lock it.
	 */
	INFO("Changed Flash Master Values to unlocked.\n");
	memcpy(section.data + flash_master_offset, flash_master,
	       ARRAY_SIZE(flash_master));
	return 0;
}

/*
 * 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_wilco_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[] = {
		0xff, 0xff, 0xff, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 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) {
		VB2_DEBUG("Target ME not locked.\n");
		return 0;
	}
	INFO("Changed Flash Master Values to unlocked.\n");
	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);

	VB2_DEBUG("Minimum required version=%d, current platform version=%d\n",
		  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.\n",
	      min_version, platform_version);
	return -1;
}

/*
 * 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 *old_store;
	char *command;
	const char *temp_image = get_firmware_image_temp_file(
			&cfg->image_current, &cfg->tempfiles);

	if (!temp_image)
		return -1;

	old_store = cbfs_extract_file(temp_image, FMAP_RW_LEGACY,
				      smm_store_name, &cfg->tempfiles);
	if (!old_store) {
		VB2_DEBUG("cbfstool failure or SMM store not available. "
			  "Don't preserve.\n");
		return 0;
	}

	/* Reuse temp_image */
	temp_image = get_firmware_image_temp_file(&cfg->image, &cfg->tempfiles);
	if (!temp_image)
		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);
	free(host_shell(command));
	free(command);

	return reload_firmware_image(temp_image, &cfg->image);
}

/*
 * Update EC (RO+RW) in most reliable way.
 *
 * Some EC will reset TCPC when doing sysjump, and will make rootfs unavailable
 * if the system was boot from USB, or other unexpected issues even if the
 * system was boot from internal disk. To prevent that, try to partial update
 * only RO and expect EC software sync to update RW later, or perform EC RO
 * software sync.
 *
 * Note: EC RO software sync was not fully tested and may cause problems
 *       (b/218612817, b/187789991).
 *       RO-update (without extra sysjump) needs support from flashrom and is
 *       currently disabled.
 *
 * Returns:
 *  EC_RECOVERY_FULL to indicate a full recovery is needed.
 *  EC_RECOVERY_RO to indicate partial update (WP_RO) is needed.
 *  EC_RECOVERY_DONE to indicate EC RO software sync is applied.
 *  Other values to report failure.
 */
static int quirk_ec_partial_recovery(struct updater_config *cfg)
{
	/*
	 * http://crbug.com/1024401: Some EC needs extra header outside EC_RO so
	 * we have to update whole WP_RO, not just EC_RO.
	 */
	const char *ec_ro = "WP_RO";
	struct firmware_image *ec_image = &cfg->ec_image;
	int do_partial = get_config_quirk(QUIRK_EC_PARTIAL_RECOVERY, cfg);

	if (!do_partial) {
		/* Need full update. */
	} else if (!firmware_section_exists(ec_image, ec_ro)) {
		INFO("EC image does not have section '%s'.\n", ec_ro);
		/* Need full update. */
	} else if (!is_ec_software_sync_enabled(cfg)) {
		/* Message already printed, need full update. */
	} else if (is_ec_in_rw()) {
		WARN("EC Software Sync detected, will only update EC RO. "
		     "The contents in EC RW will be updated after reboot.\n");
		return EC_RECOVERY_RO;
	} else if (ec_ro_software_sync(cfg) == 0) {
		INFO("EC RO and RW should be updated after reboot.\n");
		return EC_RECOVERY_DONE;
	}

	WARN("Update EC RO+RW and may cause unexpected error later. "
	     "See http://crbug.com/782427#c4 for more information.\n");
	return EC_RECOVERY_FULL;
}

/*
 * Preserve ME during firmware update.
 *
 * Updating ME region while SoC is in S0 state is an unsupported use-case. On
 * recent platforms, we are seeing issues more frequently because of this use-
 * case. For the firmware updates performed using firmware update archive,
 * preserve the ME region so that it gets updated in the successive boot.
 *
 * Returns:
 *   1 to signal ME needs to be preserved.
 *   0 to signal ME does not need to be preserved.
 */
static int quirk_preserve_me(struct updater_config *cfg)
{
	/* For a factory update donot preserve ME. */
	if (cfg->factory_update) {
	       WARN("Factory update. Not preserving ME.\n");
	       return 0;
	}

	/*
	 * For a non-archive update, the post boot script that updates ME
	 * does not have access to the firmware image after the boot. Hence
	 * donot preserve ME.
	 */
	if (!cfg->archive) {
		WARN("Update using a non-archive image. Not preserving ME.\n");
		return 0;
	}

	/*
	 * b/213706510: subratabanik@ confirmed CSE may modify itself while we
	 * are doing system update, and currently the 'preserve' is done by
	 * flashing the same (e.g., "previously read") contents to skip erasing
	 * and writing; so we have to use the diff image to prevent contents
	 * being changed when writing.
	 */
	cfg->use_diff_image = 1;

	return 1;
}

/*
 * Disable checking platform compatibility.
 */
static int quirk_no_check_platform(struct updater_config *cfg)
{
	WARN("Disabled checking platform. You are on your own.\n");
	cfg->check_platform = 0;
	return 0;
}

/*
 * Disable verifying contents after flashing.
 */
static int quirk_no_verify(struct updater_config *cfg)
{
	WARN("Disabled verifying flashed contents. You are on your own.\n");
	cfg->do_verify = 0;
	return 0;
}

/*
 * 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_WILCO_ME_FOR_UPDATE];
	quirks->name = "unlock_wilco_me_for_update";
	quirks->help = "Unlock ME for safe lockdown.";
	quirks->apply = quirk_unlock_wilco_me_for_update;

	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_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;

	quirks = &cfg->quirks[QUIRK_ALLOW_EMPTY_CUSTOM_LABEL_TAG];
	quirks->name = "allow_empty_custom_label_tag";
	quirks->help = "chromium/906962; allow devices without custom label "
		       "tags set to use default keys.";
	quirks->apply = NULL;  /* Simple config. */

	quirks = &cfg->quirks[QUIRK_EC_PARTIAL_RECOVERY];
	quirks->name = "ec_partial_recovery";
	quirks->help = "chromium/1024401; recover EC by partial RO update.";
	quirks->apply = quirk_ec_partial_recovery;

	quirks = &cfg->quirks[QUIRK_OVERRIDE_SIGNATURE_ID];
	quirks->name = "override_signature_id";
	quirks->help = "chromium/146876241; override signature id for "
			"devices shipped with different root key.";
	quirks->apply = NULL; /* Simple config. */

	quirks = &cfg->quirks[QUIRK_PRESERVE_ME];
	quirks->name = "preserve_me";
	quirks->help = "b/165590952; Preserve ME during firmware update except "
		       "for factory update or developer images.";
	quirks->apply = quirk_preserve_me;

	quirks = &cfg->quirks[QUIRK_NO_CHECK_PLATFORM];
	quirks->name = "no_check_platform";
	quirks->help = "Do not check platform name.";
	quirks->apply = quirk_no_check_platform;

	quirks = &cfg->quirks[QUIRK_NO_VERIFY];
	quirks->name = "no_verify";
	quirks->help = "Do not verify when flashing.";
	quirks->apply = quirk_no_verify;

	quirks = &cfg->quirks[QUIRK_EXTRA_RETRIES];
	quirks->name = "extra_retries";
	quirks->help = "Extra retries when writing to system firmware.";
	quirks->apply = NULL;  /* Simple config. */

	quirks = &cfg->quirks[QUIRK_EXTERNAL_FLASHROM];
	quirks->name = "external_flashrom";
	quirks->help = "Use external flashrom to access the system firmware.";
	quirks->apply = NULL;  /* Simple config. */
}

/*
 * Gets the default quirk config string from target image name.
 * Returns a string (in same format as --quirks) to load or NULL if no quirks.
 */
const char * const updater_get_model_quirks(struct updater_config *cfg)
{
	const char *pattern = cfg->image.ro_version;
	int i;

	if (!pattern) {
		VB2_DEBUG("Cannot identify system for default quirks.\n");
		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;
		VB2_DEBUG("Found system default quirks: %s\n", r->quirks);
		return r->quirks;
	}
	return NULL;
}

/*
 * Gets the quirk config string from target image CBFS.
 * Returns a string (in same format as --quirks) to load or NULL if no quirks.
 */
char *updater_get_cbfs_quirks(struct updater_config *cfg)
{
	const char *entry_name = "updater_quirks";
	const char *cbfs_region = "FW_MAIN_A";
	struct firmware_section cbfs_section;

	/* Before invoking cbfstool, try to search for CBFS file name. */
	find_firmware_section(&cbfs_section, &cfg->image, cbfs_region);
	if (!cbfs_section.size || !memmem(cbfs_section.data, cbfs_section.size,
					  entry_name, strlen(entry_name))) {
		if (!cbfs_section.size)
			VB2_DEBUG("Missing region: %s\n", cbfs_region);
		else
			VB2_DEBUG("Cannot find entry: %s\n", entry_name);
		return NULL;
	}

	const char *tmp_path = get_firmware_image_temp_file(
			&cfg->image, &cfg->tempfiles);
	uint8_t *data = NULL;
	uint32_t size = 0;

	/* Although the name exists, it may not be a real file. */
	if (!cbfs_file_exists(tmp_path, cbfs_region, entry_name)) {
		VB2_DEBUG("Found string '%s' but not a file.\n", entry_name);
		return NULL;
	}

	VB2_DEBUG("Found %s from CBFS %s\n", entry_name, cbfs_region);
	tmp_path = cbfs_extract_file(tmp_path, cbfs_region, entry_name,
				     &cfg->tempfiles);
	if (!tmp_path ||
	    vb2_read_file(tmp_path, &data, &size) != VB2_SUCCESS) {
		ERROR("Failed to read [%s] from CBFS [%s].\n",
		      entry_name, cbfs_region);
		return NULL;
	}
	VB2_DEBUG("Got quirks (%u bytes): %s\n", size, data);
	return (char *)data;
}

/*
 * Overrides signature id if the device was shipped with known
 * special rootkey.
 */
int quirk_override_signature_id(struct updater_config *cfg,
				struct model_config *model,
				const char **signature_id)
{
	const char * const DOPEFISH_KEY_HASH =
				"9a1f2cc319e2f2e61237dc51125e35ddd4d20984";

	/* b/146876241 */
	assert(model);
	if (strcmp(model->name, "phaser360") == 0) {
		struct firmware_image *image = &cfg->image_current;
		const char *key_hash = get_firmware_rootkey_hash(image);
		if (key_hash && strcmp(key_hash, DOPEFISH_KEY_HASH) == 0) {
			const char * const sig_dopefish = "phaser360-dopefish";
			WARN("A Phaser360 with Dopefish rootkey - "
			     "override signature_id to '%s'.\n", sig_dopefish);
			*signature_id = sig_dopefish;
		}
	}

	return 0;
}
