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