/* Copyright 2017 IBM Corp.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * 	http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdarg.h>

#include <libflash/libflash.h>
#include <libflash/libflash-priv.h>

#include "stubs.h"
#include "mbox-server.h"

#define zalloc(n) calloc(1, n)
#define __unused          __attribute__((unused))

#undef pr_fmt

void mbox_init(void)
{
}

#include "../libflash.c"
#include "../mbox-flash.c"
#include "../ecc.c"
#include "../blocklevel.c"

#undef pr_fmt
#define pr_fmt(fmt) "MBOX-PROXY: " fmt

/* client interface */

#include "../../include/lpc-mbox.h"

#define ERR(...) FL_DBG(__VA_ARGS__)

static int run_flash_test(struct blocklevel_device *bl)
{
	struct mbox_flash_data *mbox_flash;
	char hello[] = "Hello World";
	uint32_t erase_granule;
	uint64_t total_size;
	const char *name;
	uint16_t *test;
	char *tmp;
	int i, rc;

	mbox_flash = container_of(bl, struct mbox_flash_data, bl);

	/*
	 * Do something first so that if it has been reset it does that
	 * before we check versions
	 */
	rc = blocklevel_get_info(bl, &name, &total_size, &erase_granule);
	if (rc) {
		ERR("blocklevel_get_info() failed with err %d\n", rc);
		return 1;
	}
	if (total_size != mbox_server_total_size()) {
		ERR("Total flash size is incorrect: 0x%08lx vs 0x%08x\n",
				total_size, mbox_server_total_size());
		return 1;
	}
	if (erase_granule != mbox_server_erase_granule()) {
		ERR("Erase granule is incorrect 0x%08x vs 0x%08x\n",
				erase_granule, mbox_server_erase_granule());
		return 1;
	}


	/* Sanity check that mbox_flash has inited correctly */
	if (mbox_flash->version != mbox_server_version()) {
		ERR("MBOX Flash didn't agree with the server version\n");
		return 1;
	}
	if (mbox_flash->version == 1 && mbox_flash->shift != 12) {
		ERR("MBOX Flash version 1 isn't using a 4K shift\n");
		return 1;
	}

	mbox_server_memset(0xff);

	test = calloc(erase_granule * 20, 1);

	/* Make up a test pattern */
	for (i = 0; i < erase_granule * 10; i++)
		test[i] = i;

	/* Write 64k of stuff at 0 and at 128k */
	printf("Writing test patterns...\n");
	rc = blocklevel_write(bl, 0, test, erase_granule * 10);
	if (rc) {
		ERR("blocklevel_write(0, erase_granule * 10) failed with err %d\n", rc);
		return 1;
	}
	rc = blocklevel_write(bl, erase_granule * 20, test, erase_granule * 10);
	if (rc) {
		ERR("blocklevel_write(0x20000, 0x10000) failed with err %d\n", rc);
		return 1;
	}

	if (mbox_server_memcmp(0, test, erase_granule * 10)) {
		ERR("Test pattern mismatch !\n");
		return 1;
	}

	/* Write "Hello world" straddling the 64k boundary */
	printf("Writing test string...\n");
	rc = blocklevel_write(bl, (erase_granule * 10) - 8, hello, sizeof(hello));
	if (rc) {
		ERR("blocklevel_write(0xfffc, %s, %lu) failed with err %d\n",
				hello, sizeof(hello), rc);
		return 1;
	}

	/* Check result */
	if (mbox_server_memcmp((erase_granule * 10) - 8, hello, sizeof(hello))) {
		ERR("Test string mismatch!\n");
		return 1;
	}

	/* Erase granule is something but never 0x50, this shouldn't succeed */
	rc = blocklevel_erase(bl, 0, 0x50);
	if (!rc) {
		ERR("blocklevel_erase(0, 0x50) didn't fail!\n");
		return 1;
	}

	/* Check it didn't silently erase */
	if (mbox_server_memcmp(0, test, (erase_granule * 10) - 8)) {
		ERR("Test pattern mismatch !\n");
		return 1;
	}

	/*
	 * For v1 protocol this should NOT call MARK_WRITE_ERASED!
	 * The server MARK_WRITE_ERASED will call exit(1) if it gets a
	 * MARK_WRITE_ERASED and version == 1
	 */
	rc = blocklevel_erase(bl, 0, erase_granule);
	if (rc) {
		ERR("blocklevel_erase(0, erase_granule) failed with err %d\n", rc);
		return 1;
	}

	/*
	 * Version 1 doesn't specify that the buffer actually becomes 0xff
	 * It is up to the daemon to do what it wants really - there are
	 * implementations that do nothing but writes to the same region
	 * work fine
	 */

	/* This check is important for v2 */
	/* Check stuff got erased */
	tmp = malloc(erase_granule * 2);
	if (!tmp) {
		ERR("malloc failed\n");
		return 1;
	}
	if (mbox_server_version() > 1) {
		memset(tmp, 0xff, erase_granule);
		if (mbox_server_memcmp(0, tmp, erase_granule)) {
			ERR("Buffer not erased\n");
			rc = 1;
			goto out;
		}
	}

	/* Read beyond the end of flash */
	rc = blocklevel_read(bl, total_size, tmp, 0x1000);
	if (!rc) {
		ERR("blocklevel_read(total_size, 0x1000) (read beyond the end) succeeded\n");
		goto out;
	}

	/* Test some simple write/read cases, avoid first page */
	rc = blocklevel_write(bl, erase_granule * 2, test, erase_granule / 2);
	if (rc) {
		ERR("blocklevel_write(erase_granule, erase_granule / 2) failed with err %d\n", rc);
		goto out;
	}
	rc = blocklevel_write(bl, erase_granule * 2 + erase_granule / 2, test, erase_granule / 2);
	if (rc) {
		ERR("blocklevel_write(erase_granule * 2 + erase_granule / 2, erase_granule) failed with err %d\n", rc);
		goto out;
	}

	rc = mbox_server_memcmp(erase_granule * 2, test, erase_granule / 2);
	if (rc) {
		ERR("%s:%d mbox_server_memcmp miscompare\n", __FILE__, __LINE__);
		goto out;
	}
	rc = mbox_server_memcmp(erase_granule * 2 + erase_granule / 2, test, erase_granule / 2);
	if (rc) {
		ERR("%s:%d mbox_server_memcmp miscompare\n", __FILE__, __LINE__);
		goto out;
	}

	/* Great so the writes made it, can we read them back? Do it in
	 * four small reads */
	for (i = 0; i < 4; i++) {
		rc = blocklevel_read(bl, erase_granule * 2 + (i * erase_granule / 4), tmp + (i * erase_granule / 4), erase_granule / 4);
		if (rc) {
			ERR("blocklevel_read(0x%08x, erase_granule / 4) failed with err %d\n",
					2 * erase_granule + (i * erase_granule / 4), rc);
			goto out;
		}
	}
	rc = memcmp(test, tmp, erase_granule / 2);
	if (rc) {
		ERR("%s:%d read back miscompare\n", __FILE__, __LINE__);
		goto out;
	}
	rc = memcmp(test, tmp + erase_granule / 2, erase_granule / 2);
	if (rc) {
		ERR("%s:%d read back miscompare\n", __FILE__, __LINE__);
		goto out;
	}

	/*
	 * Make sure we didn't corrupt other stuff, also make sure one
	 * blocklevel call will understand how to read from two windows
	 */
	for (i = 3; i < 9; i = i + 2) {
		printf("i:%d erase: 0x%08x\n", i, erase_granule);
		rc = blocklevel_read(bl, i * erase_granule, tmp, 2 * erase_granule);
		if (rc) {
			ERR("blocklevel_read(0x%08x, 2 * erase_granule) failed with err: %d\n", i * erase_granule, rc);
			goto out;
		}
		rc = memcmp(((char *)test) + (i * erase_granule), tmp, 2 * erase_granule);
		if (rc) {
			ERR("%s:%d read back miscompare (pos: 0x%08x)\n", __FILE__, __LINE__, i * erase_granule);
			goto out;
		}
	}

	srand(1);
	/*
	 * Try to jump around the place doing a tonne of small reads.
	 * Worth doing the same with writes TODO
	 */
#ifdef __STRICT_TEST__
#define TEST_LOOPS 1000
#else
#define TEST_LOOPS 100
#endif
	for (i = 0; i < TEST_LOOPS; i++) {
		int r = rand();

		printf("Loop %d of %d\n", i, TEST_LOOPS);
		/* Avoid reading too far, just skip it */
		if ((r % erase_granule * 10) + (r % erase_granule * 2) > erase_granule * 10)
			continue;

		rc = blocklevel_read(bl, erase_granule * 20 + (r % erase_granule * 10), tmp, r % erase_granule * 2);
		if (rc) {
			ERR("blocklevel_read(0x%08x, 0x%08x) failed with err %d\n", 0x20000 + (r % 0x100000), r % 0x2000, rc);
			goto out;
		}
		rc = memcmp(((char *)test) + (r % erase_granule * 10), tmp, r % erase_granule * 2);
		if (rc) {
			ERR("%s:%d read back miscompare (pos: 0x%08x)\n", __FILE__, __LINE__, 0x20000 + (r % 0x10000));
			goto out;
		}
	}
out:
	free(tmp);
	return rc;
}

int main(void)
{
	struct blocklevel_device *bl;
	int rc;

	libflash_debug = true;

	mbox_server_init();

#ifdef __STRICT_TEST__
	printf("Found __STRICT_TEST__, this may take time time.\n");
#else
	printf("__STRICT_TEST__ not found, use make strict-check for a more\n");
	printf("thorough test, it will take significantly longer.\n");
#endif

	printf("Doing mbox-flash V1 tests\n");

	/* run test */
	mbox_flash_init(&bl);
	rc = run_flash_test(bl);
	if (rc)
		goto out;
	/*
	 * Trick mbox-flash into thinking there was a reboot so we can
	 * switch to v2
	 */

	printf("Doing mbox-flash V2 tests\n");

	mbox_server_reset(2, 12);

	/* Do all the tests again */
	rc = run_flash_test(bl);
	if (rc)
		goto out;

	mbox_server_reset(2, 17);

	/* Do all the tests again */
	rc = run_flash_test(bl);
	if (rc)
		goto out;


	printf("Doing mbox-flash V3 tests\n");

	mbox_server_reset(3, 20);

	/* Do all the tests again */
	rc = run_flash_test(bl);


out:
	mbox_flash_exit(bl);

	mbox_server_destroy();

	return rc;
}
