1 #include "watchdog.hpp"
2 
3 #include "ipmid.hpp"
4 #include "watchdog_service.hpp"
5 
6 #include <endian.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 #include "host-ipmid/ipmid-api.h"
16 
17 using phosphor::logging::level;
18 using phosphor::logging::log;
19 using phosphor::logging::report;
20 using sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
21 
22 ipmi_ret_t ipmi_app_watchdog_reset(ipmi_netfn_t netfn, 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 {
71     None = 0x0,
72     HardReset = 0x1,
73     PowerOff = 0x2,
74     PowerCycle = 0x3,
75 };
76 
77 /** @brief Converts an IPMI Watchdog Action to DBUS defined action
78  *  @param[in] ipmi_action The IPMI Watchdog Action
79  *  @return The Watchdog Action that the ipmi_action maps to
80  */
81 WatchdogService::Action ipmiActionToWdAction(IpmiAction ipmi_action)
82 {
83     switch (ipmi_action)
84     {
85         case IpmiAction::None:
86         {
87             return WatchdogService::Action::None;
88         }
89         case IpmiAction::HardReset:
90         {
91             return WatchdogService::Action::HardReset;
92         }
93         case IpmiAction::PowerOff:
94         {
95             return WatchdogService::Action::PowerOff;
96         }
97         case IpmiAction::PowerCycle:
98         {
99             return WatchdogService::Action::PowerCycle;
100         }
101         default:
102         {
103             throw std::domain_error("IPMI Action is invalid");
104         }
105     }
106 }
107 
108 struct wd_set_req
109 {
110     uint8_t timer_use;
111     uint8_t timer_action;
112     uint8_t pretimeout; // (seconds)
113     uint8_t expire_flags;
114     uint16_t initial_countdown; // Little Endian (deciseconds)
115 } __attribute__((packed));
116 static_assert(sizeof(wd_set_req) == 6, "wd_set_req has invalid size.");
117 static_assert(sizeof(wd_set_req) <= MAX_IPMI_BUFFER,
118               "wd_get_res can't fit in request buffer.");
119 
120 ipmi_ret_t ipmi_app_watchdog_set(ipmi_netfn_t netfn, 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 =
148             static_cast<IpmiAction>(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 {
221     uint8_t timer_use;
222     uint8_t timer_action;
223     uint8_t pretimeout;
224     uint8_t expire_flags;
225     uint16_t initial_countdown; // Little Endian (deciseconds)
226     uint16_t present_countdown; // Little Endian (deciseconds)
227 } __attribute__((packed));
228 static_assert(sizeof(wd_get_res) == 8, "wd_get_res has invalid size.");
229 static_assert(sizeof(wd_get_res) <= MAX_IPMI_BUFFER,
230               "wd_get_res can't fit in response buffer.");
231 
232 static constexpr uint8_t wd_dont_log = 0x1 << 7;
233 static constexpr uint8_t wd_running = 0x1 << 6;
234 
235 ipmi_ret_t ipmi_app_watchdog_get(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
236                                  ipmi_request_t request,
237                                  ipmi_response_t response,
238                                  ipmi_data_len_t data_len,
239                                  ipmi_context_t context)
240 {
241     // Assume we will fail and send no data outside the return code
242     *data_len = 0;
243 
244     try
245     {
246         WatchdogService wd_service;
247         WatchdogService::Properties wd_prop = wd_service.getProperties();
248 
249         // Build and return the response
250         wd_get_res res;
251         res.timer_use = wd_dont_log;
252         res.timer_action =
253             static_cast<uint8_t>(wdActionToIpmiAction(wd_prop.expireAction));
254         if (wd_prop.enabled)
255         {
256             res.timer_use |= wd_running;
257         }
258         // TODO: Do something about having pretimeout support
259         res.pretimeout = 0;
260         res.expire_flags = 0;
261         // Interval and timeRemaining need converted from milli -> deci seconds
262         res.initial_countdown = htole16(wd_prop.interval / 100);
263         res.present_countdown = htole16(wd_prop.timeRemaining / 100);
264 
265         memcpy(response, &res, sizeof(res));
266         *data_len = sizeof(res);
267         return IPMI_CC_OK;
268     }
269     catch (const InternalFailure& e)
270     {
271         report<InternalFailure>();
272         return IPMI_CC_UNSPECIFIED_ERROR;
273     }
274     catch (const std::exception& e)
275     {
276         const std::string e_str = std::string("wd_get: ") + e.what();
277         log<level::ERR>(e_str.c_str());
278         report<InternalFailure>();
279         return IPMI_CC_UNSPECIFIED_ERROR;
280     }
281     catch (...)
282     {
283         log<level::ERR>("wd_get: Unknown Error");
284         report<InternalFailure>();
285         return IPMI_CC_UNSPECIFIED_ERROR;
286     }
287 }
288