1 #include "watchdog.hpp"
2 
3 #include "ipmid.hpp"
4 #include "watchdog_service.hpp"
5 
6 #include <endian.h>
7 #include <host-ipmid/ipmid-api.h>
8 
9 #include <cstdint>
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_ret_t ipmi_app_watchdog_reset(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
43                                    ipmi_request_t request,
44                                    ipmi_response_t response,
45                                    ipmi_data_len_t data_len,
46                                    ipmi_context_t context)
47 {
48     // We never return data with this command so immediately get rid of it
49     *data_len = 0;
50 
51     try
52     {
53         WatchdogService wd_service;
54 
55         // Notify the caller if we haven't initialized our timer yet
56         // so it can configure actions and timeouts
57         if (!wd_service.getInitialized())
58         {
59             lastCallSuccessful = true;
60             return IPMI_WDOG_CC_NOT_INIT;
61         }
62 
63         // The ipmi standard dictates we enable the watchdog during reset
64         wd_service.resetTimeRemaining(true);
65         lastCallSuccessful = true;
66         return IPMI_CC_OK;
67     }
68     catch (const InternalFailure& e)
69     {
70         reportError();
71         return IPMI_CC_UNSPECIFIED_ERROR;
72     }
73     catch (const std::exception& e)
74     {
75         const std::string e_str = std::string("wd_reset: ") + e.what();
76         log<level::ERR>(e_str.c_str());
77         reportError();
78         return IPMI_CC_UNSPECIFIED_ERROR;
79     }
80     catch (...)
81     {
82         log<level::ERR>("wd_reset: Unknown Error");
83         reportError();
84         return IPMI_CC_UNSPECIFIED_ERROR;
85     }
86 }
87 
88 static constexpr uint8_t wd_dont_stop = 0x1 << 6;
89 static constexpr uint8_t wd_timeout_action_mask = 0x3;
90 
91 enum class IpmiAction : uint8_t
92 {
93     None = 0x0,
94     HardReset = 0x1,
95     PowerOff = 0x2,
96     PowerCycle = 0x3,
97 };
98 
99 /** @brief Converts an IPMI Watchdog Action to DBUS defined action
100  *  @param[in] ipmi_action The IPMI Watchdog Action
101  *  @return The Watchdog Action that the ipmi_action maps to
102  */
103 WatchdogService::Action ipmiActionToWdAction(IpmiAction ipmi_action)
104 {
105     switch (ipmi_action)
106     {
107         case IpmiAction::None:
108         {
109             return WatchdogService::Action::None;
110         }
111         case IpmiAction::HardReset:
112         {
113             return WatchdogService::Action::HardReset;
114         }
115         case IpmiAction::PowerOff:
116         {
117             return WatchdogService::Action::PowerOff;
118         }
119         case IpmiAction::PowerCycle:
120         {
121             return WatchdogService::Action::PowerCycle;
122         }
123         default:
124         {
125             throw std::domain_error("IPMI Action is invalid");
126         }
127     }
128 }
129 
130 struct wd_set_req
131 {
132     uint8_t timer_use;
133     uint8_t timer_action;
134     uint8_t pretimeout; // (seconds)
135     uint8_t expire_flags;
136     uint16_t initial_countdown; // Little Endian (deciseconds)
137 } __attribute__((packed));
138 static_assert(sizeof(wd_set_req) == 6, "wd_set_req has invalid size.");
139 static_assert(sizeof(wd_set_req) <= MAX_IPMI_BUFFER,
140               "wd_get_res can't fit in request buffer.");
141 
142 ipmi_ret_t ipmi_app_watchdog_set(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
143                                  ipmi_request_t request,
144                                  ipmi_response_t response,
145                                  ipmi_data_len_t data_len,
146                                  ipmi_context_t context)
147 {
148     // Extract the request data
149     if (*data_len < sizeof(wd_set_req))
150     {
151         *data_len = 0;
152         return IPMI_CC_REQ_DATA_LEN_INVALID;
153     }
154     wd_set_req req;
155     memcpy(&req, request, sizeof(req));
156     req.initial_countdown = le16toh(req.initial_countdown);
157     *data_len = 0;
158 
159     try
160     {
161         WatchdogService wd_service;
162         // Stop the timer if the don't stop bit is not set
163         if (!(req.timer_use & wd_dont_stop))
164         {
165             wd_service.setEnabled(false);
166         }
167 
168         // Set the action based on the request
169         const auto ipmi_action =
170             static_cast<IpmiAction>(req.timer_action & wd_timeout_action_mask);
171         wd_service.setExpireAction(ipmiActionToWdAction(ipmi_action));
172 
173         // Set the new interval and the time remaining deci -> mill seconds
174         const uint64_t interval = req.initial_countdown * 100;
175         wd_service.setInterval(interval);
176         wd_service.setTimeRemaining(interval);
177 
178         // Mark as initialized so that future resets behave correctly
179         wd_service.setInitialized(true);
180 
181         lastCallSuccessful = true;
182         return IPMI_CC_OK;
183     }
184     catch (const std::domain_error&)
185     {
186         return IPMI_CC_INVALID_FIELD_REQUEST;
187     }
188     catch (const InternalFailure& e)
189     {
190         reportError();
191         return IPMI_CC_UNSPECIFIED_ERROR;
192     }
193     catch (const std::exception& e)
194     {
195         const std::string e_str = std::string("wd_set: ") + e.what();
196         log<level::ERR>(e_str.c_str());
197         reportError();
198         return IPMI_CC_UNSPECIFIED_ERROR;
199     }
200     catch (...)
201     {
202         log<level::ERR>("wd_set: Unknown Error");
203         reportError();
204         return IPMI_CC_UNSPECIFIED_ERROR;
205     }
206 }
207 
208 /** @brief Converts a DBUS Watchdog Action to IPMI defined action
209  *  @param[in] wd_action The DBUS Watchdog Action
210  *  @return The IpmiAction that the wd_action maps to
211  */
212 IpmiAction wdActionToIpmiAction(WatchdogService::Action wd_action)
213 {
214     switch (wd_action)
215     {
216         case WatchdogService::Action::None:
217         {
218             return IpmiAction::None;
219         }
220         case WatchdogService::Action::HardReset:
221         {
222             return IpmiAction::HardReset;
223         }
224         case WatchdogService::Action::PowerOff:
225         {
226             return IpmiAction::PowerOff;
227         }
228         case WatchdogService::Action::PowerCycle:
229         {
230             return IpmiAction::PowerCycle;
231         }
232         default:
233         {
234             // We have no method via IPMI to signal that the action is unknown
235             // or unmappable in some way.
236             // Just ignore the error and return NONE so the host can reconcile.
237             return IpmiAction::None;
238         }
239     }
240 }
241 
242 struct wd_get_res
243 {
244     uint8_t timer_use;
245     uint8_t timer_action;
246     uint8_t pretimeout;
247     uint8_t expire_flags;
248     uint16_t initial_countdown; // Little Endian (deciseconds)
249     uint16_t present_countdown; // Little Endian (deciseconds)
250 } __attribute__((packed));
251 static_assert(sizeof(wd_get_res) == 8, "wd_get_res has invalid size.");
252 static_assert(sizeof(wd_get_res) <= MAX_IPMI_BUFFER,
253               "wd_get_res can't fit in response buffer.");
254 
255 static constexpr uint8_t wd_dont_log = 0x1 << 7;
256 static constexpr uint8_t wd_running = 0x1 << 6;
257 
258 ipmi_ret_t ipmi_app_watchdog_get(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
259                                  ipmi_request_t request,
260                                  ipmi_response_t response,
261                                  ipmi_data_len_t data_len,
262                                  ipmi_context_t context)
263 {
264     // Assume we will fail and send no data outside the return code
265     *data_len = 0;
266 
267     try
268     {
269         WatchdogService wd_service;
270         WatchdogService::Properties wd_prop = wd_service.getProperties();
271 
272         // Build and return the response
273         wd_get_res res;
274         res.timer_use = wd_dont_log;
275         res.timer_action =
276             static_cast<uint8_t>(wdActionToIpmiAction(wd_prop.expireAction));
277         if (wd_prop.enabled)
278         {
279             res.timer_use |= wd_running;
280         }
281         // TODO: Do something about having pretimeout support
282         res.pretimeout = 0;
283         res.expire_flags = 0;
284         // Interval and timeRemaining need converted from milli -> deci seconds
285         res.initial_countdown = htole16(wd_prop.interval / 100);
286         res.present_countdown = htole16(wd_prop.timeRemaining / 100);
287 
288         memcpy(response, &res, sizeof(res));
289         *data_len = sizeof(res);
290         lastCallSuccessful = true;
291         return IPMI_CC_OK;
292     }
293     catch (const InternalFailure& e)
294     {
295         reportError();
296         return IPMI_CC_UNSPECIFIED_ERROR;
297     }
298     catch (const std::exception& e)
299     {
300         const std::string e_str = std::string("wd_get: ") + e.what();
301         log<level::ERR>(e_str.c_str());
302         reportError();
303         return IPMI_CC_UNSPECIFIED_ERROR;
304     }
305     catch (...)
306     {
307         log<level::ERR>("wd_get: Unknown Error");
308         reportError();
309         return IPMI_CC_UNSPECIFIED_ERROR;
310     }
311 }
312