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