1 #include "watchdog.hpp" 2 3 #include "watchdog_service.hpp" 4 5 #include <endian.h> 6 #include <ipmid/api.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 using phosphor::logging::commit; 16 using phosphor::logging::level; 17 using phosphor::logging::log; 18 using sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure; 19 20 static bool lastCallSuccessful = false; 21 22 void reportError() 23 { 24 // We don't want to fill the SEL with errors if the daemon dies and doesn't 25 // come back but the watchdog keeps on ticking. Instead, we only report the 26 // error if we haven't reported one since the last successful call 27 if (!lastCallSuccessful) 28 { 29 return; 30 } 31 lastCallSuccessful = false; 32 33 // TODO: This slow down the end of the IPMI transaction waiting 34 // for the commit to finish. commit<>() can take at least 5 seconds 35 // to complete. 5s is very slow for an IPMI command and ends up 36 // congesting the IPMI channel needlessly, especially if the watchdog 37 // is ticking fairly quickly and we have some transient issues. 38 commit<InternalFailure>(); 39 } 40 41 ipmi_ret_t ipmi_app_watchdog_reset(ipmi_netfn_t netfn, ipmi_cmd_t cmd, 42 ipmi_request_t request, 43 ipmi_response_t response, 44 ipmi_data_len_t data_len, 45 ipmi_context_t context) 46 { 47 // We never return data with this command so immediately get rid of it 48 *data_len = 0; 49 50 try 51 { 52 WatchdogService wd_service; 53 54 // Notify the caller if we haven't initialized our timer yet 55 // so it can configure actions and timeouts 56 if (!wd_service.getInitialized()) 57 { 58 lastCallSuccessful = true; 59 return IPMI_WDOG_CC_NOT_INIT; 60 } 61 62 // The ipmi standard dictates we enable the watchdog during reset 63 wd_service.resetTimeRemaining(true); 64 lastCallSuccessful = true; 65 return IPMI_CC_OK; 66 } 67 catch (const InternalFailure& e) 68 { 69 reportError(); 70 return IPMI_CC_UNSPECIFIED_ERROR; 71 } 72 catch (const std::exception& e) 73 { 74 const std::string e_str = std::string("wd_reset: ") + e.what(); 75 log<level::ERR>(e_str.c_str()); 76 reportError(); 77 return IPMI_CC_UNSPECIFIED_ERROR; 78 } 79 catch (...) 80 { 81 log<level::ERR>("wd_reset: Unknown Error"); 82 reportError(); 83 return IPMI_CC_UNSPECIFIED_ERROR; 84 } 85 } 86 87 static constexpr uint8_t wd_dont_stop = 0x1 << 6; 88 static constexpr uint8_t wd_timeout_action_mask = 0x3; 89 90 static constexpr uint8_t wdTimerUseMask = 0x7; 91 92 enum class IpmiAction : uint8_t 93 { 94 None = 0x0, 95 HardReset = 0x1, 96 PowerOff = 0x2, 97 PowerCycle = 0x3, 98 }; 99 100 /** @brief Converts an IPMI Watchdog Action to DBUS defined action 101 * @param[in] ipmi_action The IPMI Watchdog Action 102 * @return The Watchdog Action that the ipmi_action maps to 103 */ 104 WatchdogService::Action ipmiActionToWdAction(IpmiAction ipmi_action) 105 { 106 switch (ipmi_action) 107 { 108 case IpmiAction::None: 109 { 110 return WatchdogService::Action::None; 111 } 112 case IpmiAction::HardReset: 113 { 114 return WatchdogService::Action::HardReset; 115 } 116 case IpmiAction::PowerOff: 117 { 118 return WatchdogService::Action::PowerOff; 119 } 120 case IpmiAction::PowerCycle: 121 { 122 return WatchdogService::Action::PowerCycle; 123 } 124 default: 125 { 126 throw std::domain_error("IPMI Action is invalid"); 127 } 128 } 129 } 130 131 enum class IpmiTimerUse : uint8_t 132 { 133 Reserved = 0x0, 134 BIOSFRB2 = 0x1, 135 BIOSPOST = 0x2, 136 OSLoad = 0x3, 137 SMSOS = 0x4, 138 OEM = 0x5, 139 }; 140 141 WatchdogService::TimerUse ipmiTimerUseToWdTimerUse(IpmiTimerUse ipmiTimerUse) 142 { 143 switch (ipmiTimerUse) 144 { 145 case IpmiTimerUse::Reserved: 146 { 147 return WatchdogService::TimerUse::Reserved; 148 } 149 case IpmiTimerUse::BIOSFRB2: 150 { 151 return WatchdogService::TimerUse::BIOSFRB2; 152 } 153 case IpmiTimerUse::BIOSPOST: 154 { 155 return WatchdogService::TimerUse::BIOSPOST; 156 } 157 case IpmiTimerUse::OSLoad: 158 { 159 return WatchdogService::TimerUse::OSLoad; 160 } 161 case IpmiTimerUse::SMSOS: 162 { 163 return WatchdogService::TimerUse::SMSOS; 164 } 165 case IpmiTimerUse::OEM: 166 { 167 return WatchdogService::TimerUse::OEM; 168 } 169 default: 170 { 171 return WatchdogService::TimerUse::Reserved; 172 } 173 } 174 } 175 176 struct wd_set_req 177 { 178 uint8_t timer_use; 179 uint8_t timer_action; 180 uint8_t pretimeout; // (seconds) 181 uint8_t expire_flags; 182 uint16_t initial_countdown; // Little Endian (deciseconds) 183 } __attribute__((packed)); 184 static_assert(sizeof(wd_set_req) == 6, "wd_set_req has invalid size."); 185 static_assert(sizeof(wd_set_req) <= MAX_IPMI_BUFFER, 186 "wd_get_res can't fit in request buffer."); 187 188 ipmi_ret_t ipmi_app_watchdog_set(ipmi_netfn_t netfn, ipmi_cmd_t cmd, 189 ipmi_request_t request, 190 ipmi_response_t response, 191 ipmi_data_len_t data_len, 192 ipmi_context_t context) 193 { 194 // Extract the request data 195 if (*data_len < sizeof(wd_set_req)) 196 { 197 *data_len = 0; 198 return IPMI_CC_REQ_DATA_LEN_INVALID; 199 } 200 wd_set_req req; 201 memcpy(&req, request, sizeof(req)); 202 req.initial_countdown = le16toh(req.initial_countdown); 203 *data_len = 0; 204 205 try 206 { 207 WatchdogService wd_service; 208 // Stop the timer if the don't stop bit is not set 209 if (!(req.timer_use & wd_dont_stop)) 210 { 211 wd_service.setEnabled(false); 212 } 213 214 // Set the action based on the request 215 const auto ipmi_action = 216 static_cast<IpmiAction>(req.timer_action & wd_timeout_action_mask); 217 wd_service.setExpireAction(ipmiActionToWdAction(ipmi_action)); 218 219 const auto ipmiTimerUse = 220 static_cast<IpmiTimerUse>(req.timer_use & wdTimerUseMask); 221 wd_service.setTimerUse(ipmiTimerUseToWdTimerUse(ipmiTimerUse)); 222 223 // Set the new interval and the time remaining deci -> mill seconds 224 const uint64_t interval = req.initial_countdown * 100; 225 wd_service.setInterval(interval); 226 wd_service.setTimeRemaining(interval); 227 228 // Mark as initialized so that future resets behave correctly 229 wd_service.setInitialized(true); 230 231 lastCallSuccessful = true; 232 return IPMI_CC_OK; 233 } 234 catch (const std::domain_error&) 235 { 236 return IPMI_CC_INVALID_FIELD_REQUEST; 237 } 238 catch (const InternalFailure& e) 239 { 240 reportError(); 241 return IPMI_CC_UNSPECIFIED_ERROR; 242 } 243 catch (const std::exception& e) 244 { 245 const std::string e_str = std::string("wd_set: ") + e.what(); 246 log<level::ERR>(e_str.c_str()); 247 reportError(); 248 return IPMI_CC_UNSPECIFIED_ERROR; 249 } 250 catch (...) 251 { 252 log<level::ERR>("wd_set: Unknown Error"); 253 reportError(); 254 return IPMI_CC_UNSPECIFIED_ERROR; 255 } 256 } 257 258 /** @brief Converts a DBUS Watchdog Action to IPMI defined action 259 * @param[in] wd_action The DBUS Watchdog Action 260 * @return The IpmiAction that the wd_action maps to 261 */ 262 IpmiAction wdActionToIpmiAction(WatchdogService::Action wd_action) 263 { 264 switch (wd_action) 265 { 266 case WatchdogService::Action::None: 267 { 268 return IpmiAction::None; 269 } 270 case WatchdogService::Action::HardReset: 271 { 272 return IpmiAction::HardReset; 273 } 274 case WatchdogService::Action::PowerOff: 275 { 276 return IpmiAction::PowerOff; 277 } 278 case WatchdogService::Action::PowerCycle: 279 { 280 return IpmiAction::PowerCycle; 281 } 282 default: 283 { 284 // We have no method via IPMI to signal that the action is unknown 285 // or unmappable in some way. 286 // Just ignore the error and return NONE so the host can reconcile. 287 return IpmiAction::None; 288 } 289 } 290 } 291 292 IpmiTimerUse wdTimerUseToIpmiTimerUse(WatchdogService::TimerUse wdTimerUse) 293 { 294 switch (wdTimerUse) 295 { 296 case WatchdogService::TimerUse::Reserved: 297 { 298 return IpmiTimerUse::Reserved; 299 } 300 case WatchdogService::TimerUse::BIOSFRB2: 301 { 302 return IpmiTimerUse::BIOSFRB2; 303 } 304 case WatchdogService::TimerUse::BIOSPOST: 305 { 306 return IpmiTimerUse::BIOSPOST; 307 } 308 case WatchdogService::TimerUse::OSLoad: 309 { 310 return IpmiTimerUse::OSLoad; 311 } 312 313 case WatchdogService::TimerUse::SMSOS: 314 { 315 return IpmiTimerUse::SMSOS; 316 } 317 case WatchdogService::TimerUse::OEM: 318 { 319 return IpmiTimerUse::OEM; 320 } 321 default: 322 { 323 return IpmiTimerUse::Reserved; 324 } 325 } 326 } 327 328 struct wd_get_res 329 { 330 uint8_t timer_use; 331 uint8_t timer_action; 332 uint8_t pretimeout; 333 uint8_t expire_flags; 334 uint16_t initial_countdown; // Little Endian (deciseconds) 335 uint16_t present_countdown; // Little Endian (deciseconds) 336 } __attribute__((packed)); 337 static_assert(sizeof(wd_get_res) == 8, "wd_get_res has invalid size."); 338 static_assert(sizeof(wd_get_res) <= MAX_IPMI_BUFFER, 339 "wd_get_res can't fit in response buffer."); 340 341 static constexpr uint8_t wd_dont_log = 0x1 << 7; 342 static constexpr uint8_t wd_running = 0x1 << 6; 343 344 ipmi_ret_t ipmi_app_watchdog_get(ipmi_netfn_t netfn, ipmi_cmd_t cmd, 345 ipmi_request_t request, 346 ipmi_response_t response, 347 ipmi_data_len_t data_len, 348 ipmi_context_t context) 349 { 350 // Assume we will fail and send no data outside the return code 351 *data_len = 0; 352 353 try 354 { 355 WatchdogService wd_service; 356 WatchdogService::Properties wd_prop = wd_service.getProperties(); 357 358 // Build and return the response 359 wd_get_res res; 360 res.timer_use = wd_dont_log; 361 res.timer_action = 362 static_cast<uint8_t>(wdActionToIpmiAction(wd_prop.expireAction)); 363 364 // Interval and timeRemaining need converted from milli -> deci seconds 365 res.initial_countdown = htole16(wd_prop.interval / 100); 366 if (wd_prop.enabled) 367 { 368 res.timer_use |= wd_running; 369 res.present_countdown = htole16(wd_prop.timeRemaining / 100); 370 } 371 else 372 { 373 res.present_countdown = res.initial_countdown; 374 } 375 376 res.timer_use |= 377 static_cast<uint8_t>(wdTimerUseToIpmiTimerUse(wd_prop.timerUse)); 378 379 // TODO: Do something about having pretimeout support 380 res.pretimeout = 0; 381 res.expire_flags = 0; 382 memcpy(response, &res, sizeof(res)); 383 *data_len = sizeof(res); 384 lastCallSuccessful = true; 385 return IPMI_CC_OK; 386 } 387 catch (const InternalFailure& e) 388 { 389 reportError(); 390 return IPMI_CC_UNSPECIFIED_ERROR; 391 } 392 catch (const std::exception& e) 393 { 394 const std::string e_str = std::string("wd_get: ") + e.what(); 395 log<level::ERR>(e_str.c_str()); 396 reportError(); 397 return IPMI_CC_UNSPECIFIED_ERROR; 398 } 399 catch (...) 400 { 401 log<level::ERR>("wd_get: Unknown Error"); 402 reportError(); 403 return IPMI_CC_UNSPECIFIED_ERROR; 404 } 405 } 406