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