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 return ipmi::responseUnspecifiedError(); 73 } 74 catch (...) 75 { 76 log<level::ERR>("wd_reset: Unknown Error"); 77 return ipmi::responseUnspecifiedError(); 78 } 79 } 80 81 static constexpr uint8_t wd_dont_stop = 0x1 << 6; 82 static constexpr uint8_t wd_timeout_action_mask = 0x3; 83 84 static constexpr uint8_t wdTimerUseResTimer1 = 0x0; 85 static constexpr uint8_t wdTimerUseResTimer2 = 0x6; 86 static constexpr uint8_t wdTimerUseResTimer3 = 0x7; 87 88 static constexpr uint8_t wdTimeoutActionMax = 3; 89 static constexpr uint8_t wdTimeoutInterruptTimer = 0x04; 90 91 enum class IpmiAction : uint8_t 92 { 93 None = 0x0, 94 HardReset = 0x1, 95 PowerOff = 0x2, 96 PowerCycle = 0x3, 97 }; 98 99 /** @brief Converts an IPMI Watchdog Action to DBUS defined action 100 * @param[in] ipmi_action The IPMI Watchdog Action 101 * @return The Watchdog Action that the ipmi_action maps to 102 */ 103 WatchdogService::Action ipmiActionToWdAction(IpmiAction ipmi_action) 104 { 105 switch (ipmi_action) 106 { 107 case IpmiAction::None: 108 { 109 return WatchdogService::Action::None; 110 } 111 case IpmiAction::HardReset: 112 { 113 return WatchdogService::Action::HardReset; 114 } 115 case IpmiAction::PowerOff: 116 { 117 return WatchdogService::Action::PowerOff; 118 } 119 case IpmiAction::PowerCycle: 120 { 121 return WatchdogService::Action::PowerCycle; 122 } 123 default: 124 { 125 throw std::domain_error("IPMI Action is invalid"); 126 } 127 } 128 } 129 130 enum class IpmiTimerUse : uint8_t 131 { 132 Reserved = 0x0, 133 BIOSFRB2 = 0x1, 134 BIOSPOST = 0x2, 135 OSLoad = 0x3, 136 SMSOS = 0x4, 137 OEM = 0x5, 138 }; 139 140 WatchdogService::TimerUse ipmiTimerUseToWdTimerUse(IpmiTimerUse ipmiTimerUse) 141 { 142 switch (ipmiTimerUse) 143 { 144 case IpmiTimerUse::Reserved: 145 { 146 return WatchdogService::TimerUse::Reserved; 147 } 148 case IpmiTimerUse::BIOSFRB2: 149 { 150 return WatchdogService::TimerUse::BIOSFRB2; 151 } 152 case IpmiTimerUse::BIOSPOST: 153 { 154 return WatchdogService::TimerUse::BIOSPOST; 155 } 156 case IpmiTimerUse::OSLoad: 157 { 158 return WatchdogService::TimerUse::OSLoad; 159 } 160 case IpmiTimerUse::SMSOS: 161 { 162 return WatchdogService::TimerUse::SMSOS; 163 } 164 case IpmiTimerUse::OEM: 165 { 166 return WatchdogService::TimerUse::OEM; 167 } 168 default: 169 { 170 return WatchdogService::TimerUse::Reserved; 171 } 172 } 173 } 174 175 static bool timerNotLogFlags = false; 176 static std::bitset<8> timerUseExpirationFlags = 0; 177 static uint3_t timerPreTimeoutInterrupt = 0; 178 static constexpr uint8_t wdExpirationFlagReservedBit0 = 0x0; 179 static constexpr uint8_t wdExpirationFlagReservedBit6 = 0x6; 180 static constexpr uint8_t wdExpirationFlagReservedBit7 = 0x7; 181 182 /**@brief The Set Watchdog Timer ipmi command. 183 * 184 * @param 185 * - timerUse 186 * - dontStopTimer 187 * - dontLog 188 * - timerAction 189 * - pretimeout 190 * - expireFlags 191 * - initialCountdown 192 * 193 * @return completion code on success. 194 **/ 195 ipmi::RspType<> 196 ipmiSetWatchdogTimer(uint3_t timerUse, uint3_t reserved, bool dontStopTimer, 197 bool dontLog, uint3_t timeoutAction, uint1_t reserved1, 198 uint3_t preTimeoutInterrupt, uint1_t reserved2, 199 uint8_t preTimeoutInterval, 200 std::bitset<8> expFlagValue, uint16_t initialCountdown) 201 { 202 if ((timerUse == wdTimerUseResTimer1) || 203 (timerUse == wdTimerUseResTimer2) || 204 (timerUse == wdTimerUseResTimer3) || 205 (timeoutAction > wdTimeoutActionMax) || 206 (preTimeoutInterrupt == wdTimeoutInterruptTimer) || 207 (reserved | reserved1 | reserved2 | 208 expFlagValue.test(wdExpirationFlagReservedBit0) | 209 expFlagValue.test(wdExpirationFlagReservedBit6) | 210 expFlagValue.test(wdExpirationFlagReservedBit7))) 211 { 212 return ipmi::responseInvalidFieldRequest(); 213 } 214 215 if (preTimeoutInterval > (initialCountdown / 10)) 216 { 217 return ipmi::responseInvalidFieldRequest(); 218 } 219 220 timerNotLogFlags = dontLog; 221 timerPreTimeoutInterrupt = preTimeoutInterrupt; 222 223 try 224 { 225 WatchdogService wd_service; 226 // Stop the timer if the don't stop bit is not set 227 if (!(dontStopTimer)) 228 { 229 wd_service.setEnabled(false); 230 } 231 232 // Set the action based on the request 233 const auto ipmi_action = static_cast<IpmiAction>( 234 static_cast<uint8_t>(timeoutAction) & wd_timeout_action_mask); 235 wd_service.setExpireAction(ipmiActionToWdAction(ipmi_action)); 236 237 const auto ipmiTimerUse = types::enum_cast<IpmiTimerUse>(timerUse); 238 wd_service.setTimerUse(ipmiTimerUseToWdTimerUse(ipmiTimerUse)); 239 240 wd_service.setExpiredTimerUse(WatchdogService::TimerUse::Reserved); 241 242 timerUseExpirationFlags &= ~expFlagValue; 243 244 // Set the new interval and the time remaining deci -> mill seconds 245 const uint64_t interval = initialCountdown * 100; 246 wd_service.setInterval(interval); 247 wd_service.resetTimeRemaining(false); 248 249 // Mark as initialized so that future resets behave correctly 250 wd_service.setInitialized(true); 251 252 lastCallSuccessful = true; 253 return ipmi::responseSuccess(); 254 } 255 catch (const std::domain_error&) 256 { 257 return ipmi::responseInvalidFieldRequest(); 258 } 259 catch (const InternalFailure& e) 260 { 261 reportError(); 262 return ipmi::responseUnspecifiedError(); 263 } 264 catch (const std::exception& e) 265 { 266 const std::string e_str = std::string("wd_set: ") + e.what(); 267 log<level::ERR>(e_str.c_str()); 268 return ipmi::responseUnspecifiedError(); 269 } 270 catch (...) 271 { 272 log<level::ERR>("wd_set: Unknown Error"); 273 return ipmi::responseUnspecifiedError(); 274 } 275 } 276 277 /** @brief Converts a DBUS Watchdog Action to IPMI defined action 278 * @param[in] wd_action The DBUS Watchdog Action 279 * @return The IpmiAction that the wd_action maps to 280 */ 281 IpmiAction wdActionToIpmiAction(WatchdogService::Action wd_action) 282 { 283 switch (wd_action) 284 { 285 case WatchdogService::Action::None: 286 { 287 return IpmiAction::None; 288 } 289 case WatchdogService::Action::HardReset: 290 { 291 return IpmiAction::HardReset; 292 } 293 case WatchdogService::Action::PowerOff: 294 { 295 return IpmiAction::PowerOff; 296 } 297 case WatchdogService::Action::PowerCycle: 298 { 299 return IpmiAction::PowerCycle; 300 } 301 default: 302 { 303 // We have no method via IPMI to signal that the action is unknown 304 // or unmappable in some way. 305 // Just ignore the error and return NONE so the host can reconcile. 306 return IpmiAction::None; 307 } 308 } 309 } 310 311 IpmiTimerUse wdTimerUseToIpmiTimerUse(WatchdogService::TimerUse wdTimerUse) 312 { 313 switch (wdTimerUse) 314 { 315 case WatchdogService::TimerUse::Reserved: 316 { 317 return IpmiTimerUse::Reserved; 318 } 319 case WatchdogService::TimerUse::BIOSFRB2: 320 { 321 return IpmiTimerUse::BIOSFRB2; 322 } 323 case WatchdogService::TimerUse::BIOSPOST: 324 { 325 return IpmiTimerUse::BIOSPOST; 326 } 327 case WatchdogService::TimerUse::OSLoad: 328 { 329 return IpmiTimerUse::OSLoad; 330 } 331 332 case WatchdogService::TimerUse::SMSOS: 333 { 334 return IpmiTimerUse::SMSOS; 335 } 336 case WatchdogService::TimerUse::OEM: 337 { 338 return IpmiTimerUse::OEM; 339 } 340 default: 341 { 342 return IpmiTimerUse::Reserved; 343 } 344 } 345 } 346 347 static constexpr uint8_t wd_running = 0x1 << 6; 348 349 /**@brief The getWatchdogTimer ipmi command. 350 * 351 * @return Completion code plus timer details. 352 * - timerUse 353 * - timerAction 354 * - pretimeout 355 * - expireFlags 356 * - initialCountdown 357 * - presentCountdown 358 **/ 359 ipmi::RspType<uint3_t, // timerUse - timer use 360 uint3_t, // timerUse - reserved 361 bool, // timerUse - timer is started 362 bool, // timerUse - don't log 363 364 uint3_t, // timerAction - timeout action 365 uint1_t, // timerAction - reserved 366 uint3_t, // timerAction - pre-timeout interrupt 367 uint1_t, // timerAction - reserved 368 369 uint8_t, // pretimeout 370 std::bitset<8>, // expireFlags 371 uint16_t, // initial Countdown - Little Endian (deciseconds) 372 uint16_t // present Countdown - Little Endian (deciseconds) 373 > 374 ipmiGetWatchdogTimer() 375 { 376 uint16_t presentCountdown = 0; 377 uint8_t pretimeout = 0; 378 379 try 380 { 381 WatchdogService wd_service; 382 WatchdogService::Properties wd_prop = wd_service.getProperties(); 383 384 // Build and return the response 385 // Interval and timeRemaining need converted from milli -> deci seconds 386 uint16_t initialCountdown = htole16(wd_prop.interval / 100); 387 388 if (wd_prop.expiredTimerUse != WatchdogService::TimerUse::Reserved) 389 { 390 timerUseExpirationFlags.set(static_cast<uint8_t>( 391 wdTimerUseToIpmiTimerUse(wd_prop.expiredTimerUse))); 392 } 393 394 if (wd_prop.enabled) 395 { 396 presentCountdown = htole16(wd_prop.timeRemaining / 100); 397 } 398 else 399 { 400 if (wd_prop.expiredTimerUse == WatchdogService::TimerUse::Reserved) 401 { 402 presentCountdown = initialCountdown; 403 } 404 else 405 { 406 presentCountdown = 0; 407 // Automatically clear it whenever a timer expiration occurs. 408 timerNotLogFlags = false; 409 } 410 } 411 412 // TODO: Do something about having pretimeout support 413 pretimeout = 0; 414 415 lastCallSuccessful = true; 416 return ipmi::responseSuccess( 417 types::enum_cast<uint3_t>( 418 wdTimerUseToIpmiTimerUse(wd_prop.timerUse)), 419 0, wd_prop.enabled, timerNotLogFlags, 420 types::enum_cast<uint3_t>( 421 wdActionToIpmiAction(wd_prop.expireAction)), 422 0, timerPreTimeoutInterrupt, 0, pretimeout, timerUseExpirationFlags, 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 return ipmi::responseUnspecifiedError(); 435 } 436 catch (...) 437 { 438 log<level::ERR>("wd_get: Unknown Error"); 439 return ipmi::responseUnspecifiedError(); 440 } 441 } 442