/* Copyright 2013-2018 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 <stdlib.h>
#include <ipmi.h>
#include <lock.h>
#include <opal.h>
#include <device.h>
#include <timer.h>
#include <timebase.h>
#include <pool.h>
#include <skiboot.h>

#define TIMER_USE_DONT_LOG	0x80
#define TIMER_USE_DONT_STOP	0x40
#define TIMER_USE_POST		0x02

/* WDT expiration actions */
#define WDT_PRETIMEOUT_SMI	0x10
#define WDT_RESET_ACTION 	0x01
#define WDT_NO_ACTION		0x00

/* IPMI defined custom completion codes for the watchdog */
#define WDT_CC_OK		0x00
#define WDT_CC_NOT_INITIALIZED	0x80

/* Flags used for IPMI callbacks */
#define WDT_SET_DO_RESET	0x01
#define WDT_RESET_NO_REINIT	0x01

/* How long to set the overall watchdog timeout for. In units of
 * 100ms. If the timer is not reset within this time the watchdog
 * expiration action will occur. */
#define WDT_TIMEOUT		600

/* How often to reset the timer using schedule_timer(). Too short and
we risk accidentally resetting the system due to opal_run_pollers() not
being called in time, too short and we waste time resetting the wdt
more frequently than necessary. */
#define WDT_MARGIN		300

static struct timer wdt_timer;
static bool wdt_stopped;
static bool wdt_ticking;

/* Saved values from the last watchdog set action */
static uint8_t last_action;
static uint16_t last_count;
static uint8_t last_pretimeout;

static void reset_wdt(struct timer *t, void *data, uint64_t now);

static void set_wdt_complete(struct ipmi_msg *msg)
{
	const uintptr_t flags = (uintptr_t)msg->user_data;

	if (flags & WDT_SET_DO_RESET) {
		/* Make sure the reset action does not create a loop and
		 * perform a reset in the case where the BMC send an
		 * uninitialized error. */
		reset_wdt(NULL, (void *)WDT_RESET_NO_REINIT, 0);
	}

	ipmi_free_msg(msg);
}

static void set_wdt(uint8_t action, uint16_t count, uint8_t pretimeout,
		bool dont_stop, bool do_reset)
{
	struct ipmi_msg *ipmi_msg;
	uintptr_t completion_flags = 0;

	if (do_reset)
		completion_flags |= WDT_SET_DO_RESET;

	/* Save the values prior to issuing the set operation so that we can
	 * re-initialize the watchdog in error cases. */
	last_action = action;
	last_count = count;
	last_pretimeout = pretimeout;

	ipmi_msg = ipmi_mkmsg(IPMI_DEFAULT_INTERFACE, IPMI_SET_WDT,
			      set_wdt_complete, NULL, NULL, 6, 0);
	if (!ipmi_msg) {
		prerror("Unable to allocate set wdt message\n");
		return;
	}
	ipmi_msg->error = set_wdt_complete;
	ipmi_msg->user_data = (void *)completion_flags;
	ipmi_msg->data[0] = TIMER_USE_POST |
		TIMER_USE_DONT_LOG |
		(dont_stop ? TIMER_USE_DONT_STOP : 0);
	ipmi_msg->data[1] = action;			/* Timer Actions */
	ipmi_msg->data[2] = pretimeout;			/* Pre-timeout Interval */
	ipmi_msg->data[3] = 0;				/* Timer Use Flags */
	ipmi_msg->data[4] = count & 0xff;		/* Initial countdown (lsb) */
	ipmi_msg->data[5] = (count >> 8) & 0xff;	/* Initial countdown (msb) */
	ipmi_queue_msg(ipmi_msg);
}

static void reset_wdt_complete(struct ipmi_msg *msg)
{
	const uintptr_t flags = (uintptr_t)msg->user_data;
	uint64_t reset_delay_ms = (WDT_TIMEOUT - WDT_MARGIN) * 100;

	if (msg->cc == WDT_CC_NOT_INITIALIZED &&
			!(flags & WDT_RESET_NO_REINIT)) {
		/* If our timer was not initialized on the BMC side, we should
		 * perform a single attempt to set it up again. */
		set_wdt(last_action, last_count, last_pretimeout, true, true);
	} else if (msg->cc != WDT_CC_OK) {
		/* Use a short (10s) timeout before performing the next reset
		 * if we encounter an unknown error. This makes sure that we
		 * are able to reset and re-initialize the timer since it might
		 * expire. */
		reset_delay_ms = 10 * 1000;
	}

	/* If we are inside of skiboot we need to periodically restart the
	 * timer. Reschedule a reset so it happens before the timeout. */
	if (wdt_ticking)
		schedule_timer(&wdt_timer, msecs_to_tb(reset_delay_ms));

	ipmi_free_msg(msg);
}

static struct ipmi_msg *wdt_reset_mkmsg(void)
{
	struct ipmi_msg *ipmi_msg;

	ipmi_msg = ipmi_mkmsg(IPMI_DEFAULT_INTERFACE, IPMI_RESET_WDT,
			      reset_wdt_complete, NULL, NULL, 0, 0);
	if (!ipmi_msg) {
		prerror("Unable to allocate reset wdt message\n");
		return NULL;
	}
	ipmi_msg->error = reset_wdt_complete;

	return ipmi_msg;
}

static void sync_reset_wdt(void)
{
	struct ipmi_msg *ipmi_msg;

	if ((ipmi_msg = wdt_reset_mkmsg()))
		ipmi_queue_msg_sync(ipmi_msg);
}

static void reset_wdt(struct timer *t __unused, void *data,
		      uint64_t now __unused)
{
	struct ipmi_msg *ipmi_msg;

	if ((ipmi_msg = wdt_reset_mkmsg())) {
		ipmi_msg->user_data = data;
		ipmi_queue_msg_head(ipmi_msg);
	}
}

void ipmi_wdt_stop(void)
{
	if (!wdt_stopped) {
		/* Make sure the background reset timer is disabled before
		 * stopping the watchdog. If we issue a reset after disabling
		 * the timer, it will be re-enabled. */
		wdt_ticking = false;
		cancel_timer(&wdt_timer);

		/* Configure the watchdog to be disabled and do no action
		 * in case the underlying implementation is buggy and times
		 * out anyway. */
		wdt_stopped = true;
		set_wdt(WDT_NO_ACTION, 100, 0, false, false);
	}
}

void ipmi_wdt_final_reset(void)
{
	/* We can safely stop the timer prior to setting up our final
	 * watchdog timeout since we have enough margin before the
	 * timeout. */
	wdt_ticking = false;
	cancel_timer(&wdt_timer);

	/*
	 * We're going to wait a little while before requiring
	 * BOOTKERNEL to have IPMI watchdog support so that people
	 * can catch up in their development environments.
	 * If you still read this after 2018, send a patch!
	 */
#if 0
	/* Configure the watchdog and make sure it is still enabled */
	set_wdt(WDT_RESET_ACTION | WDT_PRETIMEOUT_SMI, WDT_TIMEOUT,
		WDT_MARGIN/10, true, true);
	sync_reset_wdt();
#else
	set_wdt(WDT_NO_ACTION, 100, 0, false, false);
#endif
	ipmi_set_boot_count();
}

void ipmi_wdt_init(void)
{
	init_timer(&wdt_timer, reset_wdt, NULL);
	set_wdt(WDT_RESET_ACTION, WDT_TIMEOUT, 0, true, false);

	/* Start the WDT. We do it synchronously to make sure it has
	 * started before skiboot continues booting. Otherwise we
	 * could crash before the wdt has actually been started. */
	wdt_ticking = true;
	sync_reset_wdt();

	return;
}
