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

/* System module for emulator */

#include "common.h"
#include "ec_commands.h"
#include "host_test.h"
#include "panic.h"
#include "persistence.h"
#include "reboot.h"
#include "system.h"
#include "timer.h"
#include "util.h"

// Forward declaration from <stdlib.h> to avoid declaration conflicts.
void exit(int);

#define SHARED_MEM_SIZE 0x2000 /* bytes */
#define RAM_DATA_SIZE (sizeof(struct panic_data) + 512) /* bytes */
uint8_t __shared_mem_buf[SHARED_MEM_SIZE + RAM_DATA_SIZE];

static char *__ram_data = __shared_mem_buf + SHARED_MEM_SIZE;

static enum ec_image __running_copy;

static void ramdata_set_persistent(void)
{
	FILE *f = get_persistent_storage("ramdata", "wb");
	int sz;

	ASSERT(f != NULL);

	sz = fwrite(__ram_data, RAM_DATA_SIZE, 1, f);
	ASSERT(sz == 1);

	release_persistent_storage(f);
}

static void ramdata_get_persistent(void)
{
	FILE *f = get_persistent_storage("ramdata", "rb");

	if (f == NULL) {
		fprintf(stderr,
			"No RAM data found. Initializing to 0x00.\n");
		memset(__ram_data, 0, RAM_DATA_SIZE);
		return;
	}

	fread(__ram_data, RAM_DATA_SIZE, 1, f);

	release_persistent_storage(f);

	/*
	 * Assumes RAM data doesn't preserve across reboot except for sysjump.
	 * Clear persistent data once it's read.
	 */
	remove_persistent_storage("ramdata");
}

static void set_image_copy(uint32_t copy)
{
	FILE *f = get_persistent_storage("image_copy", "wb");

	ASSERT(f != NULL);
	ASSERT(fwrite(&copy, sizeof(copy), 1, f) == 1);

	release_persistent_storage(f);
}

static uint32_t get_image_copy(void)
{
	FILE *f = get_persistent_storage("image_copy", "rb");
	uint32_t ret;

	if (f == NULL)
		return EC_IMAGE_UNKNOWN;
	fread(&ret, sizeof(ret), 1, f);
	release_persistent_storage(f);
	remove_persistent_storage("image_copy");

	return ret;
}

static void save_reset_flags(uint32_t flags)
{
	FILE *f = get_persistent_storage("reset_flags", "wb");

	ASSERT(f != NULL);
	ASSERT(fwrite(&flags, sizeof(flags), 1, f) == 1);

	release_persistent_storage(f);
}

static uint32_t load_reset_flags(void)
{
	FILE *f = get_persistent_storage("reset_flags", "rb");
	uint32_t ret;

	if (f == NULL)
		return EC_RESET_FLAG_POWER_ON;
	fread(&ret, sizeof(ret), 1, f);
	release_persistent_storage(f);
	remove_persistent_storage("reset_flags");

	return ret;
}

static void save_time(timestamp_t t)
{
	FILE *f = get_persistent_storage("time", "wb");

	ASSERT(f != NULL);
	ASSERT(fwrite(&t, sizeof(t), 1, f) == 1);

	release_persistent_storage(f);
}

static int load_time(timestamp_t *t)
{
	FILE *f = get_persistent_storage("time", "rb");

	if (f == NULL)
		return 0;
	fread(t, sizeof(*t), 1, f);
	release_persistent_storage(f);
	remove_persistent_storage("time");

	return 1;
}

test_mockable struct panic_data *panic_get_data(void)
{
	return (struct panic_data *)
		(__ram_data + RAM_DATA_SIZE - sizeof(struct panic_data));
}

test_mockable uintptr_t get_panic_data_start()
{
	return (uintptr_t)
		(__ram_data + RAM_DATA_SIZE - sizeof(struct panic_data));
}

test_mockable void system_reset(int flags)
{
	uint32_t save_flags = 0;
	if (flags & SYSTEM_RESET_PRESERVE_FLAGS)
		save_flags = system_get_reset_flags() | EC_RESET_FLAG_PRESERVED;
	if (flags & SYSTEM_RESET_LEAVE_AP_OFF)
		save_flags |= EC_RESET_FLAG_AP_OFF;
	if (flags & SYSTEM_RESET_HARD)
		save_flags |= EC_RESET_FLAG_HARD;
	if (save_flags)
		save_reset_flags(save_flags);
	emulator_reboot();
}

test_mockable void system_hibernate(uint32_t seconds, uint32_t microseconds)
{
	uint32_t i;

	if (board_hibernate)
		board_hibernate();

	save_reset_flags(EC_RESET_FLAG_HIBERNATE);

	if (!seconds && !microseconds)
		exit(EXIT_CODE_HIBERNATE);

	for (i = 0; i < seconds; ++i)
		udelay(SECOND);
	udelay(microseconds);
	emulator_reboot();
}

test_mockable int system_is_locked(void)
{
	return 0;
}

#ifdef TEST_FUZZ
/* When fuzzing, do not allow sysjumps. */
int system_run_image_copy(enum ec_image copy)
{
	ccprints("Emulator would sysjump here. Fuzzing: doing nothing.");
	return EC_ERROR_UNKNOWN;
}
#endif

const char *system_get_chip_vendor(void)
{
	return "chromeos";
}

const char *system_get_chip_name(void)
{
	return "emu";
}

const char *system_get_chip_revision(void)
{
	return "";
}

int system_get_bbram(enum system_bbram_idx idx, uint8_t *value)
{
	return EC_ERROR_UNIMPLEMENTED;
}

int system_set_bbram(enum system_bbram_idx idx, uint8_t value)
{
	return EC_ERROR_UNIMPLEMENTED;
}

enum ec_image system_get_image_copy(void)
{
	return __running_copy;
}

int system_set_scratchpad(uint32_t value)
{
	FILE *f = get_persistent_storage("scratchpad", "w");

	fprintf(f, "%lu", value);
	release_persistent_storage(f);

	return EC_SUCCESS;
}

uint32_t system_get_scratchpad(void)
{
	FILE *f = get_persistent_storage("scratchpad", "r");
	uint32_t value;
	int success;

	if (f == NULL)
		return 0;

	success = fscanf(f, "%u", &value);
	release_persistent_storage(f);

	if (success)
		return value;
	else
		return 0;
}

static void __jump_resetvec(void)
{
	save_time(get_time());
	ramdata_set_persistent();
	emulator_reboot();
}

static void __ro_jump_resetvec(void)
{
	set_image_copy(EC_IMAGE_RO);
	__jump_resetvec();
}

static void __rw_jump_resetvec(void)
{
	set_image_copy(EC_IMAGE_RW);
	__jump_resetvec();
}

void system_pre_init(void)
{
	timestamp_t t;

	if (load_time(&t))
		force_time(t);

	ramdata_get_persistent();
	__running_copy = get_image_copy();
	if (__running_copy == EC_IMAGE_UNKNOWN) {
		__running_copy = EC_IMAGE_RO;
		system_set_reset_flags(load_reset_flags());
	}

	*(uintptr_t *)(__host_flash + CONFIG_RO_MEM_OFF + 4) =
		(uintptr_t)__ro_jump_resetvec;
	*(uintptr_t *)(__host_flash + CONFIG_RW_MEM_OFF + 4) =
		(uintptr_t)__rw_jump_resetvec;
}
