1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
|
// SPDX-License-Identifier: Apache-2.0
/*
* Copyright 2013-2018 IBM Corp.
* Copyright 2018 Google Corp.
*/
#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;
}
|