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 = getDbusProperty(*bus, service, objName, 101 ledInterface, ledProp); 102 state = std::get<bool>(enabled); 103 } 104 catch (const sdbusplus::exception_t& 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_t& 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 = ipmi::getService(*busp, powerRestoreIntf, 231 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 = ipmi::getService(*busp, chassisStateIntf, 281 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 (const sdbusplus::exception_t& 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 (const 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(ipmi::Context::ptr& ctx, 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 377 std::string service; 378 boost::system::error_code ec = ipmi::getService(ctx, restartCauseIntf, 379 restartCausePath, service); 380 381 if (!ec) 382 { 383 ec = ipmi::getDbusProperty(ctx, service, restartCausePath, 384 restartCauseIntf, "RestartCause", 385 restartCause); 386 } 387 if (ec) 388 { 389 log<level::ERR>("Failed to fetch RestartCause property", 390 entry("ERROR=%s", ec.message().c_str()), 391 entry("PATH=%s", restartCausePath), 392 entry("INTERFACE=%s", restartCauseIntf)); 393 return false; 394 } 395 return true; 396 } 397 398 static bool checkIPMIRestartCause(ipmi::Context::ptr& ctx, 399 bool& ipmiRestartCause) 400 { 401 std::string restartCause; 402 if (!getRestartCause(ctx, restartCause)) 403 { 404 return false; 405 } 406 ipmiRestartCause = 407 (restartCause == 408 "xyz.openbmc_project.State.Host.RestartCause.IpmiCommand"); 409 return true; 410 } 411 412 //---------------------------------------------------------------------- 413 // Get Chassis Status commands 414 //---------------------------------------------------------------------- 415 ipmi::RspType<bool, // Power is on 416 bool, // Power overload 417 bool, // Interlock 418 bool, // power fault 419 bool, // power control fault 420 uint2_t, // power restore policy 421 bool, // reserved 422 423 bool, // AC failed 424 bool, // last power down caused by a Power overload 425 bool, // last power down caused by a power interlock 426 bool, // last power down caused by power fault 427 bool, // last ‘Power is on’ state was entered via IPMI command 428 uint3_t, // reserved 429 430 bool, // Chassis intrusion active 431 bool, // Front Panel Lockout active 432 bool, // Drive Fault 433 bool, // Cooling/fan fault detected 434 uint2_t, // Chassis Identify State 435 bool, // Chassis Identify command and state info supported 436 bool, // reserved 437 438 bool, // Power off button disabled 439 bool, // Reset button disabled 440 bool, // Diagnostic Interrupt button disabled 441 bool, // Standby (sleep) button disabled 442 bool, // Power off button disable allowed 443 bool, // Reset button disable allowed 444 bool, // Diagnostic Interrupt button disable allowed 445 bool // Standby (sleep) button disable allowed 446 > 447 ipmiGetChassisStatus(ipmi::Context::ptr ctx) 448 { 449 std::optional<uint2_t> restorePolicy = 450 power_policy::getPowerRestorePolicy(); 451 std::optional<bool> powerGood = power_policy::getPowerStatus(); 452 if (!restorePolicy || !powerGood) 453 { 454 return ipmi::responseUnspecifiedError(); 455 } 456 457 // Front Panel Button Capabilities and disable/enable status(Optional) 458 std::optional<bool> powerButtonReading = getButtonEnabled(powerButtonPath); 459 // allow disable if the interface is present 460 bool powerButtonDisableAllow = static_cast<bool>(powerButtonReading); 461 // default return the button is enabled (not disabled) 462 bool powerButtonDisabled = false; 463 if (powerButtonDisableAllow) 464 { 465 // return the real value of the button status, if present 466 powerButtonDisabled = *powerButtonReading; 467 } 468 469 std::optional<bool> resetButtonReading = getButtonEnabled(resetButtonPath); 470 // allow disable if the interface is present 471 bool resetButtonDisableAllow = static_cast<bool>(resetButtonReading); 472 // default return the button is enabled (not disabled) 473 bool resetButtonDisabled = false; 474 if (resetButtonDisableAllow) 475 { 476 // return the real value of the button status, if present 477 resetButtonDisabled = *resetButtonReading; 478 } 479 480 std::optional<bool> interruptButtonReading = 481 getButtonEnabled(interruptButtonPath); 482 // allow disable if the interface is present 483 bool interruptButtonDisableAllow = 484 static_cast<bool>(interruptButtonReading); 485 // default return the button is enabled (not disabled) 486 bool interruptButtonDisabled = false; 487 if (interruptButtonDisableAllow) 488 { 489 // return the real value of the button status, if present 490 interruptButtonDisabled = *interruptButtonReading; 491 } 492 493 bool powerDownAcFailed = power_policy::getACFailStatus(); 494 495 bool powerStatusIPMI = false; 496 if (!checkIPMIRestartCause(ctx, powerStatusIPMI)) 497 { 498 return ipmi::responseUnspecifiedError(); 499 } 500 501 bool chassisIntrusionActive = false; 502 try 503 { 504 constexpr const char* chassisIntrusionObj = 505 "/xyz/openbmc_project/Intrusion/Chassis_Intrusion"; 506 constexpr const char* chassisIntrusionInf = 507 "xyz.openbmc_project.Chassis.Intrusion"; 508 509 std::string intrusionService; 510 boost::system::error_code ec = ipmi::getService( 511 ctx, chassisIntrusionInf, chassisIntrusionObj, intrusionService); 512 513 chassisIntrusionActive = !intrusionService.empty(); 514 } 515 catch (const std::exception& e) 516 { 517 log<level::ERR>("Failed to get Chassis Intrusion service", 518 entry("ERROR=%s", e.what())); 519 } 520 521 // This response has a lot of hard-coded, unsupported fields 522 // They are set to false or 0 523 constexpr bool powerOverload = false; 524 constexpr bool chassisInterlock = false; 525 constexpr bool powerFault = false; 526 constexpr bool powerControlFault = false; 527 constexpr bool powerDownOverload = false; 528 constexpr bool powerDownInterlock = false; 529 constexpr bool powerDownPowerFault = false; 530 constexpr bool frontPanelLockoutActive = false; 531 constexpr bool driveFault = false; 532 constexpr bool coolingFanFault = false; 533 // chassisIdentifySupport set because this command is implemented 534 constexpr bool chassisIdentifySupport = true; 535 uint2_t chassisIdentifyState = types::enum_cast<uint2_t>(chassisIDState); 536 constexpr bool sleepButtonDisabled = false; 537 constexpr bool sleepButtonDisableAllow = false; 538 539 return ipmi::responseSuccess( 540 *powerGood, powerOverload, chassisInterlock, powerFault, 541 powerControlFault, *restorePolicy, 542 false, // reserved 543 544 powerDownAcFailed, powerDownOverload, powerDownInterlock, 545 powerDownPowerFault, powerStatusIPMI, 546 uint3_t(0), // reserved 547 548 chassisIntrusionActive, frontPanelLockoutActive, driveFault, 549 coolingFanFault, chassisIdentifyState, chassisIdentifySupport, 550 false, // reserved 551 552 powerButtonDisabled, resetButtonDisabled, interruptButtonDisabled, 553 sleepButtonDisabled, powerButtonDisableAllow, resetButtonDisableAllow, 554 interruptButtonDisableAllow, sleepButtonDisableAllow); 555 } 556 557 static uint4_t getRestartCauseValue(const std::string& cause) 558 { 559 uint4_t restartCauseValue = 0; 560 if (cause == "xyz.openbmc_project.State.Host.RestartCause.Unknown") 561 { 562 restartCauseValue = 0x0; 563 } 564 else if (cause == "xyz.openbmc_project.State.Host.RestartCause.IpmiCommand") 565 { 566 restartCauseValue = 0x1; 567 } 568 else if (cause == "xyz.openbmc_project.State.Host.RestartCause.ResetButton") 569 { 570 restartCauseValue = 0x2; 571 } 572 else if (cause == "xyz.openbmc_project.State.Host.RestartCause.PowerButton") 573 { 574 restartCauseValue = 0x3; 575 } 576 else if (cause == 577 "xyz.openbmc_project.State.Host.RestartCause.WatchdogTimer") 578 { 579 restartCauseValue = 0x4; 580 } 581 else if (cause == "xyz.openbmc_project.State.Host.RestartCause.OEM") 582 { 583 restartCauseValue = 0x5; 584 } 585 else if (cause == 586 "xyz.openbmc_project.State.Host.RestartCause.PowerPolicyAlwaysOn") 587 { 588 restartCauseValue = 0x6; 589 } 590 else if (cause == "xyz.openbmc_project.State.Host.RestartCause." 591 "PowerPolicyPreviousState") 592 { 593 restartCauseValue = 0x7; 594 } 595 else if (cause == "xyz.openbmc_project.State.Host.RestartCause.PEFReset") 596 { 597 restartCauseValue = 0x8; 598 } 599 else if (cause == 600 "xyz.openbmc_project.State.Host.RestartCause.PEFPowerCycle") 601 { 602 restartCauseValue = 0x9; 603 } 604 else if (cause == "xyz.openbmc_project.State.Host.RestartCause.SoftReset") 605 { 606 restartCauseValue = 0xa; 607 } 608 else if (cause == "xyz.openbmc_project.State.Host.RestartCause.RTCWakeup") 609 { 610 restartCauseValue = 0xb; 611 } 612 return restartCauseValue; 613 } 614 615 ipmi::RspType<uint4_t, // Restart Cause 616 uint4_t, // reserved 617 uint8_t // channel number 618 > 619 ipmiGetSystemRestartCause(ipmi::Context::ptr ctx) 620 { 621 std::string restartCauseStr; 622 if (!getRestartCause(ctx, restartCauseStr)) 623 { 624 return ipmi::responseUnspecifiedError(); 625 } 626 constexpr uint4_t reserved = 0; 627 auto channel = static_cast<uint8_t>(ctx->channel); 628 return ipmi::responseSuccess(getRestartCauseValue(restartCauseStr), 629 reserved, channel); 630 } 631 632 ipmi::RspType<> ipmiSetFrontPanelButtonEnables(bool disablePowerButton, 633 bool disableResetButton, 634 bool disableInterruptButton, 635 bool disableSleepButton, 636 uint4_t reserved) 637 { 638 if (reserved) 639 { 640 return ipmi::responseInvalidFieldRequest(); 641 } 642 bool error = false; 643 644 error |= setButtonEnabled(powerButtonPath, disablePowerButton); 645 error |= setButtonEnabled(resetButtonPath, disableResetButton); 646 error |= setButtonEnabled(interruptButtonPath, disableInterruptButton); 647 648 if (error) 649 { 650 return ipmi::responseUnspecifiedError(); 651 } 652 653 return ipmi::responseSuccess(); 654 } 655 656 static void registerChassisFunctions(void) 657 { 658 log<level::INFO>("Registering Chassis commands"); 659 660 createIdentifyTimer(); 661 662 if (matchPtr == nullptr) 663 { 664 using namespace sdbusplus::bus::match::rules; 665 auto bus = getSdBus(); 666 667 matchPtr = std::make_unique<sdbusplus::bus::match_t>( 668 *bus, 669 sdbusplus::bus::match::rules::propertiesChanged(idButtonPath, 670 buttonIntf), 671 std::bind(idButtonPropChanged, std::placeholders::_1)); 672 } 673 674 // <Chassis Identify> 675 ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnChassis, 676 ipmi::chassis::cmdChassisIdentify, 677 ipmi::Privilege::Operator, ipmiChassisIdentify); 678 // <Get Chassis Status> 679 ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnChassis, 680 ipmi::chassis::cmdGetChassisStatus, 681 ipmi::Privilege::User, ipmiGetChassisStatus); 682 // <Get System Restart Cause> 683 ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnChassis, 684 ipmi::chassis::cmdGetSystemRestartCause, 685 ipmi::Privilege::User, ipmiGetSystemRestartCause); 686 // <Set Front Panel Enables> 687 ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnChassis, 688 ipmi::chassis::cmdSetFrontPanelButtonEnables, 689 ipmi::Privilege::Admin, 690 ipmiSetFrontPanelButtonEnables); 691 } 692 693 } // namespace ipmi::chassis 694