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 wdTimerUseMask = 0x7;
86 
87 enum class IpmiAction : uint8_t
88 {
89     None = 0x0,
90     HardReset = 0x1,
91     PowerOff = 0x2,
92     PowerCycle = 0x3,
93 };
94 
95 /** @brief Converts an IPMI Watchdog Action to DBUS defined action
96  *  @param[in] ipmi_action The IPMI Watchdog Action
97  *  @return The Watchdog Action that the ipmi_action maps to
98  */
99 WatchdogService::Action ipmiActionToWdAction(IpmiAction ipmi_action)
100 {
101     switch (ipmi_action)
102     {
103         case IpmiAction::None:
104         {
105             return WatchdogService::Action::None;
106         }
107         case IpmiAction::HardReset:
108         {
109             return WatchdogService::Action::HardReset;
110         }
111         case IpmiAction::PowerOff:
112         {
113             return WatchdogService::Action::PowerOff;
114         }
115         case IpmiAction::PowerCycle:
116         {
117             return WatchdogService::Action::PowerCycle;
118         }
119         default:
120         {
121             throw std::domain_error("IPMI Action is invalid");
122         }
123     }
124 }
125 
126 enum class IpmiTimerUse : uint8_t
127 {
128     Reserved = 0x0,
129     BIOSFRB2 = 0x1,
130     BIOSPOST = 0x2,
131     OSLoad = 0x3,
132     SMSOS = 0x4,
133     OEM = 0x5,
134 };
135 
136 WatchdogService::TimerUse ipmiTimerUseToWdTimerUse(IpmiTimerUse ipmiTimerUse)
137 {
138     switch (ipmiTimerUse)
139     {
140         case IpmiTimerUse::Reserved:
141         {
142             return WatchdogService::TimerUse::Reserved;
143         }
144         case IpmiTimerUse::BIOSFRB2:
145         {
146             return WatchdogService::TimerUse::BIOSFRB2;
147         }
148         case IpmiTimerUse::BIOSPOST:
149         {
150             return WatchdogService::TimerUse::BIOSPOST;
151         }
152         case IpmiTimerUse::OSLoad:
153         {
154             return WatchdogService::TimerUse::OSLoad;
155         }
156         case IpmiTimerUse::SMSOS:
157         {
158             return WatchdogService::TimerUse::SMSOS;
159         }
160         case IpmiTimerUse::OEM:
161         {
162             return WatchdogService::TimerUse::OEM;
163         }
164         default:
165         {
166             return WatchdogService::TimerUse::Reserved;
167         }
168     }
169 }
170 
171 struct wd_set_req
172 {
173     uint8_t timer_use;
174     uint8_t timer_action;
175     uint8_t pretimeout; // (seconds)
176     uint8_t expire_flags;
177     uint16_t initial_countdown; // Little Endian (deciseconds)
178 } __attribute__((packed));
179 static_assert(sizeof(wd_set_req) == 6, "wd_set_req has invalid size.");
180 static_assert(sizeof(wd_set_req) <= MAX_IPMI_BUFFER,
181               "wd_get_res can't fit in request buffer.");
182 
183 ipmi_ret_t ipmi_app_watchdog_set(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
184                                  ipmi_request_t request,
185                                  ipmi_response_t response,
186                                  ipmi_data_len_t data_len,
187                                  ipmi_context_t context)
188 {
189     // Extract the request data
190     if (*data_len != sizeof(wd_set_req))
191     {
192         *data_len = 0;
193         return IPMI_CC_REQ_DATA_LEN_INVALID;
194     }
195     wd_set_req req;
196     memcpy(&req, request, sizeof(req));
197     req.initial_countdown = le16toh(req.initial_countdown);
198     *data_len = 0;
199 
200     try
201     {
202         WatchdogService wd_service;
203         // Stop the timer if the don't stop bit is not set
204         if (!(req.timer_use & wd_dont_stop))
205         {
206             wd_service.setEnabled(false);
207         }
208 
209         // Set the action based on the request
210         const auto ipmi_action =
211             static_cast<IpmiAction>(req.timer_action & wd_timeout_action_mask);
212         wd_service.setExpireAction(ipmiActionToWdAction(ipmi_action));
213 
214         const auto ipmiTimerUse =
215             static_cast<IpmiTimerUse>(req.timer_use & wdTimerUseMask);
216         wd_service.setTimerUse(ipmiTimerUseToWdTimerUse(ipmiTimerUse));
217 
218         // Set the new interval and the time remaining deci -> mill seconds
219         const uint64_t interval = req.initial_countdown * 100;
220         wd_service.setInterval(interval);
221         wd_service.setTimeRemaining(interval);
222 
223         // Mark as initialized so that future resets behave correctly
224         wd_service.setInitialized(true);
225 
226         lastCallSuccessful = true;
227         return IPMI_CC_OK;
228     }
229     catch (const std::domain_error&)
230     {
231         return IPMI_CC_INVALID_FIELD_REQUEST;
232     }
233     catch (const InternalFailure& e)
234     {
235         reportError();
236         return IPMI_CC_UNSPECIFIED_ERROR;
237     }
238     catch (const std::exception& e)
239     {
240         const std::string e_str = std::string("wd_set: ") + e.what();
241         log<level::ERR>(e_str.c_str());
242         reportError();
243         return IPMI_CC_UNSPECIFIED_ERROR;
244     }
245     catch (...)
246     {
247         log<level::ERR>("wd_set: Unknown Error");
248         reportError();
249         return IPMI_CC_UNSPECIFIED_ERROR;
250     }
251 }
252 
253 /** @brief Converts a DBUS Watchdog Action to IPMI defined action
254  *  @param[in] wd_action The DBUS Watchdog Action
255  *  @return The IpmiAction that the wd_action maps to
256  */
257 IpmiAction wdActionToIpmiAction(WatchdogService::Action wd_action)
258 {
259     switch (wd_action)
260     {
261         case WatchdogService::Action::None:
262         {
263             return IpmiAction::None;
264         }
265         case WatchdogService::Action::HardReset:
266         {
267             return IpmiAction::HardReset;
268         }
269         case WatchdogService::Action::PowerOff:
270         {
271             return IpmiAction::PowerOff;
272         }
273         case WatchdogService::Action::PowerCycle:
274         {
275             return IpmiAction::PowerCycle;
276         }
277         default:
278         {
279             // We have no method via IPMI to signal that the action is unknown
280             // or unmappable in some way.
281             // Just ignore the error and return NONE so the host can reconcile.
282             return IpmiAction::None;
283         }
284     }
285 }
286 
287 IpmiTimerUse wdTimerUseToIpmiTimerUse(WatchdogService::TimerUse wdTimerUse)
288 {
289     switch (wdTimerUse)
290     {
291         case WatchdogService::TimerUse::Reserved:
292         {
293             return IpmiTimerUse::Reserved;
294         }
295         case WatchdogService::TimerUse::BIOSFRB2:
296         {
297             return IpmiTimerUse::BIOSFRB2;
298         }
299         case WatchdogService::TimerUse::BIOSPOST:
300         {
301             return IpmiTimerUse::BIOSPOST;
302         }
303         case WatchdogService::TimerUse::OSLoad:
304         {
305             return IpmiTimerUse::OSLoad;
306         }
307 
308         case WatchdogService::TimerUse::SMSOS:
309         {
310             return IpmiTimerUse::SMSOS;
311         }
312         case WatchdogService::TimerUse::OEM:
313         {
314             return IpmiTimerUse::OEM;
315         }
316         default:
317         {
318             return IpmiTimerUse::Reserved;
319         }
320     }
321 }
322 
323 struct wd_get_res
324 {
325     uint8_t timer_use;
326     uint8_t timer_action;
327     uint8_t pretimeout;
328     uint8_t expire_flags;
329     uint16_t initial_countdown; // Little Endian (deciseconds)
330     uint16_t present_countdown; // Little Endian (deciseconds)
331 } __attribute__((packed));
332 static_assert(sizeof(wd_get_res) == 8, "wd_get_res has invalid size.");
333 static_assert(sizeof(wd_get_res) <= MAX_IPMI_BUFFER,
334               "wd_get_res can't fit in response buffer.");
335 
336 static constexpr uint8_t wd_dont_log = 0x1 << 7;
337 static constexpr uint8_t wd_running = 0x1 << 6;
338 
339 ipmi_ret_t ipmi_app_watchdog_get(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
340                                  ipmi_request_t request,
341                                  ipmi_response_t response,
342                                  ipmi_data_len_t data_len,
343                                  ipmi_context_t context)
344 {
345     // Assume we will fail and send no data outside the return code
346     *data_len = 0;
347 
348     try
349     {
350         WatchdogService wd_service;
351         WatchdogService::Properties wd_prop = wd_service.getProperties();
352 
353         // Build and return the response
354         wd_get_res res;
355         res.timer_use = wd_dont_log;
356         res.timer_action =
357             static_cast<uint8_t>(wdActionToIpmiAction(wd_prop.expireAction));
358 
359         // Interval and timeRemaining need converted from milli -> deci seconds
360         res.initial_countdown = htole16(wd_prop.interval / 100);
361         if (wd_prop.enabled)
362         {
363             res.timer_use |= wd_running;
364             res.present_countdown = htole16(wd_prop.timeRemaining / 100);
365         }
366         else
367         {
368             res.present_countdown = res.initial_countdown;
369         }
370 
371         res.timer_use |=
372             static_cast<uint8_t>(wdTimerUseToIpmiTimerUse(wd_prop.timerUse));
373 
374         // TODO: Do something about having pretimeout support
375         res.pretimeout = 0;
376         res.expire_flags = 0;
377         memcpy(response, &res, sizeof(res));
378         *data_len = sizeof(res);
379         lastCallSuccessful = true;
380         return IPMI_CC_OK;
381     }
382     catch (const InternalFailure& e)
383     {
384         reportError();
385         return IPMI_CC_UNSPECIFIED_ERROR;
386     }
387     catch (const std::exception& e)
388     {
389         const std::string e_str = std::string("wd_get: ") + e.what();
390         log<level::ERR>(e_str.c_str());
391         reportError();
392         return IPMI_CC_UNSPECIFIED_ERROR;
393     }
394     catch (...)
395     {
396         log<level::ERR>("wd_get: Unknown Error");
397         reportError();
398         return IPMI_CC_UNSPECIFIED_ERROR;
399     }
400 }
401