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