228 lines
6.5 KiB
C
228 lines
6.5 KiB
C
/* 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;
|
|
}
|