/* 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 command line tool to invoke firmware updater.
 */

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>

#include "futility.h"
#include "updater.h"

#ifdef USE_FLASHROM

enum {
	OPT_DUMMY = 0x100,

	OPT_CCD,
	OPT_EMULATE,
	OPT_FACTORY,
	OPT_FAST,
	OPT_FORCE,
	OPT_GBB_FLAGS,
	OPT_HOST_ONLY,
	OPT_MANIFEST,
	OPT_MODEL,
	OPT_OUTPUT_DIR,
	OPT_PD_IMAGE,
	OPT_QUIRKS,
	OPT_QUIRKS_LIST,
	OPT_REPACK,
	OPT_SERVO,
	OPT_SERVO_NORESET,
	OPT_SERVO_PORT,
	OPT_SIGNATURE,
	OPT_SYS_PROPS,
	OPT_UNPACK,
	OPT_WRITE_PROTECTION,
};

/* Command line options */
static struct option const long_opts[] = {
	/* name  has_arg *flag val */
	{"help", 0, NULL, 'h'},
	{"debug", 0, NULL, 'd'},
	{"verbose", 0, NULL, 'v'},

	{"image", 1, NULL, 'i'},
	{"ec_image", 1, NULL, 'e'},
	{"try", 0, NULL, 't'},
	{"archive", 1, NULL, 'a'},
	{"programmer", 1, NULL, 'p'},
	{"mode", 1, NULL, 'm'},

	{"ccd", 0, NULL, OPT_CCD},
	{"servo", 0, NULL, OPT_SERVO},
	{"servo_port", 1, NULL, OPT_SERVO_PORT},
	{"emulate", 1, NULL, OPT_EMULATE},
	{"factory", 0, NULL, OPT_FACTORY},
	{"fast", 0, NULL, OPT_FAST},
	{"force", 0, NULL, OPT_FORCE},
	{"gbb_flags", 1, NULL, OPT_GBB_FLAGS},
	{"host_only", 0, NULL, OPT_HOST_ONLY},
	{"list-quirks", 0, NULL, OPT_QUIRKS_LIST},
	{"manifest", 0, NULL, OPT_MANIFEST},
	{"model", 1, NULL, OPT_MODEL},
	{"output_dir", 1, NULL, OPT_OUTPUT_DIR},
	{"pd_image", 1, NULL, OPT_PD_IMAGE},
	{"quirks", 1, NULL, OPT_QUIRKS},
	{"repack", 1, NULL, OPT_REPACK},
	{"signature_id", 1, NULL, OPT_SIGNATURE},
	{"sys_props", 1, NULL, OPT_SYS_PROPS},
	{"unpack", 1, NULL, OPT_UNPACK},
	{"wp", 1, NULL, OPT_WRITE_PROTECTION},

	/* TODO(hungte) Remove following deprecated options. */
	{"noupdate_ec", 0, NULL, OPT_HOST_ONLY},
	{"noupdate_pd", 0, NULL, OPT_HOST_ONLY},
	{"nocheck_keys", 0, NULL, OPT_FORCE},
	{"update_main", 0, NULL, OPT_DUMMY},
	{"update_ec", 0, NULL, OPT_DUMMY},
	{"update_pd", 0, NULL, OPT_DUMMY},
	{"check_keys", 0, NULL, OPT_DUMMY},

	{NULL, 0, NULL, 0},
};

static const char * const short_opts = "hdvi:e:ta:m:p:";

static void print_help(int argc, char *argv[])
{
	printf("\n"
		"Usage:  " MYNAME " %s [OPTIONS]\n"
		"\n"
		"Updates firmware in one of the following modes (default to recovery):\n"
		"  autoupdate:\tUpdate RW[A|B], or recovery if RO changed.\n"
		"  recovery:  \tUpdate RW[A&B], (RO, RO:GBB[keys] - if RO changed)\n"
		"  factory:   \tUpdate RW[A&B],  RO, RO:GBB[keys,flags]\n"
		"\n"
		"Note: firmware sections with PRESERVE flags like VPD and\n"
		"      HWID in GBB are always preserved.\n"
		"      GBB flags are preserved in autoupdate and recovery modes.\n"
		"\n"
		"OPTIONS:\n"
		"\n"
		"-i, --image=FILE    \tAP (host) firmware image (image.bin)\n"
		"-e, --ec_image=FILE \tEC firmware image (i.e, ec.bin)\n"
		"    --pd_image=FILE \tPD firmware image (i.e, pd.bin)\n"
		"-t, --try           \tTry A/B update on reboot if possible\n"
		"-a, --archive=PATH  \tRead resources from archive\n"
		"    --unpack=DIR    \tExtracts archive to DIR\n"
		"-p, --programmer=PRG\tChange AP (host) flashrom programmer\n"
		"    --fast          \tReduce read cycles and do not verify\n"
		"    --quirks=LIST   \tSpecify the quirks to apply\n"
		"    --list-quirks   \tPrint all available quirks\n"
		"-m, --mode=MODE     \tRun updater in the specified mode\n"
		"    --manifest      \tScan the archive to print a manifest in JSON\n"
		"\n"
		" * If both --manifest and --fast are specified, the updater\n"
		"   will not scan the archive and simply dump the previously\n"
		"   cached manifest (may be out-dated) from the archive.\n"
		"\n"
		"Legacy and compatibility options:\n"
		"    --factory       \tAlias for --mode=factory\n"
		"    --force         \tForce update (skip checking contents)\n"
		"    --output_dir=DIR\tSpecify the target for --mode=output\n"
		"\n"
		"Debugging and testing options:\n"
		"    --wp=1|0        \tSpecify write protection status\n"
		"    --host_only     \tUpdate only AP (host) firmware\n"
		"    --emulate=FILE  \tEmulate system firmware using file\n"
		"    --model=MODEL   \tOverride system model for images\n"
		"    --gbb_flags=FLAG\tOverride new GBB flags\n"
		"    --ccd           \tDo fast,force,wp=0,p=raiden_debug_spi\n"
		"    --servo         \tFlash using Servo (v2, v4, micro, ...)\n"
		"    --servo_port=PRT\tOverride servod port, implies --servo\n"
		"    --signature_id=S\tOverride signature ID for key files\n"
		"    --sys_props=LIST\tList of system properties to override\n"
		"-d, --debug         \tPrint debugging messages\n"
		"-v, --verbose       \tPrint verbose messages\n"
		"",
		argv[0]);
}

static void prepare_servo_control(const char *control_name, int on)
{
	char *cmd;
	if (!control_name)
		return;

	ASPRINTF(&cmd, "dut-control %s:%s", control_name, on ? "on" : "off");
	free(host_shell(cmd));
	free(cmd);
}

static int do_update(int argc, char *argv[])
{
	struct updater_config *cfg;
	struct updater_config_arguments args = {0};
	int i, errorcnt = 0, do_update = 1;
	int detect_servo = 0;
	const char *prepare_ctrl_name = NULL;
	char *servo_programmer = NULL;
	char *endptr;

	cfg = updater_new_config();
	assert(cfg);

	opterr = 0;
	while ((i = getopt_long(argc, argv, short_opts, long_opts, 0)) != -1) {
		switch (i) {
		case 'h':
			print_help(argc, argv);
			updater_delete_config(cfg);
			return !!errorcnt;
		case 'd':
			debugging_enabled = 1;
			args.verbosity++;
			break;
		case 'v':
			args.verbosity++;
			break;
		case 'i':
			args.image = optarg;
			break;
		case 'e':
			args.ec_image = optarg;
			break;
		case 't':
			args.try_update = 1;
			break;
		case 'a':
			args.archive = optarg;
			break;
		case 'm':
			args.mode = optarg;
			break;
		case 'p':
			args.programmer = optarg;
			break;

		case OPT_PD_IMAGE:
			args.pd_image = optarg;
			break;
		case OPT_REPACK:
			args.repack = optarg;
			ERROR("Sorry, --repack is only for the script.\n");
			errorcnt ++;
			break;
		case OPT_UNPACK:
			args.unpack = optarg;
			break;
		case OPT_QUIRKS:
			args.quirks = optarg;
			break;
		case OPT_QUIRKS_LIST:
			updater_list_config_quirks(cfg);
			updater_delete_config(cfg);
			return 0;
		case OPT_OUTPUT_DIR:
			args.output_dir = optarg;
			break;
		case OPT_MODEL:
			args.model = optarg;
			break;
		case OPT_SIGNATURE:
			args.signature_id = optarg;
			break;
		case OPT_WRITE_PROTECTION:
			args.write_protection = optarg;
			break;
		case OPT_EMULATE:
			args.emulation = optarg;
			break;
		case OPT_SYS_PROPS:
			args.sys_props = optarg;
			break;
		case OPT_MANIFEST:
			args.do_manifest = 1;
			break;
		case OPT_FACTORY:
			args.is_factory = 1;
			break;
		case OPT_HOST_ONLY:
			args.host_only = 1;
			break;
		case OPT_FORCE:
			args.force_update = 1;
			break;
		case OPT_FAST:
			args.fast_update = 1;
			break;
		case OPT_GBB_FLAGS:
			args.gbb_flags = strtoul(optarg, &endptr, 0);
			if (*endptr) {
				ERROR("Invalid flags: %s\n", optarg);
				errorcnt++;
			} else {
				args.override_gbb_flags = 1;
			}
			break;
		case OPT_CCD:
			args.fast_update = 1;
			args.force_update = 1;
			args.write_protection = "0";
			args.programmer = "raiden_debug_spi:target=AP";
			break;
		case OPT_SERVO:
			args.fast_update = 1;
			args.force_update = 1;
			args.write_protection = "0";
			args.host_only = 1;
			detect_servo = 1;
			break;
		case OPT_SERVO_PORT:
			setenv(ENV_SERVOD_PORT, optarg, 1);
			args.fast_update = 1;
			args.force_update = 1;
			args.write_protection = "0";
			args.host_only = 1;
			detect_servo = 1;
			break;

		case OPT_DUMMY:
			break;

		case '?':
			errorcnt++;
			if (optopt)
				ERROR("Unrecognized option: -%c\n", optopt);
			else if (argv[optind - 1])
				ERROR("Unrecognized option (possibly '%s')\n",
				      argv[optind - 1]);
			else
				ERROR("Unrecognized option.\n");
			break;
		default:
			errorcnt++;
			ERROR("Failed parsing options.\n");
		}
	}
	if (optind < argc) {
		errorcnt++;
		ERROR("Unexpected arguments.\n");
	}

	if (!errorcnt && detect_servo) {
		servo_programmer = host_detect_servo(&prepare_ctrl_name);

		if (!servo_programmer)
			errorcnt++;
		else if (!args.programmer)
			args.programmer = servo_programmer;
	}
	/*
	 * Some boards may need to fetch firmware before starting to
	 * update (i.e., in updater_setup_config) so we want to turn on
	 * cpu_fw_spi mode now.
	 */
	prepare_servo_control(prepare_ctrl_name, 1);

	if (!errorcnt)
		errorcnt += updater_setup_config(cfg, &args, &do_update);
	if (!errorcnt && do_update) {
		int r;
		STATUS("Starting firmware updater.\n");
		r = update_firmware(cfg);
		if (r != UPDATE_ERR_DONE) {
			r = VB2_MIN(r, UPDATE_ERR_UNKNOWN);
			ERROR("%s\n", updater_error_messages[r]);
			errorcnt++;
		}
		/* Use stdout for the final result. */
		printf(">> %s: Firmware updater %s.\n",
			errorcnt ? "FAILED": "DONE",
			errorcnt ? "aborted" : "exits successfully");
	}

	prepare_servo_control(prepare_ctrl_name, 0);
	free(servo_programmer);

	updater_delete_config(cfg);
	return !!errorcnt;
}
#define CMD_HELP_STR "Update system firmware"

#else /* USE_FLASHROM */

static int do_update(int argc, char *argv[])
{
	FATAL(MYNAME " was built without flashrom support, `update` command unavailable!\n");
	return -1;
}
#define CMD_HELP_STR "Update system firmware (unavailable in this build)"

#endif /* !USE_FLASHROM */

DECLARE_FUTIL_COMMAND(update, do_update, VBOOT_VERSION_ALL, CMD_HELP_STR);
