/*
 * Copyright 2017 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 "dcrypto.h"
#include "registers.h"

/* The default build options compile for size (-Os); instruct the
 * compiler to optimize for speed here.  Incidentally -O produces
 * faster code than -O2!
 */
static int __attribute__((optimize("O")))
inner_loop(uint32_t **out, const uint32_t **in, size_t len)
{
	uint32_t *outw = *out;
	const uint32_t *inw = *in;

	while (len >= 16) {
		uint32_t w0, w1, w2, w3;

		w0 = inw[0];
		w1 = inw[1];
		w2 = inw[2];
		w3 = inw[3];
		GREG32(KEYMGR, AES_WFIFO_DATA) = w0;
		GREG32(KEYMGR, AES_WFIFO_DATA) = w1;
		GREG32(KEYMGR, AES_WFIFO_DATA) = w2;
		GREG32(KEYMGR, AES_WFIFO_DATA) = w3;

		while (GREG32(KEYMGR, AES_RFIFO_EMPTY))
			;

		w0 = GREG32(KEYMGR, AES_RFIFO_DATA);
		w1 = GREG32(KEYMGR, AES_RFIFO_DATA);
		w2 = GREG32(KEYMGR, AES_RFIFO_DATA);
		w3 = GREG32(KEYMGR, AES_RFIFO_DATA);
		outw[0] = w0;
		outw[1] = w1;
		outw[2] = w2;
		outw[3] = w3;

		inw += 4;
		outw += 4;
		len -= 16;
	}

	*in = inw;
	*out = outw;
	return len;
}

static int outer_loop(uint32_t **out, const uint32_t **in, size_t len)
{
	uint32_t *outw = *out;
	const uint32_t *inw = *in;

	if (len >= 16) {
		GREG32(KEYMGR, AES_WFIFO_DATA) = inw[0];
		GREG32(KEYMGR, AES_WFIFO_DATA) = inw[1];
		GREG32(KEYMGR, AES_WFIFO_DATA) = inw[2];
		GREG32(KEYMGR, AES_WFIFO_DATA) = inw[3];
		inw += 4;
		len -= 16;

		len = inner_loop(&outw, &inw, len);

		while (GREG32(KEYMGR, AES_RFIFO_EMPTY))
			;

		outw[0] = GREG32(KEYMGR, AES_RFIFO_DATA);
		outw[1] = GREG32(KEYMGR, AES_RFIFO_DATA);
		outw[2] = GREG32(KEYMGR, AES_RFIFO_DATA);
		outw[3] = GREG32(KEYMGR, AES_RFIFO_DATA);
		outw += 4;
	}

	*in =  inw;
	*out = outw;
	return len;
}

static int aes_init(struct APPKEY_CTX *ctx, enum dcrypto_appid appid,
		const uint32_t iv[4])
{
	/* Setup USR-based application key. */
	if (!DCRYPTO_appkey_init(appid, ctx))
		return 0;

	/* Configure AES engine. */
	GWRITE_FIELD(KEYMGR, AES_CTRL, RESET, CTRL_NO_SOFT_RESET);
	GWRITE_FIELD(KEYMGR, AES_CTRL, KEYSIZE, 2 /* AES-256 */);
	GWRITE_FIELD(KEYMGR, AES_CTRL, CIPHER_MODE, CIPHER_MODE_CTR);
	GWRITE_FIELD(KEYMGR, AES_CTRL, ENC_MODE, ENCRYPT_MODE);
	GWRITE_FIELD(KEYMGR, AES_CTRL, CTR_ENDIAN, CTRL_CTR_BIG_ENDIAN);

	/*
	 * For fixed-key, bulk ciphering, turn off random nops (which
	 * are enabled by default).
	 */
	GWRITE_FIELD(KEYMGR, AES_RAND_STALL_CTL, STALL_EN, 0);

	/* Enable hidden key usage, each appid gets its own
	 * USR, with USR0 starting at 0x2a0.
	 */
	GWRITE_FIELD(KEYMGR, AES_USE_HIDDEN_KEY, INDEX,
		0x2a0 + (appid * 2));
	GWRITE_FIELD(KEYMGR, AES_USE_HIDDEN_KEY, ENABLE, 1);
	GWRITE_FIELD(KEYMGR, AES_CTRL, ENABLE, CTRL_ENABLE);

	/* Wait for key-expansion. */
	GREG32(KEYMGR, AES_KEY_START) = 1;
	while (GREG32(KEYMGR, AES_KEY_START))
		;

	/* Check for errors (e.g. USR not correctly setup. */
	if (GREG32(KEYMGR, HKEY_ERR_FLAGS))
		return 0;

	/* Set IV. */
	GR_KEYMGR_AES_CTR(0) = iv[0];
	GR_KEYMGR_AES_CTR(1) = iv[1];
	GR_KEYMGR_AES_CTR(2) = iv[2];
	GR_KEYMGR_AES_CTR(3) = iv[3];

	return 1;
}

int DCRYPTO_app_cipher(enum dcrypto_appid appid, const void *salt,
		void *out, const void *in, size_t len)
{
	struct APPKEY_CTX ctx;
	const uint32_t *inw = in;
	uint32_t *outw = out;

	/* Test pointers for word alignment. */
	if (((uintptr_t) in & 0x03) || ((uintptr_t) out & 0x03))
		return 0;

	{
		/* Initialize key, and AES engine. */
		uint32_t iv[4];

		memcpy(iv, salt, sizeof(iv));
		if (!aes_init(&ctx, appid, iv))
			return 0;
	}

	len = outer_loop(&outw, &inw, len);

	if (len) {
		/* Cipher the final partial block */
		uint32_t tmpin[4];
		uint32_t tmpout[4];
		const uint32_t *tmpinw;
		uint32_t *tmpoutw;

		tmpinw = tmpin;
		tmpoutw = tmpout;

		memcpy(tmpin, inw, len);
		outer_loop(&tmpoutw, &tmpinw, 16);
		memcpy(outw, tmpout, len);
	}

	DCRYPTO_appkey_finish(&ctx);
	return 1;
}

#ifdef CRYPTO_TEST_SETUP

#include "common.h"
#include "console.h"
#include "hooks.h"
#include "shared_mem.h"
#include "task.h"
#include "timer.h"
#include "watchdog.h"

#define HEAP_HEAD_ROOM 0x400
static uint32_t number_of_iterations;
static uint8_t result;

/* Staticstics for ecrypt and decryp passes. */
struct ciph_stats {
	uint16_t min_time;
	uint16_t max_time;
	uint32_t total_time;
} __packed; /* Just in case. */

/* A common structure to contain information about the test run. */
struct test_info {
	size_t test_blob_size;
	struct ciph_stats enc_stats;
	struct ciph_stats dec_stats;
	char *p; /* Pointer to an allcoated buffer of test_blob_size bytes. */
};

static void init_stats(struct ciph_stats *stats)
{
	stats->min_time = ~0;
	stats->max_time = 0;
	stats->total_time = 0;
}

static void update_stats(struct ciph_stats *stats, uint32_t time)
{
	if (time < stats->min_time)
		stats->min_time = time;

	if (time > stats->max_time)
		stats->max_time = time;

	stats->total_time += time;
}

static void report_stats(const char *direction, struct ciph_stats *stats)
{
	ccprintf("%s results: min %d us, max %d us, average %d us\n",
		 direction, stats->min_time, stats->max_time,
		 stats->total_time / number_of_iterations);
}

/*
 * Prepare to run the test: allocate memory, initialize stats structures.
 *
 * Returns EC_SUCCESS if everything is fine, EC_ERROR_OVERFLOW on malloc
 * failures.
 */
static int prepare_running(struct test_info *pinfo)
{
	memset(pinfo, 0, sizeof(*pinfo));


	pinfo->test_blob_size = shared_mem_size();
	/*
	 * Leave some room for crypto functions if they need to allocate
	 * something, just in case. 0x20 extra bytes are needed to be able to
	 * modify size alignment of the allocated buffer.
	 */
	if (pinfo->test_blob_size < (HEAP_HEAD_ROOM + 0x20)) {
		ccprintf("Not enough memory to run the test\n");
		return EC_ERROR_OVERFLOW;
	}
	pinfo->test_blob_size = (pinfo->test_blob_size - HEAP_HEAD_ROOM);

	if (shared_mem_acquire(pinfo->test_blob_size,
			       (char **)&(pinfo->p)) != EC_SUCCESS) {
		ccprintf("Failed to allocate %d bytes\n",
			 pinfo->test_blob_size);
		return EC_ERROR_OVERFLOW;
	}

	/*
	 * Use odd block size to make sure unaligned length blocks are handled
	 * properly. This leaves room in the end of the buffer to check if the
	 * decryption routine scratches it.
	 */
	pinfo->test_blob_size &= ~0x1f;
	pinfo->test_blob_size |= 7;

	ccprintf("running %d iterations\n", number_of_iterations);
	ccprintf("blob size %d at %p\n", pinfo->test_blob_size, pinfo->p);

	init_stats(&(pinfo->enc_stats));
	init_stats(&(pinfo->dec_stats));

	return EC_SUCCESS;
}

/*
 * Let's split the buffer in two equal halves, encrypt the lower half into the
 * upper half and compare them word by word. There should be no repetitions.
 *
 * The side effect of this is starting the test with random clear text data.
 *
 * The first 16 bytes of the allocated buffer are used as the encryption IV.
 */
static int basic_check(struct test_info *pinfo)
{
	size_t half;
	int i;
	uint32_t *p;

	ccprintf("original data  %.16h\n", pinfo->p);

	half = (pinfo->test_blob_size/2) & ~3;
	if (!DCRYPTO_app_cipher(NVMEM, pinfo->p, pinfo->p,
				pinfo->p + half, half)) {
		ccprintf("first ecnryption run failed\n");
		return EC_ERROR_UNKNOWN;
	}

	p = (uint32_t *)pinfo->p;
	half /= sizeof(*p);

	for (i = 0; i < half; i++)
		if (p[i] == p[i + half]) {
			ccprintf("repeating 32 bit word detected"
				 " at offset 0x%x!\n", i * 4);
			return EC_ERROR_UNKNOWN;
		}

	ccprintf("hashed data    %.16h\n", pinfo->p);

	return EC_SUCCESS;
}

/*
 * Main iteration of the console command, runs ecnryption/decryption cycles,
 * vefifying that decrypted text's hash matches the original, and accumulating
 * timing statistics.
 */
static int command_loop(struct test_info *pinfo)
{
	uint8_t sha[SHA_DIGEST_SIZE];
	uint8_t sha_after[SHA_DIGEST_SIZE];
	uint32_t iteration;
	uint8_t *p_last_byte;
	int rv;

	/*
	 * Prepare the hash of the original data to be able to verify
	 * results.
	 */
	DCRYPTO_SHA1_hash((uint8_t *)(pinfo->p), pinfo->test_blob_size, sha);

	/* Use the hash as an IV for the cipher. */
	memcpy(sha_after, sha, sizeof(sha_after));

	iteration = number_of_iterations;
	p_last_byte = pinfo->p + pinfo->test_blob_size;

	while (iteration--) {
		char last_byte = (char) iteration;
		uint32_t tstamp;

		*p_last_byte = last_byte;

		if (!(iteration % 500))
			watchdog_reload();

		tstamp = get_time().val;
		rv = DCRYPTO_app_cipher(NVMEM, sha_after, pinfo->p,
					pinfo->p, pinfo->test_blob_size);
		tstamp = get_time().val - tstamp;

		if (!rv) {
			ccprintf("encryption failed\n");
			return EC_ERROR_UNKNOWN;
		}
		if (*p_last_byte != last_byte) {
			ccprintf("encryption overflowed\n");
			return EC_ERROR_UNKNOWN;
		}
		update_stats(&pinfo->enc_stats, tstamp);

		tstamp = get_time().val;
		rv = DCRYPTO_app_cipher(NVMEM, sha_after, pinfo->p,
					pinfo->p, pinfo->test_blob_size);
		tstamp = get_time().val - tstamp;

		if (!rv) {
			ccprintf("decryption failed\n");
			return EC_ERROR_UNKNOWN;
		}
		if (*p_last_byte != last_byte) {
			ccprintf("decryption overflowed\n");
			return EC_ERROR_UNKNOWN;
		}

		DCRYPTO_SHA1_hash((uint8_t *)(pinfo->p),
				  pinfo->test_blob_size, sha_after);
		if (memcmp(sha, sha_after, sizeof(sha))) {
			ccprintf("\n"
				 "sha1 before and after mismatch, %d to go!\n",
				 iteration);
			return EC_ERROR_UNKNOWN;
		}

		update_stats(&pinfo->dec_stats, tstamp);

		/* get a new IV */
		DCRYPTO_SHA1_hash(sha_after, sizeof(sha), sha_after);
	}

	return EC_SUCCESS;
}

/*
 * Run cipher command on the hooks task context, as dcrypto's stack
 * requirements exceed console tasks' allowance.
 */
static void run_cipher_cmd(void)
{
	struct test_info info;

	result = prepare_running(&info);

	if (result == EC_SUCCESS)
		result = basic_check(&info);

	if (result == EC_SUCCESS)
		result = command_loop(&info);

	if (result == EC_SUCCESS) {
		report_stats("Encryption", &info.enc_stats);
		report_stats("Decryption", &info.dec_stats);
	} else if (info.p) {
		ccprintf("current data   %.16h\n", info.p);
	}

	if (info.p)
		shared_mem_release(info.p);

	task_set_event(TASK_ID_CONSOLE, TASK_EVENT_CUSTOM(1), 0);
}
DECLARE_DEFERRED(run_cipher_cmd);

static int cmd_cipher(int argc, char **argv)
{
	uint32_t events;
	uint32_t max_time;

	/* Ignore potential input errors, let the user handle them. */
	if (argc > 1)
		number_of_iterations = strtoi(argv[1], NULL, 0);
	else
		number_of_iterations = 1000;

	if (!number_of_iterations) {
		ccprintf("not running zero iterations\n");
		return EC_ERROR_PARAM1;
	}

	hook_call_deferred(&run_cipher_cmd_data, 0);

	/* Roughly, .5 us per byte should be more than enough. */
	max_time = number_of_iterations * shared_mem_size() / 2;

	ccprintf("Will wait up to %d ms\n", (max_time + 500)/1000);

	events = task_wait_event_mask(TASK_EVENT_CUSTOM(1), max_time);
	if (!(events & TASK_EVENT_CUSTOM(1))) {
		ccprintf("Timed out, you might want to reboot...\n");
		return EC_ERROR_TIMEOUT;
	}

	return result;
}
DECLARE_SAFE_CONSOLE_COMMAND(cipher, cmd_cipher, NULL, NULL);
#endif
