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