1 #include "watchdog.hpp"
2
3 #include "watchdog_service.hpp"
4
5 #include <endian.h>
6
7 #include <ipmid/api.hpp>
8 #include <phosphor-logging/elog-errors.hpp>
9 #include <phosphor-logging/elog.hpp>
10 #include <phosphor-logging/lg2.hpp>
11 #include <xyz/openbmc_project/Common/error.hpp>
12
13 #include <bitset>
14 #include <cstdint>
15 #include <string>
16
17 using phosphor::logging::commit;
18 using phosphor::logging::level;
19 using phosphor::logging::log;
20 using sdbusplus::error::xyz::openbmc_project::common::InternalFailure;
21
22 static bool lastCallSuccessful = false;
23
reportError()24 void reportError()
25 {
26 // We don't want to fill the SEL with errors if the daemon dies and doesn't
27 // come back but the watchdog keeps on ticking. Instead, we only report the
28 // error if we haven't reported one since the last successful call
29 if (!lastCallSuccessful)
30 {
31 return;
32 }
33 lastCallSuccessful = false;
34
35 // TODO: This slow down the end of the IPMI transaction waiting
36 // for the commit to finish. commit<>() can take at least 5 seconds
37 // to complete. 5s is very slow for an IPMI command and ends up
38 // congesting the IPMI channel needlessly, especially if the watchdog
39 // is ticking fairly quickly and we have some transient issues.
40 commit<InternalFailure>();
41 }
42
ipmiAppResetWatchdogTimer()43 ipmi::RspType<> ipmiAppResetWatchdogTimer()
44 {
45 try
46 {
47 WatchdogService wd_service;
48
49 // Notify the caller if we haven't initialized our timer yet
50 // so it can configure actions and timeouts
51 if (!wd_service.getInitialized())
52 {
53 lastCallSuccessful = true;
54
55 constexpr uint8_t ccWatchdogNotInit = 0x80;
56 return ipmi::response(ccWatchdogNotInit);
57 }
58
59 // The ipmi standard dictates we enable the watchdog during reset
60 wd_service.resetTimeRemaining(true);
61 lastCallSuccessful = true;
62 return ipmi::responseSuccess();
63 }
64 catch (const InternalFailure& e)
65 {
66 reportError();
67 return ipmi::responseUnspecifiedError();
68 }
69 catch (const std::exception& e)
70 {
71 lg2::error("wd_reset: {ERROR}", "ERROR", e);
72 return ipmi::responseUnspecifiedError();
73 }
74 catch (...)
75 {
76 lg2::error("wd_reset: Unknown Error");
77 return ipmi::responseUnspecifiedError();
78 }
79 }
80
81 static constexpr uint8_t wd_timeout_action_mask = 0x3;
82
83 static constexpr uint8_t wdTimerUseResTimer1 = 0x0;
84 static constexpr uint8_t wdTimerUseResTimer2 = 0x6;
85 static constexpr uint8_t wdTimerUseResTimer3 = 0x7;
86
87 static constexpr uint8_t wdTimeoutActionMax = 3;
88 static constexpr uint8_t wdTimeoutInterruptTimer = 0x04;
89
90 enum class IpmiAction : uint8_t
91 {
92 None = 0x0,
93 HardReset = 0x1,
94 PowerOff = 0x2,
95 PowerCycle = 0x3,
96 };
97
98 /** @brief Converts an IPMI Watchdog Action to DBUS defined action
99 * @param[in] ipmi_action The IPMI Watchdog Action
100 * @return The Watchdog Action that the ipmi_action maps to
101 */
ipmiActionToWdAction(IpmiAction ipmi_action)102 WatchdogService::Action ipmiActionToWdAction(IpmiAction ipmi_action)
103 {
104 switch (ipmi_action)
105 {
106 case IpmiAction::None:
107 {
108 return WatchdogService::Action::None;
109 }
110 case IpmiAction::HardReset:
111 {
112 return WatchdogService::Action::HardReset;
113 }
114 case IpmiAction::PowerOff:
115 {
116 return WatchdogService::Action::PowerOff;
117 }
118 case IpmiAction::PowerCycle:
119 {
120 return WatchdogService::Action::PowerCycle;
121 }
122 default:
123 {
124 throw std::domain_error("IPMI Action is invalid");
125 }
126 }
127 }
128
129 enum class IpmiTimerUse : uint8_t
130 {
131 Reserved = 0x0,
132 BIOSFRB2 = 0x1,
133 BIOSPOST = 0x2,
134 OSLoad = 0x3,
135 SMSOS = 0x4,
136 OEM = 0x5,
137 };
138
ipmiTimerUseToWdTimerUse(IpmiTimerUse ipmiTimerUse)139 WatchdogService::TimerUse ipmiTimerUseToWdTimerUse(IpmiTimerUse ipmiTimerUse)
140 {
141 switch (ipmiTimerUse)
142 {
143 case IpmiTimerUse::Reserved:
144 {
145 return WatchdogService::TimerUse::Reserved;
146 }
147 case IpmiTimerUse::BIOSFRB2:
148 {
149 return WatchdogService::TimerUse::BIOSFRB2;
150 }
151 case IpmiTimerUse::BIOSPOST:
152 {
153 return WatchdogService::TimerUse::BIOSPOST;
154 }
155 case IpmiTimerUse::OSLoad:
156 {
157 return WatchdogService::TimerUse::OSLoad;
158 }
159 case IpmiTimerUse::SMSOS:
160 {
161 return WatchdogService::TimerUse::SMSOS;
162 }
163 case IpmiTimerUse::OEM:
164 {
165 return WatchdogService::TimerUse::OEM;
166 }
167 default:
168 {
169 return WatchdogService::TimerUse::Reserved;
170 }
171 }
172 }
173
174 static bool timerNotLogFlags = false;
175 static std::bitset<8> timerUseExpirationFlags = 0;
176 static uint3_t timerPreTimeoutInterrupt = 0;
177 static constexpr uint8_t wdExpirationFlagReservedBit0 = 0x0;
178 static constexpr uint8_t wdExpirationFlagReservedBit6 = 0x6;
179 static constexpr uint8_t wdExpirationFlagReservedBit7 = 0x7;
180
181 /**@brief The Set Watchdog Timer ipmi command.
182 *
183 * @param
184 * - timerUse
185 * - dontStopTimer
186 * - dontLog
187 * - timerAction
188 * - pretimeout
189 * - expireFlags
190 * - initialCountdown
191 *
192 * @return completion code on success.
193 **/
ipmiSetWatchdogTimer(uint3_t timerUse,uint3_t reserved,bool dontStopTimer,bool dontLog,uint3_t timeoutAction,uint1_t reserved1,uint3_t preTimeoutInterrupt,uint1_t reserved2,uint8_t preTimeoutInterval,std::bitset<8> expFlagValue,uint16_t initialCountdown)194 ipmi::RspType<> ipmiSetWatchdogTimer(
195 uint3_t timerUse, uint3_t reserved, bool dontStopTimer, bool dontLog,
196 uint3_t timeoutAction, uint1_t reserved1, uint3_t preTimeoutInterrupt,
197 uint1_t reserved2, uint8_t preTimeoutInterval, std::bitset<8> expFlagValue,
198 uint16_t initialCountdown)
199 {
200 if ((timerUse == wdTimerUseResTimer1) ||
201 (timerUse == wdTimerUseResTimer2) ||
202 (timerUse == wdTimerUseResTimer3) ||
203 (timeoutAction > wdTimeoutActionMax) ||
204 (preTimeoutInterrupt == wdTimeoutInterruptTimer) ||
205 (reserved | reserved1 | reserved2 |
206 expFlagValue.test(wdExpirationFlagReservedBit0) |
207 expFlagValue.test(wdExpirationFlagReservedBit6) |
208 expFlagValue.test(wdExpirationFlagReservedBit7)))
209 {
210 return ipmi::responseInvalidFieldRequest();
211 }
212
213 if (preTimeoutInterval > (initialCountdown / 10))
214 {
215 return ipmi::responseInvalidFieldRequest();
216 }
217
218 timerNotLogFlags = dontLog;
219 timerPreTimeoutInterrupt = preTimeoutInterrupt;
220
221 try
222 {
223 WatchdogService wd_service;
224 // Stop the timer if the don't stop bit is not set
225 if (!(dontStopTimer))
226 {
227 wd_service.setEnabled(false);
228 }
229
230 // Set the action based on the request
231 const auto ipmi_action = static_cast<IpmiAction>(
232 static_cast<uint8_t>(timeoutAction) & wd_timeout_action_mask);
233 wd_service.setExpireAction(ipmiActionToWdAction(ipmi_action));
234
235 const auto ipmiTimerUse = types::enum_cast<IpmiTimerUse>(timerUse);
236 wd_service.setTimerUse(ipmiTimerUseToWdTimerUse(ipmiTimerUse));
237
238 wd_service.setExpiredTimerUse(WatchdogService::TimerUse::Reserved);
239
240 timerUseExpirationFlags &= ~expFlagValue;
241
242 // Set the new interval and the time remaining deci -> mill seconds
243 const uint64_t interval = initialCountdown * 100;
244 wd_service.setInterval(interval);
245 wd_service.resetTimeRemaining(false);
246
247 // Mark as initialized so that future resets behave correctly
248 wd_service.setInitialized(true);
249 wd_service.setLogTimeout(!dontLog);
250
251 lastCallSuccessful = true;
252 return ipmi::responseSuccess();
253 }
254 catch (const std::domain_error&)
255 {
256 return ipmi::responseInvalidFieldRequest();
257 }
258 catch (const InternalFailure& e)
259 {
260 reportError();
261 return ipmi::responseUnspecifiedError();
262 }
263 catch (const std::exception& e)
264 {
265 lg2::error("wd_set: {ERROR}", "ERROR", e);
266 return ipmi::responseUnspecifiedError();
267 }
268 catch (...)
269 {
270 lg2::error("wd_set: Unknown Error");
271 return ipmi::responseUnspecifiedError();
272 }
273 }
274
275 /** @brief Converts a DBUS Watchdog Action to IPMI defined action
276 * @param[in] wd_action The DBUS Watchdog Action
277 * @return The IpmiAction that the wd_action maps to
278 */
wdActionToIpmiAction(WatchdogService::Action wd_action)279 IpmiAction wdActionToIpmiAction(WatchdogService::Action wd_action)
280 {
281 switch (wd_action)
282 {
283 case WatchdogService::Action::None:
284 {
285 return IpmiAction::None;
286 }
287 case WatchdogService::Action::HardReset:
288 {
289 return IpmiAction::HardReset;
290 }
291 case WatchdogService::Action::PowerOff:
292 {
293 return IpmiAction::PowerOff;
294 }
295 case WatchdogService::Action::PowerCycle:
296 {
297 return IpmiAction::PowerCycle;
298 }
299 default:
300 {
301 // We have no method via IPMI to signal that the action is unknown
302 // or unmappable in some way.
303 // Just ignore the error and return NONE so the host can reconcile.
304 return IpmiAction::None;
305 }
306 }
307 }
308
wdTimerUseToIpmiTimerUse(WatchdogService::TimerUse wdTimerUse)309 IpmiTimerUse wdTimerUseToIpmiTimerUse(WatchdogService::TimerUse wdTimerUse)
310 {
311 switch (wdTimerUse)
312 {
313 case WatchdogService::TimerUse::Reserved:
314 {
315 return IpmiTimerUse::Reserved;
316 }
317 case WatchdogService::TimerUse::BIOSFRB2:
318 {
319 return IpmiTimerUse::BIOSFRB2;
320 }
321 case WatchdogService::TimerUse::BIOSPOST:
322 {
323 return IpmiTimerUse::BIOSPOST;
324 }
325 case WatchdogService::TimerUse::OSLoad:
326 {
327 return IpmiTimerUse::OSLoad;
328 }
329
330 case WatchdogService::TimerUse::SMSOS:
331 {
332 return IpmiTimerUse::SMSOS;
333 }
334 case WatchdogService::TimerUse::OEM:
335 {
336 return IpmiTimerUse::OEM;
337 }
338 default:
339 {
340 return IpmiTimerUse::Reserved;
341 }
342 }
343 }
344
345 /**@brief The getWatchdogTimer ipmi command.
346 *
347 * @return Completion code plus timer details.
348 * - timerUse
349 * - timerAction
350 * - pretimeout
351 * - expireFlags
352 * - initialCountdown
353 * - presentCountdown
354 **/
355 ipmi::RspType<uint3_t, // timerUse - timer use
356 uint3_t, // timerUse - reserved
357 bool, // timerUse - timer is started
358 bool, // timerUse - don't log
359
360 uint3_t, // timerAction - timeout action
361 uint1_t, // timerAction - reserved
362 uint3_t, // timerAction - pre-timeout interrupt
363 uint1_t, // timerAction - reserved
364
365 uint8_t, // pretimeout
366 std::bitset<8>, // expireFlags
367 uint16_t, // initial Countdown - Little Endian (deciseconds)
368 uint16_t // present Countdown - Little Endian (deciseconds)
369 >
ipmiGetWatchdogTimer()370 ipmiGetWatchdogTimer()
371 {
372 uint16_t presentCountdown = 0;
373 uint8_t pretimeout = 0;
374
375 try
376 {
377 WatchdogService wd_service;
378 WatchdogService::Properties wd_prop = wd_service.getProperties();
379
380 // Build and return the response
381 // Interval and timeRemaining need converted from milli -> deci seconds
382 uint16_t initialCountdown = htole16(wd_prop.interval / 100);
383
384 if (wd_prop.expiredTimerUse != WatchdogService::TimerUse::Reserved)
385 {
386 timerUseExpirationFlags.set(static_cast<uint8_t>(
387 wdTimerUseToIpmiTimerUse(wd_prop.expiredTimerUse)));
388 }
389
390 if (wd_prop.enabled)
391 {
392 presentCountdown = htole16(wd_prop.timeRemaining / 100);
393 }
394 else
395 {
396 if (wd_prop.expiredTimerUse == WatchdogService::TimerUse::Reserved)
397 {
398 presentCountdown = initialCountdown;
399 }
400 else
401 {
402 presentCountdown = 0;
403 // Automatically clear it whenever a timer expiration occurs.
404 timerNotLogFlags = false;
405 }
406 }
407
408 // TODO: Do something about having pretimeout support
409 pretimeout = 0;
410
411 lastCallSuccessful = true;
412 return ipmi::responseSuccess(
413 types::enum_cast<uint3_t>(
414 wdTimerUseToIpmiTimerUse(wd_prop.timerUse)),
415 0, wd_prop.enabled, timerNotLogFlags,
416 types::enum_cast<uint3_t>(
417 wdActionToIpmiAction(wd_prop.expireAction)),
418 0, timerPreTimeoutInterrupt, 0, pretimeout, timerUseExpirationFlags,
419 initialCountdown, presentCountdown);
420 }
421 catch (const InternalFailure& e)
422 {
423 reportError();
424 return ipmi::responseUnspecifiedError();
425 }
426 catch (const std::exception& e)
427 {
428 lg2::error("wd_get: {ERROR}", "ERROR", e);
429 return ipmi::responseUnspecifiedError();
430 }
431 catch (...)
432 {
433 lg2::error("wd_get: Unknown Error");
434 return ipmi::responseUnspecifiedError();
435 }
436 }
437