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