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