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
219
220
221
222
223
224
225
226
227
228
|
/* 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;
}
|