/* Copyright 2016 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.
 */

#include "byteorder.h"
#include "ccd_config.h"
#include "console.h"
#include "dcrypto.h"
#include "extension.h"
#include "gpio.h"
#include "hooks.h"
#include "physical_presence.h"
#include "registers.h"
#include "spi.h"
#include "spi_flash.h"
#include "system.h"
#include "task.h"
#include "timer.h"
#include "tpm_registers.h"
#include "tpm_vendor_cmds.h"
#include "usb_spi.h"
#include "usb_spi_board.h"
#include "watchdog.h"

#define CPRINTS(format, args...) cprints(CC_USB, format, ## args)

#ifdef CONFIG_SPI_HASH
/* Don't hash more than this at once */
#define MAX_SPI_HASH_SIZE (4 * 1024 * 1024)

/*
 * Buffer size to use for reading and hashing.  This must be a multiple of the
 * SHA256 block size (64 bytes) and at least 4 less than the maximum SPI
 * transaction size for H1 (0x80 bytes).  So, 64.
 */
#define SPI_HASH_CHUNK_SIZE 64

/* Timeout for auto-disabling SPI hash device, in microseconds */
#define SPI_HASH_TIMEOUT_US (60 * SECOND)

/* Current device for SPI hashing */
static uint8_t spi_hash_device = USB_SPI_DISABLE;

/*
 * Do we need to use NPCX7 gang programming mode?
 *
 * If 0, then we hold the EC in reset the whole time we've acquired the SPI
 * bus, to keep the EC from accessing it.
 *
 * If 1, then:
 *
 *	When we acquire the EC SPI bus, we need to reset the EC, assert the
 *	gang programmer enable, then take the EC out of reset so its boot ROM
 *	can map the EC's internal SPI bus to the EC gang programmer pins.
 *
 *	When we relinquish the EC SPI bus, we need to reset the EC again while
 *	keeping gang programmer deasserted, then take the EC out of reset.  The
 *	EC will then boot normally.
 */
static uint8_t use_npcx_gang_mode;

/*
 * Device and gang mode selected by last spihash command, for use by
 * spi_hash_pp_done().
 */
static uint8_t spi_hash_new_device;
static uint8_t spi_hash_new_gang_mode;

static void spi_hash_inactive_timeout(void);
DECLARE_DEFERRED(spi_hash_inactive_timeout);

/*
 * Set to true when AP RO verification shortcut is enabled. Helps to prevent
 * concurrent USB SPI operations over CCD.
 */
static bool shortcut_active_;
#ifdef CONFIG_AP_RO_VERIFICATION
bool usb_spi_shortcut_active(void)
{
	return shortcut_active_;
}
#endif /* CONFIG_AP_RO_VERIFICATION */
#endif /* CONFIG_SPI_HASH */


/*****************************************************************************/
/*
 * Mutex and variable for tracking whether the SPI bus is used by the USB
 * connection or hashing commands.
 *
 * Access these ONLY through set_spi_bus_user() and get_spi_bus_user(), to
 * ensure thread-safe access to the SPI bus.
 */
static struct mutex spi_bus_user_mutex;
static enum spi_bus_user_t {
	SPI_BUS_USER_NONE = 0,
	SPI_BUS_USER_USB,
	SPI_BUS_USER_HASH
} spi_bus_user = SPI_BUS_USER_NONE;

/*
 * If True, don't touch the reset signals while setting up AP usb spi. This
 * doesn't affect spi hash.
 */
static uint8_t usb_spi_custom_reset;

/**
 * Set who's using the SPI bus.
 *
 * This is thread-safe and will not block if someone owns the bus.  You can't
 * take the bus if someone else has it, and you can only free it if you hold
 * it.  It has no extra effect if you already own the bus.
 *
 * @param user		What bus user is asking?
 * @param want_bus	Do we want the bus (!=0) or no longer want it (==0)?
 *
 * @return EC_SUCCESS, or non-zero error code.
 */
static int set_spi_bus_user(enum spi_bus_user_t user, int want_bus)
{
	int rv = EC_SUCCESS;

	/*
	 * Serialize access to bus user variable, but don't mutex lock the
	 * entire bus because that would freeze USB or the console instead of
	 * just failing.
	 */
	mutex_lock(&spi_bus_user_mutex);

	if (want_bus) {
		/* Can only take the bus if it's free or we already own it */
		if (spi_bus_user == SPI_BUS_USER_NONE)
			spi_bus_user = user;
		else if (spi_bus_user != user)
			rv = EC_ERROR_BUSY;
	} else {
		/* Can only free the bus if it was ours */
		if (spi_bus_user == user)
			spi_bus_user = SPI_BUS_USER_NONE;
		else
			rv = EC_ERROR_BUSY;
	}

	mutex_unlock(&spi_bus_user_mutex);

	return rv;
}

/**
 * Get the current SPI bus user.
 */
static enum spi_bus_user_t get_spi_bus_user(void)
{
	return spi_bus_user;
}

/*****************************************************************************/
/* Methods to enable / disable the SPI bus and pin mux */

static void disable_ec_ap_spi(uint8_t custom_reset)
{
	int was_ap_spi_en = gpio_get_level(GPIO_AP_FLASH_SELECT);

	/* Disable EC SPI access. */
	gpio_set_level(GPIO_EC_FLASH_SELECT, 0);

	/* Disable AP SPI access. */
	if (was_ap_spi_en) {
		/*
		 * The fact that AP SPI access was enabled means that the EC was
		 * held in reset.  Therefore, it needs to be released here.
		 */
		gpio_set_level(GPIO_AP_FLASH_SELECT, 0);
		/*
		 * Some boards require special EC_RST_L SYS_RST_L behavior.
		 * Don't touch the signals if the user requested custom reset
		 * behavior.
		 */
		if (!custom_reset) {
			deassert_ec_rst();
			deassert_sys_rst();
		}
	}
}

static void enable_ec_spi(void)
{
	/* Select EC flash */
	gpio_set_level(GPIO_AP_FLASH_SELECT, 0);
	gpio_set_level(GPIO_EC_FLASH_SELECT, 1);

	/*
	 * Note that we don't hold the EC in reset here.  This is because some
	 * ECs with internal SPI flash cannot be held in reset in order to
	 * access the flash.
	 */
}

static void enable_ap_spi(uint8_t custom_reset)
{
	/* Select AP flash */
	gpio_set_level(GPIO_AP_FLASH_SELECT, 1);
	gpio_set_level(GPIO_EC_FLASH_SELECT, 0);

	/*
	 * On some systems SYS_RST_L is not level sensitive, so the only way to
	 * be sure we're holding the AP in reset is to hold the EC in reset.
	 * Don't touch the reset signals if the user requested a custom reset
	 * setup.
	 */
	if (!custom_reset)
		assert_ec_rst();
}

/**
 * Enable the pin mux to the SPI controller port.
 */
static void enable_spi_pinmux(void)
{
	GWRITE_FIELD(PINMUX, DIOA4_CTL, PD, 0);    /* SPI_MOSI */
	GWRITE_FIELD(PINMUX, DIOA8_CTL, PD, 0);    /* SPI_CLK */

	/* Connect DIO A4, A8, and A14 to the SPI peripheral */
	GWRITE(PINMUX, DIOA4_SEL, 0); /* SPI_MOSI */
	GWRITE(PINMUX, DIOA8_SEL, 0); /* SPI_CS_L */
	/* Set SPI_CS to be an internal pull up */
	GWRITE_FIELD(PINMUX, DIOA14_CTL, PU, 1);

	CPRINTS("%s: %s", __func__,
		gpio_get_level(GPIO_AP_FLASH_SELECT) ? "AP" : "EC");

	spi_enable(CONFIG_SPI_FLASH_PORT, 1);

	/*
	 * Need to provide enough time for the SPI bus to stabilize
	 * (b/154966209).
	 */
	msleep(2);
}

/**
 * Disable the pin mux to the SPI controller port.
 */
static void disable_spi_pinmux(void)
{
	spi_enable(CONFIG_SPI_FLASH_PORT, 0);

	/* Disconnect SPI peripheral to tri-state pads */
	/* Disable internal pull up */
	GWRITE_FIELD(PINMUX, DIOA14_CTL, PU, 0);
	/* TODO: Implement way to get the gpio */
	ASSERT(GREAD(PINMUX, GPIO0_GPIO7_SEL) == GC_PINMUX_DIOA4_SEL);
	ASSERT(GREAD(PINMUX, GPIO0_GPIO8_SEL) == GC_PINMUX_DIOA8_SEL);

	GWRITE_FIELD(PINMUX, DIOA4_CTL, PD, 1);    /* SPI_MOSI */
	GWRITE_FIELD(PINMUX, DIOA8_CTL, PD, 1);    /* SPI_CLK */

	/* Set SPI MOSI, CLK, and CS_L as inputs */
	GWRITE(PINMUX, DIOA4_SEL, GC_PINMUX_GPIO0_GPIO7_SEL);
	GWRITE(PINMUX, DIOA8_SEL, GC_PINMUX_GPIO0_GPIO8_SEL);
}

/*****************************************************************************/
/* USB SPI methods */

/*
 * Check that the CCD capability for the host is enabled and the SPI bus isn't
 * active.
 */
static int usb_spi_check_host_accessible(int host)
{
	if (host == USB_SPI_EC) {
		if (!ccd_is_cap_enabled(CCD_CAP_EC_FLASH)) {
			CPRINTS("%s: EC access denied", __func__);
			return EC_ERROR_ACCESS_DENIED;
		}
	} else if (host == USB_SPI_AP) {
		if (!ccd_is_cap_enabled(CCD_CAP_AP_FLASH)) {
			CPRINTS("%s: AP access denied", __func__);
			return EC_ERROR_ACCESS_DENIED;
		}
	} else {
		CPRINTS("%s: device %d not supported", __func__, host);
		return EC_ERROR_INVAL;
	}

	if (set_spi_bus_user(SPI_BUS_USER_USB, 1) != EC_SUCCESS) {
		CPRINTS("%s: bus in use", __func__);
		return EC_ERROR_BUSY;
	}
	return EC_SUCCESS;
}

int usb_spi_board_enable(int host)
{
	/* Make sure we're allowed to enable the requested device */
	int rv = usb_spi_check_host_accessible(host);

	if (rv != EC_SUCCESS) {
		usb_spi_custom_reset = 0;
		return rv;
	}

	disable_ec_ap_spi(usb_spi_custom_reset);

	/*
	 * Only need to check EC vs. AP, because other hosts were ruled out
	 * above.
	 */
	if (host == USB_SPI_EC)
		enable_ec_spi();
	else
		enable_ap_spi(usb_spi_custom_reset);

	enable_spi_pinmux();
	return EC_SUCCESS;
}

void usb_spi_board_disable(void)
{
	CPRINTS("%s", __func__);

	/* Only disable the SPI bus if we own it */
	if (get_spi_bus_user() != SPI_BUS_USER_USB)
		return;

	disable_spi_pinmux();
	disable_ec_ap_spi(usb_spi_custom_reset);
	usb_spi_custom_reset = 0;
	set_spi_bus_user(SPI_BUS_USER_USB, 0);
}

int usb_spi_interface(struct usb_spi_config const *config,
		      struct usb_setup_packet *req)
{
	if (req->bmRequestType != (USB_DIR_OUT |
				    USB_TYPE_VENDOR |
				    USB_RECIP_INTERFACE))
		return 1;

	if (req->wValue  != 0 ||
	    req->wIndex  != config->interface ||
	    req->wLength != 0)
		return 1;

	if (!config->state->enabled_device)
		return 1;

	switch (req->bRequest) {
	case USB_SPI_REQ_ENABLE_AP_CUSTOM:
		usb_spi_custom_reset = 1;
		config->state->enabled_host = USB_SPI_AP;
		break;
	case USB_SPI_REQ_ENABLE_AP:
		usb_spi_custom_reset = 0;
		config->state->enabled_host = USB_SPI_AP;
		break;
	case USB_SPI_REQ_ENABLE_EC:
		usb_spi_custom_reset = 0;
		config->state->enabled_host = USB_SPI_EC;
		break;
	case USB_SPI_REQ_ENABLE:
		usb_spi_custom_reset = 0;
		CPRINTS("%s: Must specify target", __func__);
		/* Fall through... */
	case USB_SPI_REQ_DISABLE:
		config->state->enabled_host = USB_SPI_DISABLE;
		break;

	default:
		return 1;
	}

	/*
	 * Our state has changed, call the deferred function to handle the
	 * state change.
	 */
	hook_call_deferred(config->deferred, 0);
	return 0;
}

#ifdef CONFIG_SPI_HASH
/*****************************************************************************/
/* Hashing support */

/**
 * Returns the content of SPI flash
 *
 * @param buf_usr Buffer to write flash contents
 * @param offset Flash offset to start reading from
 * @param bytes Number of bytes to read.
 *
 * @return EC_SUCCESS, or non-zero if any error.
 */
static int spi_read_chunk(uint8_t *buf_usr, unsigned int offset,
			  unsigned int bytes)
{
	uint8_t cmd[4];

	if (bytes > SPI_HASH_CHUNK_SIZE)
		return EC_ERROR_INVAL;

	cmd[0] = SPI_FLASH_READ;
	cmd[1] = (offset >> 16) & 0xFF;
	cmd[2] = (offset >> 8) & 0xFF;
	cmd[3] = offset & 0xFF;

	return spi_transaction(SPI_FLASH_DEVICE, cmd, 4, buf_usr, bytes);
}

/**
 * Reset EC out of gang programming mode if needed.
 */
static void spi_hash_stop_ec_device(void)
{
	/* If device is not currently EC, nothing to do */
	if (spi_hash_device != USB_SPI_EC)
		return;

	if (use_npcx_gang_mode) {
		/*
		 * EC was in gang mode.  Pulse reset without asserting gang
		 * programmer enable, so that when we take the EC out of reset
		 * it will boot normally.
		 */
		assert_ec_rst();
		usleep(200);
		use_npcx_gang_mode = 0;
	}

	/*
	 * Release EC from reset (either from above, or because gang progamming
	 * mode was disabled so the EC was held in reset during SPI access).
	 */
	deassert_ec_rst();
}

/**
 * Disable SPI hashing mode.
 *
 * @return Vendor command return code.
 */
static enum vendor_cmd_rc spi_hash_disable(void)
{
	if (spi_hash_device == USB_SPI_DISABLE)
		return VENDOR_RC_SUCCESS;

	/* Can't disable SPI if we don't own it */
	if (get_spi_bus_user() != SPI_BUS_USER_HASH)
		return VENDOR_RC_NOT_ALLOWED;

	/* Disable the SPI bus and chip select */
	disable_spi_pinmux();
	disable_ec_ap_spi(0);

	/* Stop the EC device, if it was active */
	spi_hash_stop_ec_device();

	/* Release the bus */
	spi_hash_device = USB_SPI_DISABLE;
	spi_hash_new_device = USB_SPI_DISABLE;
	spi_hash_new_gang_mode = 0;
	set_spi_bus_user(SPI_BUS_USER_HASH, 0);

	/* Disable inactivity timer to turn hashing mode off */
	hook_call_deferred(&spi_hash_inactive_timeout_data, -1);

	CPRINTS("%s", __func__);
	return VENDOR_RC_SUCCESS;
}

/**
 * Deferred function to disable SPI hash mode on inactivity.
 */
static void spi_hash_inactive_timeout(void)
{
	spi_hash_disable();
}

/**
 * Callback to set up the new SPI device after physical presence check.
 */
static void spi_hash_pp_done(void)
{
	/* Acquire the bus */
	if (set_spi_bus_user(SPI_BUS_USER_HASH, 1)) {
		CPRINTS("%s: bus busy", __func__);
		return;
	}

	/* Clear previous enable if needed */
	if (spi_hash_device != USB_SPI_DISABLE)
		disable_ec_ap_spi(0);

	/* Set up new device */
	if (spi_hash_new_device == USB_SPI_AP) {
		/* Stop the EC device, if it was previously active */
		spi_hash_stop_ec_device();

		enable_ap_spi(0);
	} else {
		/* Force the EC into reset and enable EC SPI bus */
		assert_ec_rst();
		enable_ec_spi();

		/*
		 * If EC is headed into gang programmer mode, need to release
		 * EC from reset after acquiring the bus.  EC_FLASH_SELECT runs
		 * to the EC's GP_SEL_ODL signal, which is what enables gang
		 * programmer mode.
		 */
		if (spi_hash_new_gang_mode) {
			usleep(200);
			deassert_ec_rst();
			use_npcx_gang_mode = 1;
		}
	}

	enable_spi_pinmux();
	spi_hash_device = spi_hash_new_device;

	/* Start inactivity timer to turn hashing mode off */
	hook_call_deferred(&spi_hash_inactive_timeout_data,
			   SPI_HASH_TIMEOUT_US);

	CPRINTS("%s: %s", __func__,
		(spi_hash_device == USB_SPI_AP ? "AP" : "EC"));
}

void enable_ap_spi_hash_shortcut(void)
{
	/*
	 * This is a big hammer, invoked when the Chrome OS device is
	 * processing the EC reset. Even if SPI bus was in use when the
	 * operator triggered the AP RO hash verification it should be
	 * released and re-acquired now.
	 */
	enum spi_bus_user_t curr_user;

	shortcut_active_ = true;

	curr_user = get_spi_bus_user();
	if (curr_user != SPI_BUS_USER_NONE)
		set_spi_bus_user(curr_user, 0);

	/*
	 * Simulate successful completion of physical presence detection
	 * required to allow the AP flash hash check. This function is invoked
	 * when the operator entered the appropriate sequence on the device
	 * keyboard, so physical presence is already established.
	 */
	spi_hash_new_device = USB_SPI_AP;
	spi_hash_pp_done();
}

void disable_ap_spi_hash_shortcut(void)
{
	spi_hash_disable();

	shortcut_active_ = false;
}

/* Process vendor subcommand dealing with Physical presence polling. */
static enum vendor_cmd_rc spihash_pp_poll(void *buf,
					  size_t input_size,
					  size_t *response_size)
{
	char *buffer = buf;

	if (spi_hash_device != USB_SPI_DISABLE) {
		buffer[0] = CCD_PP_DONE;
	} else {
		switch (physical_presense_fsm_state()) {
		case PP_AWAITING_PRESS:
			buffer[0] = CCD_PP_AWAITING_PRESS;
			break;
		case PP_BETWEEN_PRESSES:
			buffer[0] = CCD_PP_BETWEEN_PRESSES;
			break;
		default:
			buffer[0] = CCD_PP_CLOSED;
			break;
		}
	}
	*response_size = 1;
	return VENDOR_RC_SUCCESS;
}

/**
 * Set the SPI hashing device.
 *
 * @param dev		Device (enum usb_spi)
 * @param gang_mode	If non-zero, EC uses gang mode
 *
 * @return Vendor command return code
 */
static enum vendor_cmd_rc spi_hash_set_device(int dev, int gang_mode,
					      uint8_t *response_buf,
					      size_t *response_size)
{
	*response_size = 0;

	if (dev == spi_hash_device)
		return VENDOR_RC_SUCCESS;

	/* Enabling requires permission */
	if (!(ccd_is_cap_enabled(CCD_CAP_FLASH_READ)))
		return VENDOR_RC_NOT_ALLOWED;

	spi_hash_new_device = dev;
	spi_hash_new_gang_mode = gang_mode;

	/* Handle enabling */
	if (spi_hash_device == USB_SPI_DISABLE &&
	    !(ccd_is_cap_enabled(CCD_CAP_AP_FLASH) &&
	      ccd_is_cap_enabled(CCD_CAP_EC_FLASH))) {
		/*
		 * We were disabled, and CCD does not grant permission
		 * to both flash chips.  So we need physical presence
		 * to take the SPI bus.  That prevents a malicious
		 * peripheral from using this to reset the device.
		 *
		 * Technically, we could track the chips separately,
		 * and only require physical presence the first time we
		 * check a chip which CCD doesn't grant access to.  But
		 * that's more bookkeeping, so for now the only way to
		 * skip physical presence is to have access to both.
		 */
		int rv = physical_detect_start(0, spi_hash_pp_done);

		if (rv == EC_SUCCESS)
			return VENDOR_RC_IN_PROGRESS;

		*response_size = 1;
		response_buf[0] = rv;

		return VENDOR_RC_INTERNAL_ERROR;
	}

	/*
	 * If we're still here, we already own the SPI bus, and are
	 * changing which chip we're looking at.  Update hash device
	 * directly; no new physical presence required.
	 */
	spi_hash_pp_done();
	return VENDOR_RC_SUCCESS;
}

static enum vendor_cmd_rc spi_hash_dump(uint8_t *dest, uint32_t offset,
					uint32_t size)
{
	/* Fail if we don't own the bus */
	if (get_spi_bus_user() != SPI_BUS_USER_HASH) {
		CPRINTS("%s: not enabled", __func__);
		return VENDOR_RC_NOT_ALLOWED;
	}

	/* Bump inactivity timer to turn hashing mode off */
	hook_call_deferred(&spi_hash_inactive_timeout_data,
			   SPI_HASH_TIMEOUT_US);

	if (size > SPI_HASH_MAX_RESPONSE_BYTES)
		return VENDOR_RC_BOGUS_ARGS;

	if (spi_read_chunk(dest, offset, size) != EC_SUCCESS) {
		CPRINTS("%s: read error at 0x%x", __func__, offset);
		return VENDOR_RC_READ_FLASH_FAIL;
	}

	return VENDOR_RC_SUCCESS;
}

int usb_spi_sha256_start(struct sha256_ctx *ctx)
{
	if (get_spi_bus_user() != SPI_BUS_USER_HASH) {
		CPRINTS("%s: not enabled", __func__);
		return EC_ERROR_BUSY;
	}

	if (DCRYPTO_hw_sha256_init(ctx) != DCRYPTO_OK)
		return EC_ERROR_HW_INTERNAL;

	return EC_SUCCESS;
}

int usb_spi_read_buffer(void *buf, unsigned int offset,  size_t bytes)
{
	uint8_t *p = buf;

	while (bytes) {
		const int this_chunk = MIN(bytes, SPI_HASH_CHUNK_SIZE);

		/* Read the data */
		if (spi_read_chunk(p, offset, this_chunk) != EC_SUCCESS) {
			CPRINTS("%s: read error at 0x%x", __func__, offset);
			return VENDOR_RC_READ_FLASH_FAIL;
		}

		bytes -= this_chunk;
		offset += this_chunk;
		p += this_chunk;
	}
	return EC_SUCCESS;
}

int usb_spi_sha256_update(struct sha256_ctx *ctx, uint32_t offset,
			  uint32_t size)
{
	uint8_t data[SPI_HASH_CHUNK_SIZE];

	while (size) {
		const int this_chunk = MIN(size, SPI_HASH_CHUNK_SIZE);

		/* Read the data */
		if (spi_read_chunk(data, offset, this_chunk) != EC_SUCCESS) {
			CPRINTS("%s: read error at 0x%x", __func__, offset);
			return VENDOR_RC_READ_FLASH_FAIL;
		}
		/* Update hash */
		SHA256_update(ctx, data, this_chunk);

		/* Kick the watchdog every 128 chunks. */
		if (((size / SPI_HASH_CHUNK_SIZE) % 128) == 127) {
			msleep(1);
			watchdog_reload();
		}

		size -= this_chunk;
		offset += this_chunk;
	}
	return EC_SUCCESS;
}

void usb_spi_sha256_final(struct sha256_ctx *ctx, void *digest,
			  size_t digest_size)
{
	size_t copy_size;

	copy_size = MIN(digest_size, SHA256_DIGEST_SIZE);
	memcpy(digest, SHA256_final(ctx), copy_size);

	if (copy_size < digest_size)
		memset((uint8_t *)digest + copy_size, 0,
		       digest_size - copy_size);
}

static enum vendor_cmd_rc spi_hash_sha256(uint8_t *dest, uint32_t offset,
					  uint32_t size)
{
	struct sha256_ctx sha;

	CPRINTS("%s: 0x%x 0x%x", __func__, offset, size);
	if (size > MAX_SPI_HASH_SIZE)
		return VENDOR_RC_BOGUS_ARGS;

	/* Bump inactivity timer to turn hashing mode off */
	hook_call_deferred(&spi_hash_inactive_timeout_data,
			   SPI_HASH_TIMEOUT_US);

	if (usb_spi_sha256_start(&sha) != EC_SUCCESS)
		return VENDOR_RC_INTERNAL_ERROR;

	if (usb_spi_sha256_update(&sha, offset, size) != EC_SUCCESS)
		return VENDOR_RC_READ_FLASH_FAIL;

	usb_spi_sha256_final(&sha, dest, SHA256_DIGEST_SIZE);

	CPRINTS("%s: done", __func__);
	return VENDOR_RC_SUCCESS;
}

/*
 * TPM Vendor command handler for SPI hash commands which need to be available
 * both through CLI and over /dev/tpm0.
 */
static enum vendor_cmd_rc spi_hash_vendor(enum vendor_cmd_cc code,
					  void *buf,
					  size_t input_size,
					  size_t *response_size)
{
	const struct vendor_cc_spi_hash_request *req = buf;
	enum vendor_cmd_rc rc;

	/* Default to no response data */
	*response_size = 0;

	/* Pick what to do based on subcommand. */
	switch (req->subcmd) {
	case SPI_HASH_SUBCMD_DISABLE:
		/* Handle disabling */
		return spi_hash_disable();
	case SPI_HASH_SUBCMD_AP:
		return spi_hash_set_device(USB_SPI_AP, 0, buf, response_size);
	case SPI_HASH_SUBCMD_EC:
		return spi_hash_set_device(USB_SPI_EC,
					   !!(req->flags &
					      SPI_HASH_FLAG_EC_GANG),
					   buf, response_size);
	case SPI_HASH_SUBCMD_SHA256:
		*response_size = SHA256_DIGEST_SIZE;
		rc = spi_hash_sha256(buf, req->offset, req->size);
		if (rc != VENDOR_RC_SUCCESS)
			*response_size = 0;
		return rc;
	case SPI_HASH_SUBCMD_DUMP:
		/* Save size before we overwrite it with data */
		*response_size = req->size;
		rc = spi_hash_dump(buf, req->offset, req->size);
		if (rc != VENDOR_RC_SUCCESS)
			*response_size = 0;
		return rc;
	case SPI_HASH_PP_POLL:
		return spihash_pp_poll(buf, input_size, response_size);

	default:
		CPRINTS("%s:%d - unknown subcommand %d",
			__func__, __LINE__, req->subcmd);
		*response_size = 0;
		return VENDOR_RC_NO_SUCH_SUBCOMMAND;
	}
}
DECLARE_VENDOR_COMMAND(VENDOR_CC_SPI_HASH, spi_hash_vendor);

/**
 * Wrapper for hash commands which are passed through the TPM task context.
 */
static int hash_command_wrapper(int argc, char *argv[])
{
	int rv;
	struct vendor_cc_spi_hash_request req;
	struct tpm_cmd_header *tpm_header;
	const size_t command_size = sizeof(*tpm_header) +
			MAX(sizeof(req), SPI_HASH_MAX_RESPONSE_BYTES);
	uint8_t buf[command_size];
	uint8_t *p;
	uint32_t return_code;

	/* If no args, just return */
	if (argc < 2) {
		ccprintf("SPI hash device: %s\n",
			 (spi_hash_device ?
			  (spi_hash_device == USB_SPI_AP ? "AP" : "EC") :
			  "disable"));
		return EC_SUCCESS;
	}

	/* Parse args into stack-based struct */
	memset(&req, 0, sizeof(req));
	if (!strcasecmp(argv[1], "AP")) {
		req.subcmd = SPI_HASH_SUBCMD_AP;
	} else if (!strcasecmp(argv[1], "EC")) {
		req.subcmd = SPI_HASH_SUBCMD_EC;
		if (argc > 2 && !strcasecmp(argv[2], "gang"))
			req.flags |= SPI_HASH_FLAG_EC_GANG;
	} else if (!strcasecmp(argv[1], "disable")) {
		req.subcmd = SPI_HASH_SUBCMD_DISABLE;
	} else if (argc == 3) {
		req.subcmd = SPI_HASH_SUBCMD_SHA256;
		rv = parse_offset_size(argc, argv, 1, &req.offset, &req.size);
		if (rv)
			return rv;
	} else if (argc == 4 && !strcasecmp(argv[1], "dump")) {
		req.subcmd = SPI_HASH_SUBCMD_DUMP;
		rv = parse_offset_size(argc, argv, 2, &req.offset, &req.size);
		if (rv)
			return rv;
	} else {
		return EC_ERROR_PARAM1;
	}

	/* Build the extension command */
	tpm_header = (struct tpm_cmd_header *)buf;
	tpm_header->tag = htobe16(0x8001); /* TPM_ST_NO_SESSIONS */
	tpm_header->size = htobe32(command_size);
	tpm_header->command_code = htobe32(TPM_CC_VENDOR_BIT_MASK);
	tpm_header->subcommand_code = htobe16(VENDOR_CC_SPI_HASH);
	/* Copy request data */
	p = (uint8_t *)(tpm_header + 1);
	memcpy(p, &req, sizeof(req));

	tpm_alt_extension(tpm_header, command_size);

	/*
	 * Return status in the command code field now, in case of error,
	 * error code is the first byte after the header.
	 */
	return_code = be32toh(tpm_header->command_code);

	if ((return_code != EC_SUCCESS) &&
	    ((return_code - VENDOR_RC_ERR) != VENDOR_RC_IN_PROGRESS)) {
		rv = p[0];
	} else {
		rv = EC_SUCCESS;

		if (req.subcmd == SPI_HASH_SUBCMD_DUMP)
			ccprintf("data: %ph\n", HEX_BUF(p, req.size));
		else if (req.subcmd == SPI_HASH_SUBCMD_SHA256)
			ccprintf("hash: %ph\n", HEX_BUF(p, 32));
	}

	return rv;
}
DECLARE_SAFE_CONSOLE_COMMAND(spihash, hash_command_wrapper,
		     "ap | ec [gang] | disable | [dump] <offset> <size>",
		     "Hash SPI flash via TPM vendor command");
#endif /* CONFIG_SPI_HASH */
