/*
 * This file is part of the coreboot project.
 *
 * Copyright (C) 2012 The ChromiumOS Authors.  All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#include <console/console.h>
#include <cpu/x86/smm.h>
#include <elog.h>

#define GSMI_RET_SUCCESS		0x00
#define GSMI_RET_INVALID_PARAMETER	0x82
#define GSMI_RET_UNSUPPORTED		0x83

#define GSMI_CMD_SET_EVENT_LOG		0x08
#define GSMI_CMD_CLEAR_EVENT_LOG	0x09
#define GSMI_CMD_LOG_S0IX_SUSPEND	0x0a
#define GSMI_CMD_LOG_S0IX_RESUME	0x0b
#define GSMI_CMD_HANDSHAKE_TYPE		0xc1

#define GSMI_HANDSHAKE_NONE		0x7f
#define GSMI_LOG_ENTRY_TYPE_KERNEL	0xDEAD

struct gsmi_set_eventlog_param {
	u32 data_ptr;
	u32 data_len;
	u32 type;
} __packed;

struct gsmi_set_eventlog_type1 {
	u16 type;
	u32 instance;
} __packed;

struct gsmi_clear_eventlog_param {
	u32 percentage;
	u32 data_type;
} __packed;

void __weak elog_gsmi_cb_platform_log_wake_source(void)
{
	/* Default weak implementation, does nothing. */
}

void __weak elog_gsmi_cb_mainboard_log_wake_source(void)
{
	/* Default weak implementation, does nothing. */
}

/* Param is usually EBX, ret in EAX */
u32 gsmi_exec(u8 command, u32 *param)
{
	struct gsmi_set_eventlog_param *sel;
	struct gsmi_set_eventlog_type1 *type1;
	struct gsmi_clear_eventlog_param *cel;
	u32 ret = GSMI_RET_UNSUPPORTED;

	switch (command) {
	case GSMI_CMD_HANDSHAKE_TYPE:
		/* Used by kernel to verify basic SMI functionality */
		printk(BIOS_DEBUG, "GSMI Handshake\n");
		ret = GSMI_HANDSHAKE_NONE;
		break;

	case GSMI_CMD_SET_EVENT_LOG:
		/* Look for a type1 event */
		sel = (struct gsmi_set_eventlog_param *)(*param);
		if (!sel)
			break;

		/* Make sure the input is usable */
		if (sel->type != 1 && sel->data_ptr != 0 &&
		    sel->data_len != sizeof(struct gsmi_set_eventlog_type1))
			break;

		/* Event structure within the data buffer */
		type1 = (struct gsmi_set_eventlog_type1 *)(sel->data_ptr);
		if (!type1)
			break;

		printk(BIOS_DEBUG, "GSMI Set Event Log "
		       "(type=0x%x instance=0x%x)\n",
		       type1->type, type1->instance);

		if (type1->type == GSMI_LOG_ENTRY_TYPE_KERNEL) {
			/* Special case for linux kernel shutdown reason */
			elog_add_event_dword(ELOG_TYPE_OS_EVENT,
					     type1->instance);
		} else {
			/* Add other events that may be used for testing */
			elog_add_event_dword(type1->type, type1->instance);
		}
		ret = GSMI_RET_SUCCESS;
		break;

	case GSMI_CMD_CLEAR_EVENT_LOG:
		/* Get parameter buffer even though we don't use it */
		cel = (struct gsmi_clear_eventlog_param *)(*param);
		if (!cel)
			break;

		printk(BIOS_DEBUG, "GSMI Clear Event Log (%u%% type=%u)\n",
		       cel->percentage, cel->data_type);

		if (elog_clear() == 0)
			ret = GSMI_RET_SUCCESS;
		break;

	case GSMI_CMD_LOG_S0IX_SUSPEND:
	case GSMI_CMD_LOG_S0IX_RESUME:
		ret = GSMI_RET_SUCCESS;

		if (command == GSMI_CMD_LOG_S0IX_SUSPEND)
			elog_add_event(ELOG_TYPE_S0IX_ENTER);
		else {
			elog_add_event(ELOG_TYPE_S0IX_EXIT);
			elog_gsmi_cb_platform_log_wake_source();
			elog_gsmi_cb_mainboard_log_wake_source();
		}
		break;

	default:
		printk(BIOS_DEBUG, "GSMI Unknown: 0x%02x\n", command);
		break;
	}

	return ret;
}