/* Copyright 2020 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.
 *
 * Test CBI
 */

#include "common.h"
#include "console.h"
#include "cros_board_info.h"
#include "ec_commands.h"
#include "gpio.h"
#include "i2c.h"
#include "test_util.h"
#include "util.h"

static void test_setup(void)
{
	/* Make sure that write protect is disabled */
#ifdef CONFIG_WP_ACTIVE_HIGH
	gpio_set_level(GPIO_WP, 0);
#else
	gpio_set_level(GPIO_WP_L, 1);
#endif /* CONFIG_WP_ACTIVE_HIGH */

	cbi_create();
	cbi_write();
}

static void test_teardown(void)
{
}

DECLARE_EC_TEST(test_uint8)
{
	uint8_t d8;
	uint32_t d32;
	uint8_t size;
	const int tag = 0xff;

	/* Set & get uint8_t */
	d8 = 0xa5;
	zassert_equal(cbi_set_board_info(tag, &d8, sizeof(d8)), EC_SUCCESS,
		      NULL);
	size = 1;
	zassert_equal(cbi_get_board_info(tag, &d8, &size), EC_SUCCESS, NULL);
	zassert_equal(d8, 0xa5, "0x%x, 0x%x", d8, 0xa5);
	zassert_equal(size, 1, "%x, %x", size, 1);

	/* Size-up */
	d32 = 0x1234abcd;
	zassert_equal(cbi_set_board_info(tag, (void *)&d32, sizeof(d32)),
		      EC_SUCCESS, NULL);
	size = 4;
	zassert_equal(cbi_get_board_info(tag, (void *)&d32, &size), EC_SUCCESS,
		      NULL);
	zassert_equal(d32, 0x1234abcd, "0x%x, 0x%x", d32, 0x1234abcd);
	zassert_equal(size, 4, "%u, %u", size, 4);

	return EC_SUCCESS;
}

DECLARE_EC_TEST(test_uint32)
{
	uint8_t d8;
	uint32_t d32;
	uint8_t size;
	const int tag = 0xff;

	/* Set & get uint32_t */
	d32 = 0x1234abcd;
	zassert_equal(cbi_set_board_info(tag, (void *)&d32, sizeof(d32)),
		      EC_SUCCESS, NULL);
	size = 4;
	zassert_equal(cbi_get_board_info(tag, (void *)&d32, &size), EC_SUCCESS,
		      NULL);
	zassert_equal(d32, 0x1234abcd, "0x%x, 0x%x", d32, 0x1234abcd);
	zassert_equal(size, 4, "%u, %u", size, 4);

	/* Size-down */
	d8 = 0xa5;
	zassert_equal(cbi_set_board_info(tag, &d8, sizeof(d8)), EC_SUCCESS,
		      NULL);
	size = 1;
	zassert_equal(cbi_get_board_info(tag, &d8, &size), EC_SUCCESS, NULL);
	zassert_equal(d8, 0xa5, "0x%x, 0x%x", d8, 0xa5);
	zassert_equal(size, 1, "%u, %u", size, 1);

	return EC_SUCCESS;
}

DECLARE_EC_TEST(test_string)
{
	const uint8_t string[] = "abcdefghijklmn";
	uint8_t buf[32];
	uint8_t size;
	const int tag = 0xff;

	/* Set & get string */
	zassert_equal(cbi_set_board_info(tag, string, sizeof(string)),
		      EC_SUCCESS, NULL);
	size = sizeof(buf);
	zassert_equal(cbi_get_board_info(tag, buf, &size), EC_SUCCESS, NULL);
	zassert_equal(strncmp(buf, string, sizeof(string)), 0, NULL);
	/* Size contains null byte */
	/* This should be zassert_equal, but for EC test fmt is always "0x%x"
	 * which will generate compilation error.
	 */
	zassert_true((size_t)size - 1 == strlen(buf), "%zu, %zu",
		     (size_t)size - 1, strlen(buf));

	/* Read buffer too small */
	size = 4;
	zassert_equal(cbi_get_board_info(tag, buf, &size), EC_ERROR_INVAL,
		      NULL);

	return EC_SUCCESS;
}

DECLARE_EC_TEST(test_not_found)
{
	uint8_t d8;
	const int tag = 0xff;
	uint8_t size;

	size = 1;
	zassert_equal(cbi_get_board_info(tag, &d8, &size), EC_ERROR_UNKNOWN,
		      NULL);

	return EC_SUCCESS;
}

DECLARE_EC_TEST(test_too_large)
{
	uint8_t buf[CBI_IMAGE_SIZE-1];
	const int tag = 0xff;

	/* Data too large */
	memset(buf, 0xa5, sizeof(buf));
	zassert_equal(cbi_set_board_info(tag, buf, sizeof(buf)),
		      EC_ERROR_OVERFLOW, NULL);

	return EC_SUCCESS;
}

DECLARE_EC_TEST(test_all_tags)
{
	uint8_t d8;
	uint32_t d32;
	uint64_t d64;
	const char string[] = "abc";
	uint8_t buf[32];
	uint8_t size;
	int count = 0;

	/* Populate all data and read out */
	d8 = 0x12;
	zassert_equal(cbi_set_board_info(CBI_TAG_BOARD_VERSION, &d8,
					 sizeof(d8)),
		      EC_SUCCESS, NULL);
	count++;
	zassert_equal(cbi_set_board_info(CBI_TAG_OEM_ID, &d8, sizeof(d8)),
		      EC_SUCCESS, NULL);
	count++;
	zassert_equal(cbi_set_board_info(CBI_TAG_SKU_ID, &d8, sizeof(d8)),
		      EC_SUCCESS, NULL);
	count++;
	zassert_equal(cbi_set_board_info(CBI_TAG_DRAM_PART_NUM,
					 string, sizeof(string)),
		      EC_SUCCESS, NULL);
	count++;
	zassert_equal(cbi_set_board_info(CBI_TAG_OEM_NAME,
					 string, sizeof(string)),
		      EC_SUCCESS, NULL);
	count++;
	zassert_equal(cbi_set_board_info(CBI_TAG_MODEL_ID, &d8, sizeof(d8)),
		      EC_SUCCESS, NULL);
	count++;
	zassert_equal(cbi_set_board_info(CBI_TAG_FW_CONFIG, &d8, sizeof(d8)),
		      EC_SUCCESS, NULL);
	count++;
	zassert_equal(cbi_set_board_info(CBI_TAG_PCB_SUPPLIER, &d8,
					 sizeof(d8)),
		      EC_SUCCESS, NULL);
	count++;
	zassert_equal(cbi_set_board_info(CBI_TAG_SSFC, &d8, sizeof(d8)),
		      EC_SUCCESS, NULL);
	count++;
	zassert_equal(cbi_set_board_info(CBI_TAG_REWORK_ID, &d8, sizeof(d8)),
		      EC_SUCCESS, NULL);
	count++;

	/* Read out all */
	zassert_equal(cbi_get_board_version(&d32), EC_SUCCESS, NULL);
	zassert_equal(d32, d8, "0x%x, 0x%x", d32, d8);
	zassert_equal(cbi_get_oem_id(&d32), EC_SUCCESS, NULL);
	zassert_equal(d32, d8, "0x%x, 0x%x", d32, d8);
	zassert_equal(cbi_get_sku_id(&d32), EC_SUCCESS, NULL);
	zassert_equal(d32, d8, "0x%x, 0x%x", d32, d8);
	size = sizeof(buf);
	zassert_equal(cbi_get_board_info(CBI_TAG_DRAM_PART_NUM, buf, &size),
		      EC_SUCCESS, NULL);
	zassert_equal(strncmp(buf, string, sizeof(string)), 0, NULL);
	/* This should be zassert_equal, but for EC test fmt is always "0x%x"
	 * which will generate compilation error.
	 */
	zassert_true((size_t)size - 1 == strlen(buf), "%zu, %zu",
		     (size_t)size - 1, strlen(buf));
	size = sizeof(buf);
	zassert_equal(cbi_get_board_info(CBI_TAG_OEM_NAME, buf, &size),
		      EC_SUCCESS, NULL);
	zassert_equal(strncmp(buf, string, sizeof(string)), 0, NULL);
	/* This should be zassert_equal, but for EC test fmt is always "0x%x"
	 * which will generate compilation error.
	 */
	zassert_true((size_t)size - 1 == strlen(buf), "%zu, %zu",
		     (size_t)size - 1, strlen(buf));
	zassert_equal(cbi_get_model_id(&d32), EC_SUCCESS, NULL);
	zassert_equal(d32, d8, "0x%x, 0x%x", d32, d8);
	zassert_equal(cbi_get_fw_config(&d32), EC_SUCCESS, NULL);
	zassert_equal(d32, d8, "0x%x, 0x%x", d32, d8);
	zassert_equal(cbi_get_pcb_supplier(&d32), EC_SUCCESS, NULL);
	zassert_equal(d32, d8, "0x%x, 0x%x", d32, d8);
	zassert_equal(cbi_get_ssfc(&d32), EC_SUCCESS, NULL);
	zassert_equal(d32, d8, "0x%x, 0x%x", d32, d8);
	zassert_equal(cbi_get_rework_id(&d64), EC_SUCCESS, NULL);
	/* This should be zassert_equal, but for EC test fmt is always "0x%x"
	 * which will generate compilation error.
	 */
	zassert_true((unsigned long long)d64 == (unsigned long long)d8,
		     "0x%llx, 0x%llx", (unsigned long long)d64,
		     (unsigned long long)d8);

	/* Fail if a (new) tag is missing from the unit test. */
	zassert_equal(count, CBI_TAG_COUNT, "%d, %d", count, CBI_TAG_COUNT);

	/* Write protect */
#ifdef CONFIG_WP_ACTIVE_HIGH
	gpio_set_level(GPIO_WP, 1);
#else
	gpio_set_level(GPIO_WP_L, 0);
#endif /* CONFIG_WP_ACTIVE_HIGH */
	zassert_equal(cbi_write(), EC_ERROR_ACCESS_DENIED, NULL);

	return EC_SUCCESS;
}

DECLARE_EC_TEST(test_bad_crc)
{
	uint8_t d8;
	const int tag = 0xff;
	uint8_t size;
	int crc;

	/* Bad CRC */
	d8 = 0xa5;
	zassert_equal(cbi_set_board_info(tag, &d8, sizeof(d8)), EC_SUCCESS,
		      NULL);
	i2c_read8(I2C_PORT_EEPROM, I2C_ADDR_EEPROM_FLAGS,
		   offsetof(struct cbi_header, crc), &crc);
	i2c_write8(I2C_PORT_EEPROM, I2C_ADDR_EEPROM_FLAGS,
		   offsetof(struct cbi_header, crc), ++crc);
	cbi_invalidate_cache();
	size = sizeof(d8);
	zassert_equal(cbi_get_board_info(tag, &d8, &size), EC_ERROR_UNKNOWN,
		      NULL);

	return EC_SUCCESS;
}

TEST_SUITE(test_suite_cbi)
{
	ztest_test_suite(test_cbi,
			 ztest_unit_test_setup_teardown(test_uint8, test_setup,
							test_teardown),
			 ztest_unit_test_setup_teardown(test_uint32, test_setup,
							test_teardown),
			 ztest_unit_test_setup_teardown(test_string, test_setup,
							test_teardown),
			 ztest_unit_test_setup_teardown(test_not_found,
							test_setup,
							test_teardown),
			 ztest_unit_test_setup_teardown(test_too_large,
							test_setup,
							test_teardown),
			 ztest_unit_test_setup_teardown(test_all_tags,
							test_setup,
							test_teardown),
			 ztest_unit_test_setup_teardown(test_bad_crc,
							test_setup,
							test_teardown));
	ztest_run_test_suite(test_cbi);
}
