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

#ifdef CRYPTO_TEST_SETUP

/* test and benchmark */
#include "common.h"
#include "console.h"
#include "hooks.h"
#include "task.h"
#include "watchdog.h"

#define cyclecounter() GREG32(M3, DWT_CYCCNT)
#define START_PROFILE(x)                                                       \
	{                                                                      \
		x -= cyclecounter();                                           \
	}
#define END_PROFILE(x)                                                         \
	{                                                                      \
		x += cyclecounter();                                           \
	}
static uint32_t t_sw;
static uint32_t t_hw;
static uint32_t t_transform;
static uint32_t t_dcrypto;

#else /* CRYPTO_TEST_SETUP */

#define START_PROFILE(x)
#define END_PROFILE(x)

#endif /* CRYPTO_TEST_SETUP */

/* Firmware blob for crypto accelerator */
#include "dcrypto_sha512.inc"

struct DMEM_sha512 {
	uint64_t H0[4];
	uint64_t H1[4];
	uint64_t H2[4];
	uint64_t H3[4];
	uint64_t H4[4];
	uint64_t H5[4];
	uint64_t H6[4];
	uint64_t H7[4];
	uint32_t nblocks;
	uint32_t unused[2 * 8 - 1];
	uint32_t input[4 * 8 * 8]; // dmem[10..41]
};

static void copy_words(const void *in, uint32_t *dst, size_t nwords)
{
	const uint32_t *src = (const uint32_t *) in;

	do {
		uint32_t w1 = __builtin_bswap32(*src++);
		uint32_t w2 = __builtin_bswap32(*src++);
		*dst++ = w2;
		*dst++ = w1;
	} while (nwords -= 2);
}

static void dcrypto_SHA512_setup(void)
{
	dcrypto_imem_load(0, IMEM_dcrypto_sha512,
			  ARRAY_SIZE(IMEM_dcrypto_sha512));
}

static void dcrypto_SHA512_Transform(struct sha512_ctx *ctx,
				     const uint32_t *buf, size_t nwords)
{
	int result = 0;
	struct DMEM_sha512 *p512 =
	    (struct DMEM_sha512 *) GREG32_ADDR(CRYPTO, DMEM_DUMMY);

	START_PROFILE(t_transform)

	/* Pass in H[] */
	p512->H0[0] = ctx->state[0];
	p512->H1[0] = ctx->state[1];
	p512->H2[0] = ctx->state[2];
	p512->H3[0] = ctx->state[3];
	p512->H4[0] = ctx->state[4];
	p512->H5[0] = ctx->state[5];
	p512->H6[0] = ctx->state[6];
	p512->H7[0] = ctx->state[7];

	p512->nblocks = nwords / 32;

	/* Pass in buf[] */
	copy_words(buf, p512->input, nwords);

	START_PROFILE(t_dcrypto)
	result |= dcrypto_call(CF_compress_adr);
	END_PROFILE(t_dcrypto)

	/* Retrieve new H[] */
	ctx->state[0] = p512->H0[0];
	ctx->state[1] = p512->H1[0];
	ctx->state[2] = p512->H2[0];
	ctx->state[3] = p512->H3[0];
	ctx->state[4] = p512->H4[0];
	ctx->state[5] = p512->H5[0];
	ctx->state[6] = p512->H6[0];
	ctx->state[7] = p512->H7[0];

	/* TODO: errno or such to capture errors */
	(void) (result == 0);

	END_PROFILE(t_transform)
}

void SHA512_hw_update(struct sha512_ctx *ctx, const void *data, size_t len)
{
	int i = (int)(ctx->count & (sizeof(ctx->b8) - 1));
	const uint8_t *p = (const uint8_t *)data;
	uint8_t *d = &ctx->b8[i];

	ctx->count += len;

	dcrypto_init_and_lock();
	dcrypto_SHA512_setup();

	/* Take fast path for 32-bit aligned 1KB inputs */
	if (i == 0 && len == 1024 && (((intptr_t)data) & 3) == 0) {
		dcrypto_SHA512_Transform(ctx, (const uint32_t *)data, 8 * 32);
	} else {
		if (len <= sizeof(ctx->b8) - i) {
			memcpy(d, p, len);
			if (len == sizeof(ctx->b8) - i) {
				dcrypto_SHA512_Transform(
					ctx, (uint32_t *)(ctx->b8), 32);
			}
		} else {
			memcpy(d, p, sizeof(ctx->b8) - i);
			dcrypto_SHA512_Transform(ctx, ctx->b32, 32);
			d = ctx->b8;
			len -= (sizeof(ctx->b8) - i);
			p += (sizeof(ctx->b8) - i);
			while (len >= sizeof(ctx->b8)) {
				memcpy(d, p, sizeof(ctx->b8));
				p += sizeof(ctx->b8);
				len -= sizeof(ctx->b8);
				dcrypto_SHA512_Transform(
					ctx, (uint32_t *)(ctx->b8), 32);
			}
			/* Leave remainder in ctx->buf */
			memcpy(d, p, len);
		}
	}
	dcrypto_unlock();
}

struct sha512_digest *SHA512_hw_final(struct sha512_ctx *ctx)
{
	uint64_t cnt = ctx->count * 8;
	size_t i = (size_t) (ctx->count & (sizeof(ctx->b8) - 1));
	uint8_t *p = &ctx->b8[i];

	*p++ = 0x80;
	i++;

	dcrypto_init_and_lock();
	dcrypto_SHA512_setup();

	if (i > sizeof(ctx->b8) - 16U) {
		memset(p, 0, sizeof(ctx->b8) - i);
		dcrypto_SHA512_Transform(ctx, ctx->b32, 32);
		i = 0;
		p = ctx->b8;
	}

	memset(p, 0, sizeof(ctx->b8) - 8 - i);
	p += sizeof(ctx->b8) - 8 - i;

	for (i = 0; i < 8; ++i) {
		uint8_t tmp = (uint8_t)(cnt >> 56);
		cnt <<= 8;
		*p++ = tmp;
	}

	dcrypto_SHA512_Transform(ctx, ctx->b32, 32);

	p = ctx->b8;
	for (i = 0; i < 8; i++) {
		uint64_t tmp = ctx->state[i];
		*p++ = (uint8_t)(tmp >> 56);
		*p++ = (uint8_t)(tmp >> 48);
		*p++ = (uint8_t)(tmp >> 40);
		*p++ = (uint8_t)(tmp >> 32);
		*p++ = (uint8_t)(tmp >> 24);
		*p++ = (uint8_t)(tmp >> 16);
		*p++ = (uint8_t)(tmp >> 8);
		*p++ = (uint8_t)(tmp >> 0);
	}

	dcrypto_unlock();
	return &ctx->digest;
}

BUILD_ASSERT(sizeof(union hash_ctx) >= sizeof(struct sha512_ctx));
BUILD_ASSERT(sizeof(union hash_ctx) >= sizeof(struct sha384_ctx));

static void SHA512_hw_init_as_hash(union hash_ctx *const ctx)
	__alias(SHA512_hw_init);

static void SHA384_hw_init_as_hash(union hash_ctx *const ctx)
	__alias(SHA384_hw_init);

static void SHA512_hw_update_as_hash(union hash_ctx *const ctx,
				     const void *data, size_t len)
	__alias(SHA512_hw_update);

static const union sha_digests *SHA512_final_as_hash(union hash_ctx *const ctx)
	__alias(SHA512_hw_final);

void SHA384_hw_update(struct sha384_ctx *const ctx, const void *data,
		      size_t len) __alias(SHA512_hw_update);

const struct sha384_digest *SHA384_hw_final(struct sha384_ctx *const ctx)
	__alias(SHA512_hw_final);

const struct sha512_digest *SHA512_hw_hash(const void *data, size_t len,
					   struct sha512_digest *digest)
{
	struct sha512_ctx ctx;

	SHA512_hw_init(&ctx);
	SHA512_hw_update(&ctx, data, len);
	memcpy(digest, SHA512_hw_final(&ctx), SHA512_DIGEST_SIZE);

	return digest;
}

void SHA512_hw_init(struct sha512_ctx *ctx)
{
	static const struct hash_vtable dcrypto_SHA512_VTAB = {
		SHA512_hw_init_as_hash,	  SHA512_hw_update_as_hash,
		SHA512_final_as_hash,	  HMAC_sw_final,
		SHA512_DIGEST_SIZE,	  SHA512_BLOCK_SIZE,
		sizeof(struct sha512_ctx)
	};
	static const uint64_t sha512_init[SHA512_DIGEST_DWORDS] = {
		0x6a09e667f3bcc908ll, 0xbb67ae8584caa73bll,
		0x3c6ef372fe94f82bll, 0xa54ff53a5f1d36f1ll,
		0x510e527fade682d1ll, 0x9b05688c2b3e6c1fll,
		0x1f83d9abfb41bd6bll, 0x5be0cd19137e2179ll
	};

	memcpy(ctx->state, sha512_init, sizeof(ctx->state));
	ctx->count = 0;
	ctx->f = &dcrypto_SHA512_VTAB;
}

const struct sha384_digest *SHA384_hw_hash(const void *data, size_t len,
			      struct sha384_digest *digest)
{
	struct sha384_ctx ctx;

	SHA384_hw_init(&ctx);
	SHA384_hw_update(&ctx, data, len);
	memcpy(digest->b8, SHA384_hw_final(&ctx)->b8, SHA384_DIGEST_SIZE);
	return digest;
}

void SHA384_hw_init(struct sha512_ctx *ctx)
{
	static const struct hash_vtable dcrypto_SHA384_VTAB = {
		SHA384_hw_init_as_hash,	  SHA512_hw_update_as_hash,
		SHA512_final_as_hash,	  HMAC_sw_final,
		SHA384_DIGEST_SIZE,	  SHA512_BLOCK_SIZE,
		sizeof(struct sha384_ctx)
	};
	static const uint64_t sha384_init[SHA512_DIGEST_DWORDS] = {
		0xcbbb9d5dc1059ed8ll, 0x629a292a367cd507ll,
		0x9159015a3070dd17ll, 0x152fecd8f70e5939ll,
		0x67332667ffc00b31ll, 0x8eb44a8768581511ll,
		0xdb0c2e0d64f98fa7ll, 0x47b5481dbefa4fa4ll
	};

	memcpy(ctx->state, sha384_init, sizeof(ctx->state));

	ctx->count = 0;
	ctx->f = &dcrypto_SHA384_VTAB;
}

#if defined(CONFIG_SHA512_HW_EQ_SW)
/**
 * Make sw version equal to hw. Unlike SHA2-256, dcrypto implementation
 * of SHA2-512/384 allows to save context, so can fully replace software
 * implementation.
 */
const struct sha512_digest *SHA512_sw_hash(const void *data, size_t len,
					   struct sha512_digest *digest)
	__alias(SHA512_hw_hash);


void SHA512_sw_init(struct sha512_ctx *const ctx) __alias(SHA512_hw_init);

void SHA512_sw_update(struct sha512_ctx *const ctx, const void *data,
		      size_t len) __alias(SHA512_hw_update);

const struct sha512_digest *SHA512_sw_final(struct sha512_ctx *const ctx)
	__alias(SHA512_hw_final);


const struct sha384_digest *SHA384_sw_hash(const void *data, size_t len,
					   struct sha384_digest *digest)
	__alias(SHA384_hw_hash);

void SHA384_sw_init(struct sha384_ctx *const ctx) __alias(SHA384_hw_init);

void SHA384_sw_update(struct sha384_ctx *const ctx, const void *data,
		      size_t len) __alias(SHA384_hw_update);

const struct sha384_digest *SHA384_sw_final(struct sha384_ctx *const ctx)
	__alias(SHA384_hw_final);

#endif

#ifdef CRYPTO_TEST_SETUP

static uint32_t msg[256]; // 1KB
static int msg_len;
static int msg_loops;
static struct sha512_ctx sw;
static struct sha512_ctx hw;
static const struct sha512_digest *sw_digest;
static const struct sha512_digest *hw_digest;
static uint32_t t_sw;
static uint32_t t_hw;

static void run_sha512_cmd(void)
{
	int i;

	t_transform = 0;
	t_dcrypto = 0;
	t_sw = 0;
	t_hw = 0;

	START_PROFILE(t_sw)
	SHA512_sw_init(&sw);
	for (i = 0; i < msg_loops; ++i) {
		SHA512_update(&sw, msg, msg_len);
	}
	sw_digest = SHA512_final(&sw);
	END_PROFILE(t_sw)

	watchdog_reload();

	START_PROFILE(t_hw)
	SHA512_hw_init(&hw);
	for (i = 0; i < msg_loops; ++i) {
		SHA512_update(&hw, msg, msg_len);
	}
	hw_digest = SHA512_final(&hw);
	END_PROFILE(t_hw)

	watchdog_reload();

	ccprintf("sw(%u):\n", t_sw);
	for (i = 0; i < SHA512_DIGEST_SIZE; ++i)
		ccprintf("%02x", sw_digest->b8[i]);
	ccprintf("\n");

	ccprintf("hw(%u/%u/%u):\n", t_hw, t_transform, t_dcrypto);
	for (i = 0; i < SHA512_DIGEST_SIZE; ++i)
		ccprintf("%02x", hw_digest->b8[i]);
	ccprintf("\n");
}

static int cmd_sha512_bench(int argc, char *argv[])
{
	memset(msg, '!', sizeof(msg));

	if (argc > 1) {
		msg_loops = 1;
		msg_len = strlen(argv[1]);
		memcpy(msg, argv[1], msg_len);
	} else {
		msg_loops = 64; // benchmark 64K
		msg_len = sizeof(msg);
	}

	run_sha512_cmd();

	return EC_SUCCESS;
}
DECLARE_SAFE_CONSOLE_COMMAND(sha512_bench, cmd_sha512_bench, NULL, NULL);

static int cmd_sha512_test(int argc, char *argv[])
{
	size_t i;

	ccprintf("sha512 self-test started!\n");

	for (i = 0; i < 129; ++i) {
		memset(msg, i, i);
		watchdog_reload();
		SHA512_sw_init(&sw);
		SHA512_update(&sw, msg, i);
		sw_digest = SHA512_final(&sw);

		SHA512_hw_init(&hw);
		SHA512_update(&hw, msg, i);
		hw_digest = SHA512_final(&hw);

		if (memcmp(sw_digest, hw_digest, SHA512_DIGEST_SIZE) != 0) {
			ccprintf("sha512 self-test fail at %d!\n", i);
			cflush();
		}
	}

	ccprintf("sha512 self-test PASS!\n");

	return EC_SUCCESS;
}
DECLARE_SAFE_CONSOLE_COMMAND(sha512_test, cmd_sha512_test, NULL, NULL);

#endif /* CRYPTO_TEST_SETUP */
