xref: /openbmc/phosphor-host-ipmid/app/watchdog.cpp (revision cc96b4250de30584897c9fcdebdcee3a2357a8e3)
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