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 wdTimerUseResTimer1 = 0x0; 86 static constexpr uint8_t wdTimerUseResTimer2 = 0x6; 87 static constexpr uint8_t wdTimerUseResTimer3 = 0x7; 88 89 static constexpr uint8_t wdTimeoutActionMax = 3; 90 static constexpr uint8_t wdTimeoutInterruptTimer = 0x04; 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 static bool timerNotLogFlags = false; 177 static uint8_t timerUseExpirationFlags = 0; 178 static uint3_t timerPreTimeoutInterrupt = 0; 179 180 /**@brief The Set Watchdog Timer ipmi command. 181 * 182 * @param 183 * - timerUse 184 * - dontStopTimer 185 * - dontLog 186 * - timerAction 187 * - pretimeout 188 * - expireFlags 189 * - initialCountdown 190 * 191 * @return completion code on success. 192 **/ 193 ipmi::RspType<> ipmiSetWatchdogTimer( 194 uint3_t timerUse, uint3_t reserved, bool dontStopTimer, bool dontLog, 195 uint3_t timeoutAction, uint1_t reserved1, uint3_t preTimeoutInterrupt, 196 uint1_t reserved2, uint8_t preTimeoutInterval, uint1_t reserved3, 197 uint5_t expFlagValue, uint2_t reserved4, uint16_t initialCountdown) 198 { 199 if ((timerUse == wdTimerUseResTimer1) || 200 (timerUse == wdTimerUseResTimer2) || 201 (timerUse == wdTimerUseResTimer3) || 202 (timeoutAction > wdTimeoutActionMax) || 203 (preTimeoutInterrupt == wdTimeoutInterruptTimer) || 204 (reserved | reserved1 | reserved2 | reserved3 | reserved4)) 205 { 206 return ipmi::responseInvalidFieldRequest(); 207 } 208 209 if (preTimeoutInterval > (initialCountdown / 10)) 210 { 211 return ipmi::responseInvalidFieldRequest(); 212 } 213 214 timerNotLogFlags = dontLog; 215 timerPreTimeoutInterrupt = preTimeoutInterrupt; 216 217 try 218 { 219 WatchdogService wd_service; 220 // Stop the timer if the don't stop bit is not set 221 if (!(dontStopTimer)) 222 { 223 wd_service.setEnabled(false); 224 } 225 226 // Set the action based on the request 227 const auto ipmi_action = static_cast<IpmiAction>( 228 static_cast<uint8_t>(timeoutAction) & wd_timeout_action_mask); 229 wd_service.setExpireAction(ipmiActionToWdAction(ipmi_action)); 230 231 const auto ipmiTimerUse = 232 static_cast<IpmiTimerUse>(static_cast<uint8_t>(timerUse)); 233 wd_service.setTimerUse(ipmiTimerUseToWdTimerUse(ipmiTimerUse)); 234 235 wd_service.setExpiredTimerUse(WatchdogService::TimerUse::Reserved); 236 237 timerUseExpirationFlags &= static_cast<uint8_t>(~expFlagValue) << 2; 238 239 // Set the new interval and the time remaining deci -> mill seconds 240 const uint64_t interval = initialCountdown * 100; 241 wd_service.setInterval(interval); 242 wd_service.setTimeRemaining(interval); 243 244 // Mark as initialized so that future resets behave correctly 245 wd_service.setInitialized(true); 246 247 lastCallSuccessful = true; 248 return ipmi::responseSuccess(); 249 } 250 catch (const std::domain_error&) 251 { 252 return ipmi::responseInvalidFieldRequest(); 253 } 254 catch (const InternalFailure& e) 255 { 256 reportError(); 257 return ipmi::responseUnspecifiedError(); 258 } 259 catch (const std::exception& e) 260 { 261 const std::string e_str = std::string("wd_set: ") + e.what(); 262 log<level::ERR>(e_str.c_str()); 263 reportError(); 264 return ipmi::responseUnspecifiedError(); 265 } 266 catch (...) 267 { 268 log<level::ERR>("wd_set: Unknown Error"); 269 reportError(); 270 return ipmi::responseUnspecifiedError(); 271 } 272 } 273 274 /** @brief Converts a DBUS Watchdog Action to IPMI defined action 275 * @param[in] wd_action The DBUS Watchdog Action 276 * @return The IpmiAction that the wd_action maps to 277 */ 278 IpmiAction wdActionToIpmiAction(WatchdogService::Action wd_action) 279 { 280 switch (wd_action) 281 { 282 case WatchdogService::Action::None: 283 { 284 return IpmiAction::None; 285 } 286 case WatchdogService::Action::HardReset: 287 { 288 return IpmiAction::HardReset; 289 } 290 case WatchdogService::Action::PowerOff: 291 { 292 return IpmiAction::PowerOff; 293 } 294 case WatchdogService::Action::PowerCycle: 295 { 296 return IpmiAction::PowerCycle; 297 } 298 default: 299 { 300 // We have no method via IPMI to signal that the action is unknown 301 // or unmappable in some way. 302 // Just ignore the error and return NONE so the host can reconcile. 303 return IpmiAction::None; 304 } 305 } 306 } 307 308 IpmiTimerUse wdTimerUseToIpmiTimerUse(WatchdogService::TimerUse wdTimerUse) 309 { 310 switch (wdTimerUse) 311 { 312 case WatchdogService::TimerUse::Reserved: 313 { 314 return IpmiTimerUse::Reserved; 315 } 316 case WatchdogService::TimerUse::BIOSFRB2: 317 { 318 return IpmiTimerUse::BIOSFRB2; 319 } 320 case WatchdogService::TimerUse::BIOSPOST: 321 { 322 return IpmiTimerUse::BIOSPOST; 323 } 324 case WatchdogService::TimerUse::OSLoad: 325 { 326 return IpmiTimerUse::OSLoad; 327 } 328 329 case WatchdogService::TimerUse::SMSOS: 330 { 331 return IpmiTimerUse::SMSOS; 332 } 333 case WatchdogService::TimerUse::OEM: 334 { 335 return IpmiTimerUse::OEM; 336 } 337 default: 338 { 339 return IpmiTimerUse::Reserved; 340 } 341 } 342 } 343 344 static constexpr uint8_t wd_running = 0x1 << 6; 345 346 /**@brief The getWatchdogTimer ipmi command. 347 * 348 * @return Completion code plus timer details. 349 * - timerUse 350 * - timerAction 351 * - pretimeout 352 * - expireFlags 353 * - initialCountdown 354 * - presentCountdown 355 **/ 356 ipmi::RspType<uint3_t, // timerUse - timer use 357 uint3_t, // timerUse - reserved 358 bool, // timerUse - timer is started 359 bool, // timerUse - don't log 360 361 uint3_t, // timerAction - timeout action 362 uint1_t, // timerAction - reserved 363 uint3_t, // timerAction - pre-timeout interrupt 364 uint1_t, // timerAction - reserved 365 366 uint8_t, // pretimeout 367 uint8_t, // expireFlags 368 uint16_t, // initial Countdown - Little Endian (deciseconds) 369 uint16_t // present Countdown - Little Endian (deciseconds) 370 > 371 ipmiGetWatchdogTimer() 372 { 373 uint8_t expireFlags = 0; 374 uint16_t presentCountdown = 0; 375 uint8_t pretimeout = 0; 376 377 try 378 { 379 WatchdogService wd_service; 380 WatchdogService::Properties wd_prop = wd_service.getProperties(); 381 382 // Build and return the response 383 // Interval and timeRemaining need converted from milli -> deci seconds 384 uint16_t initialCountdown = htole16(wd_prop.interval / 100); 385 386 if (wd_prop.expiredTimerUse != WatchdogService::TimerUse::Reserved) 387 { 388 timerUseExpirationFlags |= 389 1 << static_cast<uint8_t>( 390 wdTimerUseToIpmiTimerUse(wd_prop.expiredTimerUse)); 391 } 392 393 if (wd_prop.enabled) 394 { 395 presentCountdown = htole16(wd_prop.timeRemaining / 100); 396 expireFlags = 0; 397 } 398 else 399 { 400 if (wd_prop.expiredTimerUse == WatchdogService::TimerUse::Reserved) 401 { 402 presentCountdown = initialCountdown; 403 expireFlags = 0; 404 } 405 else 406 { 407 presentCountdown = 0; 408 expireFlags = timerUseExpirationFlags; 409 // Automatically clear it whenever a timer expiration occurs. 410 timerNotLogFlags = false; 411 } 412 } 413 414 // TODO: Do something about having pretimeout support 415 pretimeout = 0; 416 417 lastCallSuccessful = true; 418 return ipmi::responseSuccess( 419 static_cast<uint3_t>(wdTimerUseToIpmiTimerUse(wd_prop.timerUse)), 0, 420 wd_prop.enabled, timerNotLogFlags, 421 static_cast<uint3_t>(wdActionToIpmiAction(wd_prop.expireAction)), 0, 422 timerPreTimeoutInterrupt, 0, pretimeout, expireFlags, 423 initialCountdown, presentCountdown); 424 } 425 catch (const InternalFailure& e) 426 { 427 reportError(); 428 return ipmi::responseUnspecifiedError(); 429 } 430 catch (const std::exception& e) 431 { 432 const std::string e_str = std::string("wd_get: ") + e.what(); 433 log<level::ERR>(e_str.c_str()); 434 reportError(); 435 return ipmi::responseUnspecifiedError(); 436 } 437 catch (...) 438 { 439 log<level::ERR>("wd_get: Unknown Error"); 440 reportError(); 441 return ipmi::responseUnspecifiedError(); 442 } 443 } 444