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