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