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