1 /* 2 // Copyright (c) 2019 Intel Corporation 3 // 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at 7 // 8 // http://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 */ 16 #include "xyz/openbmc_project/Common/error.hpp" 17 18 #include <ipmid/api.hpp> 19 #include <ipmid/utils.hpp> 20 #include <nlohmann/json.hpp> 21 #include <phosphor-logging/elog-errors.hpp> 22 #include <phosphor-logging/log.hpp> 23 #include <sdbusplus/timer.hpp> 24 #include <xyz/openbmc_project/Control/Power/RestorePolicy/server.hpp> 25 26 #include <fstream> 27 #include <iostream> 28 #include <regex> 29 #include <stdexcept> 30 #include <string_view> 31 32 using namespace phosphor::logging; 33 34 namespace ipmi::chassis 35 { 36 static constexpr const char* buttonIntf = "xyz.openbmc_project.Chassis.Buttons"; 37 38 const static constexpr char* idButtonPath = 39 "/xyz/openbmc_project/chassis/buttons/id"; 40 static constexpr const char* powerButtonPath = 41 "/xyz/openbmc_project/chassis/buttons/power"; 42 static constexpr const char* resetButtonPath = 43 "/xyz/openbmc_project/chassis/buttons/reset"; 44 static constexpr const char* interruptButtonPath = 45 "/xyz/openbmc_project/chassis/buttons/nmi"; 46 47 const static constexpr char* idButtonProp = "ButtonPressed"; 48 49 const static constexpr char* ledService = 50 "xyz.openbmc_project.LED.GroupManager"; 51 const static constexpr char* ledIDOnObj = 52 "/xyz/openbmc_project/led/groups/enclosure_identify"; 53 const static constexpr char* ledIDBlinkObj = 54 "/xyz/openbmc_project/led/groups/enclosure_identify_blink"; 55 const static constexpr char* ledInterface = "xyz.openbmc_project.Led.Group"; 56 const static constexpr char* ledProp = "Asserted"; 57 enum class ChassisIDState 58 { 59 off = 0, 60 temporary = 1, 61 indefinite = 2, 62 }; 63 static ChassisIDState chassisIDState = ChassisIDState::off; 64 65 constexpr size_t defaultIdentifyTimeOut = 15; 66 67 std::unique_ptr<phosphor::Timer> identifyTimer 68 __attribute__((init_priority(101))); 69 std::unique_ptr<sdbusplus::bus::match_t> matchPtr 70 __attribute__((init_priority(101))); 71 72 static void registerChassisFunctions() __attribute__((constructor)); 73 74 static ipmi::ServiceCache LEDService(ledInterface, ledIDBlinkObj); 75 76 void enclosureIdentifyLed(const char* objName, bool isIdLedOn) 77 { 78 auto bus = getSdBus(); 79 80 try 81 { 82 std::string service = LEDService.getService(*bus); 83 setDbusProperty(*bus, service, objName, ledInterface, ledProp, 84 isIdLedOn); 85 } 86 catch (const std::exception& e) 87 { 88 log<level::ERR>("enclosureIdentifyLed: can't set property", 89 entry("ERR=%s", e.what())); 90 } 91 } 92 93 bool getIDState(const char* objName, bool& state) 94 { 95 auto bus = getSdBus(); 96 97 try 98 { 99 std::string service = LEDService.getService(*bus); 100 ipmi::Value enabled = 101 getDbusProperty(*bus, service, objName, ledInterface, ledProp); 102 state = std::get<bool>(enabled); 103 } 104 catch (sdbusplus::exception::SdBusError& e) 105 { 106 log<level::ERR>("Fail to get property", entry("PATH=%s", objName), 107 entry("ERROR=%s", e.what())); 108 return false; 109 } 110 return true; 111 } 112 113 void enclosureIdentifyLedBlinkOff() 114 { 115 chassisIDState = ChassisIDState::off; 116 enclosureIdentifyLed(ledIDBlinkObj, false); 117 } 118 119 void idButtonPropChanged(sdbusplus::message::message& msg) 120 { 121 bool asserted = false; 122 bool buttonPressed = false; 123 124 std::map<std::string, ipmi::Value> props; 125 std::vector<std::string> inval; 126 std::string iface; 127 msg.read(iface, props, inval); 128 129 for (const auto& t : props) 130 { 131 auto key = t.first; 132 auto value = t.second; 133 134 if (key == idButtonProp) 135 { 136 buttonPressed = std::get<bool>(value); 137 } 138 break; 139 } 140 141 if (buttonPressed) 142 { 143 if (identifyTimer->isRunning()) 144 { 145 log<level::INFO>("ID timer is running"); 146 } 147 148 // make sure timer is stopped 149 identifyTimer->stop(); 150 151 if (!getIDState(ledIDBlinkObj, asserted)) 152 { 153 return; 154 } 155 156 if (asserted) 157 { 158 // LED is blinking, turn off the LED 159 chassisIDState = ChassisIDState::off; 160 enclosureIdentifyLed(ledIDBlinkObj, false); 161 enclosureIdentifyLed(ledIDOnObj, false); 162 } 163 else 164 { 165 // toggle the IED on/off 166 if (!getIDState(ledIDOnObj, asserted)) 167 { 168 return; 169 } 170 enclosureIdentifyLed(ledIDOnObj, !asserted); 171 } 172 } 173 } 174 175 void createIdentifyTimer() 176 { 177 if (!identifyTimer) 178 { 179 identifyTimer = 180 std::make_unique<phosphor::Timer>(enclosureIdentifyLedBlinkOff); 181 } 182 } 183 184 ipmi::RspType<> ipmiChassisIdentify(std::optional<uint8_t> interval, 185 std::optional<uint8_t> force) 186 { 187 uint8_t identifyInterval = interval.value_or(defaultIdentifyTimeOut); 188 bool forceIdentify = force.value_or(0) & 0x01; 189 190 enclosureIdentifyLed(ledIDOnObj, false); 191 identifyTimer->stop(); 192 193 if (identifyInterval || forceIdentify) 194 { 195 enclosureIdentifyLed(ledIDBlinkObj, true); 196 if (forceIdentify) 197 { 198 chassisIDState = ChassisIDState::indefinite; 199 return ipmi::responseSuccess(); 200 } 201 chassisIDState = ChassisIDState::temporary; 202 // start the timer 203 auto time = std::chrono::duration_cast<std::chrono::microseconds>( 204 std::chrono::seconds(identifyInterval)); 205 identifyTimer->start(time); 206 } 207 else 208 { 209 chassisIDState = ChassisIDState::off; 210 enclosureIdentifyLed(ledIDBlinkObj, false); 211 } 212 return ipmi::responseSuccess(); 213 } 214 215 namespace power_policy 216 { 217 /* helper function for Get Chassis Status Command 218 */ 219 std::optional<uint2_t> getPowerRestorePolicy() 220 { 221 constexpr const char* powerRestorePath = 222 "/xyz/openbmc_project/control/host0/power_restore_policy"; 223 constexpr const char* powerRestoreIntf = 224 "xyz.openbmc_project.Control.Power.RestorePolicy"; 225 uint2_t restorePolicy = 0; 226 std::shared_ptr<sdbusplus::asio::connection> busp = getSdBus(); 227 228 try 229 { 230 auto service = 231 ipmi::getService(*busp, powerRestoreIntf, powerRestorePath); 232 233 ipmi::Value result = 234 ipmi::getDbusProperty(*busp, service, powerRestorePath, 235 powerRestoreIntf, "PowerRestorePolicy"); 236 auto powerRestore = sdbusplus::xyz::openbmc_project::Control::Power:: 237 server::RestorePolicy::convertPolicyFromString( 238 std::get<std::string>(result)); 239 240 using namespace sdbusplus::xyz::openbmc_project::Control::Power::server; 241 switch (powerRestore) 242 { 243 case RestorePolicy::Policy::AlwaysOff: 244 restorePolicy = 0x00; 245 break; 246 case RestorePolicy::Policy::Restore: 247 restorePolicy = 0x01; 248 break; 249 case RestorePolicy::Policy::AlwaysOn: 250 restorePolicy = 0x02; 251 break; 252 } 253 } 254 catch (const std::exception& e) 255 { 256 log<level::ERR>("Failed to fetch PowerRestorePolicy property", 257 entry("ERROR=%s", e.what()), 258 entry("PATH=%s", powerRestorePath), 259 entry("INTERFACE=%s", powerRestoreIntf)); 260 return std::nullopt; 261 } 262 return std::make_optional(restorePolicy); 263 } 264 265 /* 266 * getPowerStatus 267 * helper function for Get Chassis Status Command 268 * return - optional value for pgood (no value on error) 269 */ 270 std::optional<bool> getPowerStatus() 271 { 272 bool powerGood = false; 273 std::shared_ptr<sdbusplus::asio::connection> busp = getSdBus(); 274 try 275 { 276 constexpr const char* chassisStatePath = 277 "/xyz/openbmc_project/state/chassis0"; 278 constexpr const char* chassisStateIntf = 279 "xyz.openbmc_project.State.Chassis"; 280 auto service = 281 ipmi::getService(*busp, chassisStateIntf, chassisStatePath); 282 283 ipmi::Value variant = 284 ipmi::getDbusProperty(*busp, service, chassisStatePath, 285 chassisStateIntf, "CurrentPowerState"); 286 std::string powerState = std::get<std::string>(variant); 287 if (powerState == "xyz.openbmc_project.State.Chassis.PowerState.On") 288 { 289 powerGood = true; 290 } 291 } 292 catch (const std::exception& e) 293 { 294 log<level::ERR>("Failed to fetch power state property", 295 entry("ERROR=%s", e.what())); 296 return std::nullopt; 297 } 298 return std::make_optional(powerGood); 299 } 300 301 /* 302 * getACFailStatus 303 * helper function for Get Chassis Status Command 304 * return - bool value for ACFail (false on error) 305 */ 306 bool getACFailStatus() 307 { 308 constexpr const char* acBootObj = 309 "/xyz/openbmc_project/control/host0/ac_boot"; 310 constexpr const char* acBootIntf = "xyz.openbmc_project.Common.ACBoot"; 311 std::string acFail; 312 std::shared_ptr<sdbusplus::asio::connection> bus = getSdBus(); 313 try 314 { 315 auto service = ipmi::getService(*bus, acBootIntf, acBootObj); 316 317 ipmi::Value variant = ipmi::getDbusProperty(*bus, service, acBootObj, 318 acBootIntf, "ACBoot"); 319 acFail = std::get<std::string>(variant); 320 } 321 catch (const std::exception& e) 322 { 323 log<level::ERR>( 324 "Failed to fetch ACBoot property", entry("ERROR=%s", e.what()), 325 entry("PATH=%s", acBootObj), entry("INTERFACE=%s", acBootIntf)); 326 } 327 return acFail == "True"; 328 } 329 } // namespace power_policy 330 331 static std::optional<bool> getButtonEnabled(const std::string& buttonPath) 332 { 333 bool buttonDisabled = false; 334 std::shared_ptr<sdbusplus::asio::connection> busp = getSdBus(); 335 try 336 { 337 auto service = ipmi::getService(*getSdBus(), buttonIntf, buttonPath); 338 ipmi::Value disabled = ipmi::getDbusProperty( 339 *busp, service, buttonPath, buttonIntf, "ButtonMasked"); 340 buttonDisabled = std::get<bool>(disabled); 341 } 342 catch (sdbusplus::exception::SdBusError& e) 343 { 344 log<level::ERR>("Fail to get button disabled property", 345 entry("PATH=%s", buttonPath.c_str()), 346 entry("ERROR=%s", e.what())); 347 return std::nullopt; 348 } 349 return std::make_optional(buttonDisabled); 350 } 351 352 static bool setButtonEnabled(const std::string& buttonPath, const bool disabled) 353 { 354 try 355 { 356 auto service = ipmi::getService(*getSdBus(), buttonIntf, buttonPath); 357 ipmi::setDbusProperty(*getSdBus(), service, buttonPath, buttonIntf, 358 "ButtonMasked", disabled); 359 } 360 catch (std::exception& e) 361 { 362 log<level::ERR>("Failed to set button disabled", 363 entry("EXCEPTION=%s, REQUEST=%x", e.what(), disabled)); 364 return -1; 365 } 366 367 return 0; 368 } 369 370 static bool getRestartCause(std::string& restartCause) 371 { 372 constexpr const char* restartCausePath = 373 "/xyz/openbmc_project/control/host0/restart_cause"; 374 constexpr const char* restartCauseIntf = 375 "xyz.openbmc_project.Control.Host.RestartCause"; 376 std::shared_ptr<sdbusplus::asio::connection> busp = getSdBus(); 377 378 try 379 { 380 auto service = 381 ipmi::getService(*busp, restartCauseIntf, restartCausePath); 382 383 ipmi::Value result = ipmi::getDbusProperty( 384 *busp, service, restartCausePath, restartCauseIntf, "RestartCause"); 385 restartCause = std::get<std::string>(result); 386 } 387 catch (const std::exception& e) 388 { 389 log<level::ERR>("Failed to fetch RestartCause property", 390 entry("ERROR=%s", e.what()), 391 entry("PATH=%s", restartCausePath), 392 entry("INTERFACE=%s", restartCauseIntf)); 393 return false; 394 } 395 return true; 396 } 397 398 static bool checkIPMIRestartCause(bool& ipmiRestartCause) 399 { 400 std::string restartCause; 401 if (!getRestartCause(restartCause)) 402 { 403 return false; 404 } 405 ipmiRestartCause = 406 (restartCause == 407 "xyz.openbmc_project.State.Host.RestartCause.IpmiCommand"); 408 return true; 409 } 410 411 //---------------------------------------------------------------------- 412 // Get Chassis Status commands 413 //---------------------------------------------------------------------- 414 ipmi::RspType<bool, // Power is on 415 bool, // Power overload 416 bool, // Interlock 417 bool, // power fault 418 bool, // power control fault 419 uint2_t, // power restore policy 420 bool, // reserved 421 422 bool, // AC failed 423 bool, // last power down caused by a Power overload 424 bool, // last power down caused by a power interlock 425 bool, // last power down caused by power fault 426 bool, // last ‘Power is on’ state was entered via IPMI command 427 uint3_t, // reserved 428 429 bool, // Chassis intrusion active 430 bool, // Front Panel Lockout active 431 bool, // Drive Fault 432 bool, // Cooling/fan fault detected 433 uint2_t, // Chassis Identify State 434 bool, // Chassis Identify command and state info supported 435 bool, // reserved 436 437 bool, // Power off button disabled 438 bool, // Reset button disabled 439 bool, // Diagnostic Interrupt button disabled 440 bool, // Standby (sleep) button disabled 441 bool, // Power off button disable allowed 442 bool, // Reset button disable allowed 443 bool, // Diagnostic Interrupt button disable allowed 444 bool // Standby (sleep) button disable allowed 445 > 446 ipmiGetChassisStatus() 447 { 448 std::optional<uint2_t> restorePolicy = 449 power_policy::getPowerRestorePolicy(); 450 std::optional<bool> powerGood = power_policy::getPowerStatus(); 451 if (!restorePolicy || !powerGood) 452 { 453 return ipmi::responseUnspecifiedError(); 454 } 455 456 // Front Panel Button Capabilities and disable/enable status(Optional) 457 std::optional<bool> powerButtonReading = getButtonEnabled(powerButtonPath); 458 // allow disable if the interface is present 459 bool powerButtonDisableAllow = static_cast<bool>(powerButtonReading); 460 // default return the button is enabled (not disabled) 461 bool powerButtonDisabled = false; 462 if (powerButtonDisableAllow) 463 { 464 // return the real value of the button status, if present 465 powerButtonDisabled = *powerButtonReading; 466 } 467 468 std::optional<bool> resetButtonReading = getButtonEnabled(resetButtonPath); 469 // allow disable if the interface is present 470 bool resetButtonDisableAllow = static_cast<bool>(resetButtonReading); 471 // default return the button is enabled (not disabled) 472 bool resetButtonDisabled = false; 473 if (resetButtonDisableAllow) 474 { 475 // return the real value of the button status, if present 476 resetButtonDisabled = *resetButtonReading; 477 } 478 479 std::optional<bool> interruptButtonReading = 480 getButtonEnabled(interruptButtonPath); 481 // allow disable if the interface is present 482 bool interruptButtonDisableAllow = 483 static_cast<bool>(interruptButtonReading); 484 // default return the button is enabled (not disabled) 485 bool interruptButtonDisabled = false; 486 if (interruptButtonDisableAllow) 487 { 488 // return the real value of the button status, if present 489 interruptButtonDisabled = *interruptButtonReading; 490 } 491 492 bool powerDownAcFailed = power_policy::getACFailStatus(); 493 494 bool powerStatusIPMI = false; 495 if (!checkIPMIRestartCause(powerStatusIPMI)) 496 { 497 return ipmi::responseUnspecifiedError(); 498 } 499 500 // This response has a lot of hard-coded, unsupported fields 501 // They are set to false or 0 502 constexpr bool powerOverload = false; 503 constexpr bool chassisInterlock = false; 504 constexpr bool powerFault = false; 505 constexpr bool powerControlFault = false; 506 constexpr bool powerDownOverload = false; 507 constexpr bool powerDownInterlock = false; 508 constexpr bool powerDownPowerFault = false; 509 constexpr bool chassisIntrusionActive = false; 510 constexpr bool frontPanelLockoutActive = false; 511 constexpr bool driveFault = false; 512 constexpr bool coolingFanFault = false; 513 // chassisIdentifySupport set because this command is implemented 514 constexpr bool chassisIdentifySupport = true; 515 uint2_t chassisIdentifyState = chassisIDState; 516 constexpr bool sleepButtonDisabled = false; 517 constexpr bool sleepButtonDisableAllow = false; 518 519 return ipmi::responseSuccess( 520 *powerGood, powerOverload, chassisInterlock, powerFault, 521 powerControlFault, *restorePolicy, 522 false, // reserved 523 524 powerDownAcFailed, powerDownOverload, powerDownInterlock, 525 powerDownPowerFault, powerStatusIPMI, 526 uint3_t(0), // reserved 527 528 chassisIntrusionActive, frontPanelLockoutActive, driveFault, 529 coolingFanFault, chassisIdentifyState, chassisIdentifySupport, 530 false, // reserved 531 532 powerButtonDisabled, resetButtonDisabled, interruptButtonDisabled, 533 sleepButtonDisabled, powerButtonDisableAllow, resetButtonDisableAllow, 534 interruptButtonDisableAllow, sleepButtonDisableAllow); 535 } 536 537 static uint4_t getRestartCauseValue(const std::string& cause) 538 { 539 uint4_t restartCauseValue = 0; 540 if (cause == "xyz.openbmc_project.State.Host.RestartCause.Unknown") 541 { 542 restartCauseValue = 0x0; 543 } 544 else if (cause == "xyz.openbmc_project.State.Host.RestartCause.IpmiCommand") 545 { 546 restartCauseValue = 0x1; 547 } 548 else if (cause == "xyz.openbmc_project.State.Host.RestartCause.ResetButton") 549 { 550 restartCauseValue = 0x2; 551 } 552 else if (cause == "xyz.openbmc_project.State.Host.RestartCause.PowerButton") 553 { 554 restartCauseValue = 0x3; 555 } 556 else if (cause == 557 "xyz.openbmc_project.State.Host.RestartCause.WatchdogTimer") 558 { 559 restartCauseValue = 0x4; 560 } 561 else if (cause == "xyz.openbmc_project.State.Host.RestartCause.OEM") 562 { 563 restartCauseValue = 0x5; 564 } 565 else if (cause == 566 "xyz.openbmc_project.State.Host.RestartCause.PowerPolicyAlwaysOn") 567 { 568 restartCauseValue = 0x6; 569 } 570 else if (cause == "xyz.openbmc_project.State.Host.RestartCause." 571 "PowerPolicyPreviousState") 572 { 573 restartCauseValue = 0x7; 574 } 575 else if (cause == "xyz.openbmc_project.State.Host.RestartCause.PEFReset") 576 { 577 restartCauseValue = 0x8; 578 } 579 else if (cause == 580 "xyz.openbmc_project.State.Host.RestartCause.PEFPowerCycle") 581 { 582 restartCauseValue = 0x9; 583 } 584 else if (cause == "xyz.openbmc_project.State.Host.RestartCause.SoftReset") 585 { 586 restartCauseValue = 0xa; 587 } 588 else if (cause == "xyz.openbmc_project.State.Host.RestartCause.RTCWakeup") 589 { 590 restartCauseValue = 0xb; 591 } 592 return restartCauseValue; 593 } 594 595 ipmi::RspType<uint4_t, // Restart Cause 596 uint4_t, // reserved 597 uint8_t // channel number (not supported) 598 > 599 ipmiGetSystemRestartCause() 600 { 601 std::string restartCauseStr; 602 if (!getRestartCause(restartCauseStr)) 603 { 604 return ipmi::responseUnspecifiedError(); 605 } 606 607 return ipmi::responseSuccess(getRestartCauseValue(restartCauseStr), 0, 0); 608 } 609 610 ipmi::RspType<> ipmiSetFrontPanelButtonEnables(bool disablePowerButton, 611 bool disableResetButton, 612 bool disableInterruptButton, 613 bool disableSleepButton, 614 uint4_t reserved) 615 { 616 bool error = false; 617 618 error |= setButtonEnabled(powerButtonPath, disablePowerButton); 619 error |= setButtonEnabled(resetButtonPath, disableResetButton); 620 error |= setButtonEnabled(interruptButtonPath, disableInterruptButton); 621 622 if (error) 623 { 624 return ipmi::responseUnspecifiedError(); 625 } 626 return ipmi::responseSuccess(); 627 } 628 629 static void registerChassisFunctions(void) 630 { 631 log<level::INFO>("Registering Chassis commands"); 632 633 createIdentifyTimer(); 634 635 if (matchPtr == nullptr) 636 { 637 using namespace sdbusplus::bus::match::rules; 638 auto bus = getSdBus(); 639 640 matchPtr = std::make_unique<sdbusplus::bus::match_t>( 641 *bus, 642 sdbusplus::bus::match::rules::propertiesChanged(idButtonPath, 643 buttonIntf), 644 std::bind(idButtonPropChanged, std::placeholders::_1)); 645 } 646 647 // <Chassis Identify> 648 ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnChassis, 649 ipmi::chassis::cmdChassisIdentify, 650 ipmi::Privilege::Operator, ipmiChassisIdentify); 651 // <Get Chassis Status> 652 ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnChassis, 653 ipmi::chassis::cmdGetChassisStatus, 654 ipmi::Privilege::User, ipmiGetChassisStatus); 655 // <Get System Restart Cause> 656 ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnChassis, 657 ipmi::chassis::cmdGetSystemRestartCause, 658 ipmi::Privilege::User, ipmiGetSystemRestartCause); 659 // <Set Front Panel Enables> 660 ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnChassis, 661 ipmi::chassis::cmdSetFrontPanelButtonEnables, 662 ipmi::Privilege::Admin, 663 ipmiSetFrontPanelButtonEnables); 664 } 665 666 } // namespace ipmi::chassis 667