1 #include "watchdog.hpp"
2 
3 #include "watchdog_service.hpp"
4 
5 #include <endian.h>
6 #include <ipmid/api.h>
7 
8 #include <cstdint>
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_ret_t ipmi_app_watchdog_reset(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
42                                    ipmi_request_t request,
43                                    ipmi_response_t response,
44                                    ipmi_data_len_t data_len,
45                                    ipmi_context_t context)
46 {
47     // We never return data with this command so immediately get rid of it
48     *data_len = 0;
49 
50     try
51     {
52         WatchdogService wd_service;
53 
54         // Notify the caller if we haven't initialized our timer yet
55         // so it can configure actions and timeouts
56         if (!wd_service.getInitialized())
57         {
58             lastCallSuccessful = true;
59             return IPMI_WDOG_CC_NOT_INIT;
60         }
61 
62         // The ipmi standard dictates we enable the watchdog during reset
63         wd_service.resetTimeRemaining(true);
64         lastCallSuccessful = true;
65         return IPMI_CC_OK;
66     }
67     catch (const InternalFailure& e)
68     {
69         reportError();
70         return IPMI_CC_UNSPECIFIED_ERROR;
71     }
72     catch (const std::exception& e)
73     {
74         const std::string e_str = std::string("wd_reset: ") + e.what();
75         log<level::ERR>(e_str.c_str());
76         reportError();
77         return IPMI_CC_UNSPECIFIED_ERROR;
78     }
79     catch (...)
80     {
81         log<level::ERR>("wd_reset: Unknown Error");
82         reportError();
83         return IPMI_CC_UNSPECIFIED_ERROR;
84     }
85 }
86 
87 static constexpr uint8_t wd_dont_stop = 0x1 << 6;
88 static constexpr uint8_t wd_timeout_action_mask = 0x3;
89 
90 static constexpr uint8_t wdTimerUseMask = 0x7;
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 struct wd_set_req
177 {
178     uint8_t timer_use;
179     uint8_t timer_action;
180     uint8_t pretimeout; // (seconds)
181     uint8_t expire_flags;
182     uint16_t initial_countdown; // Little Endian (deciseconds)
183 } __attribute__((packed));
184 static_assert(sizeof(wd_set_req) == 6, "wd_set_req has invalid size.");
185 static_assert(sizeof(wd_set_req) <= MAX_IPMI_BUFFER,
186               "wd_get_res can't fit in request buffer.");
187 
188 ipmi_ret_t ipmi_app_watchdog_set(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
189                                  ipmi_request_t request,
190                                  ipmi_response_t response,
191                                  ipmi_data_len_t data_len,
192                                  ipmi_context_t context)
193 {
194     // Extract the request data
195     if (*data_len < sizeof(wd_set_req))
196     {
197         *data_len = 0;
198         return IPMI_CC_REQ_DATA_LEN_INVALID;
199     }
200     wd_set_req req;
201     memcpy(&req, request, sizeof(req));
202     req.initial_countdown = le16toh(req.initial_countdown);
203     *data_len = 0;
204 
205     try
206     {
207         WatchdogService wd_service;
208         // Stop the timer if the don't stop bit is not set
209         if (!(req.timer_use & wd_dont_stop))
210         {
211             wd_service.setEnabled(false);
212         }
213 
214         // Set the action based on the request
215         const auto ipmi_action =
216             static_cast<IpmiAction>(req.timer_action & wd_timeout_action_mask);
217         wd_service.setExpireAction(ipmiActionToWdAction(ipmi_action));
218 
219         const auto ipmiTimerUse =
220             static_cast<IpmiTimerUse>(req.timer_use & wdTimerUseMask);
221         wd_service.setTimerUse(ipmiTimerUseToWdTimerUse(ipmiTimerUse));
222 
223         // Set the new interval and the time remaining deci -> mill seconds
224         const uint64_t interval = req.initial_countdown * 100;
225         wd_service.setInterval(interval);
226         wd_service.setTimeRemaining(interval);
227 
228         // Mark as initialized so that future resets behave correctly
229         wd_service.setInitialized(true);
230 
231         lastCallSuccessful = true;
232         return IPMI_CC_OK;
233     }
234     catch (const std::domain_error&)
235     {
236         return IPMI_CC_INVALID_FIELD_REQUEST;
237     }
238     catch (const InternalFailure& e)
239     {
240         reportError();
241         return IPMI_CC_UNSPECIFIED_ERROR;
242     }
243     catch (const std::exception& e)
244     {
245         const std::string e_str = std::string("wd_set: ") + e.what();
246         log<level::ERR>(e_str.c_str());
247         reportError();
248         return IPMI_CC_UNSPECIFIED_ERROR;
249     }
250     catch (...)
251     {
252         log<level::ERR>("wd_set: Unknown Error");
253         reportError();
254         return IPMI_CC_UNSPECIFIED_ERROR;
255     }
256 }
257 
258 /** @brief Converts a DBUS Watchdog Action to IPMI defined action
259  *  @param[in] wd_action The DBUS Watchdog Action
260  *  @return The IpmiAction that the wd_action maps to
261  */
262 IpmiAction wdActionToIpmiAction(WatchdogService::Action wd_action)
263 {
264     switch (wd_action)
265     {
266         case WatchdogService::Action::None:
267         {
268             return IpmiAction::None;
269         }
270         case WatchdogService::Action::HardReset:
271         {
272             return IpmiAction::HardReset;
273         }
274         case WatchdogService::Action::PowerOff:
275         {
276             return IpmiAction::PowerOff;
277         }
278         case WatchdogService::Action::PowerCycle:
279         {
280             return IpmiAction::PowerCycle;
281         }
282         default:
283         {
284             // We have no method via IPMI to signal that the action is unknown
285             // or unmappable in some way.
286             // Just ignore the error and return NONE so the host can reconcile.
287             return IpmiAction::None;
288         }
289     }
290 }
291 
292 IpmiTimerUse wdTimerUseToIpmiTimerUse(WatchdogService::TimerUse wdTimerUse)
293 {
294     switch (wdTimerUse)
295     {
296         case WatchdogService::TimerUse::Reserved:
297         {
298             return IpmiTimerUse::Reserved;
299         }
300         case WatchdogService::TimerUse::BIOSFRB2:
301         {
302             return IpmiTimerUse::BIOSFRB2;
303         }
304         case WatchdogService::TimerUse::BIOSPOST:
305         {
306             return IpmiTimerUse::BIOSPOST;
307         }
308         case WatchdogService::TimerUse::OSLoad:
309         {
310             return IpmiTimerUse::OSLoad;
311         }
312 
313         case WatchdogService::TimerUse::SMSOS:
314         {
315             return IpmiTimerUse::SMSOS;
316         }
317         case WatchdogService::TimerUse::OEM:
318         {
319             return IpmiTimerUse::OEM;
320         }
321         default:
322         {
323             return IpmiTimerUse::Reserved;
324         }
325     }
326 }
327 
328 struct wd_get_res
329 {
330     uint8_t timer_use;
331     uint8_t timer_action;
332     uint8_t pretimeout;
333     uint8_t expire_flags;
334     uint16_t initial_countdown; // Little Endian (deciseconds)
335     uint16_t present_countdown; // Little Endian (deciseconds)
336 } __attribute__((packed));
337 static_assert(sizeof(wd_get_res) == 8, "wd_get_res has invalid size.");
338 static_assert(sizeof(wd_get_res) <= MAX_IPMI_BUFFER,
339               "wd_get_res can't fit in response buffer.");
340 
341 static constexpr uint8_t wd_dont_log = 0x1 << 7;
342 static constexpr uint8_t wd_running = 0x1 << 6;
343 
344 ipmi_ret_t ipmi_app_watchdog_get(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
345                                  ipmi_request_t request,
346                                  ipmi_response_t response,
347                                  ipmi_data_len_t data_len,
348                                  ipmi_context_t context)
349 {
350     // Assume we will fail and send no data outside the return code
351     *data_len = 0;
352 
353     try
354     {
355         WatchdogService wd_service;
356         WatchdogService::Properties wd_prop = wd_service.getProperties();
357 
358         // Build and return the response
359         wd_get_res res;
360         res.timer_use = wd_dont_log;
361         res.timer_action =
362             static_cast<uint8_t>(wdActionToIpmiAction(wd_prop.expireAction));
363 
364         // Interval and timeRemaining need converted from milli -> deci seconds
365         res.initial_countdown = htole16(wd_prop.interval / 100);
366         if (wd_prop.enabled)
367         {
368             res.timer_use |= wd_running;
369             res.present_countdown = htole16(wd_prop.timeRemaining / 100);
370         }
371         else
372         {
373             res.present_countdown = res.initial_countdown;
374         }
375 
376         res.timer_use |=
377             static_cast<uint8_t>(wdTimerUseToIpmiTimerUse(wd_prop.timerUse));
378 
379         // TODO: Do something about having pretimeout support
380         res.pretimeout = 0;
381         res.expire_flags = 0;
382         memcpy(response, &res, sizeof(res));
383         *data_len = sizeof(res);
384         lastCallSuccessful = true;
385         return IPMI_CC_OK;
386     }
387     catch (const InternalFailure& e)
388     {
389         reportError();
390         return IPMI_CC_UNSPECIFIED_ERROR;
391     }
392     catch (const std::exception& e)
393     {
394         const std::string e_str = std::string("wd_get: ") + e.what();
395         log<level::ERR>(e_str.c_str());
396         reportError();
397         return IPMI_CC_UNSPECIFIED_ERROR;
398     }
399     catch (...)
400     {
401         log<level::ERR>("wd_get: Unknown Error");
402         reportError();
403         return IPMI_CC_UNSPECIFIED_ERROR;
404     }
405 }
406