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