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