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