/* // Copyright (c) 2019 Intel Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. */ #include "xyz/openbmc_project/Common/error.hpp" #include #include #include #include #include #include #include #include #include #include #include #include using namespace phosphor::logging; namespace ipmi::chassis { static constexpr const char* buttonIntf = "xyz.openbmc_project.Chassis.Buttons"; const static constexpr char* idButtonPath = "/xyz/openbmc_project/chassis/buttons/id"; static constexpr const char* powerButtonPath = "/xyz/openbmc_project/chassis/buttons/power"; static constexpr const char* resetButtonPath = "/xyz/openbmc_project/chassis/buttons/reset"; static constexpr const char* interruptButtonPath = "/xyz/openbmc_project/chassis/buttons/nmi"; const static constexpr char* idButtonProp = "ButtonPressed"; const static constexpr char* ledService = "xyz.openbmc_project.LED.GroupManager"; const static constexpr char* ledIDOnObj = "/xyz/openbmc_project/led/groups/enclosure_identify"; const static constexpr char* ledIDBlinkObj = "/xyz/openbmc_project/led/groups/enclosure_identify_blink"; const static constexpr char* ledInterface = "xyz.openbmc_project.Led.Group"; const static constexpr char* ledProp = "Asserted"; enum class ChassisIDState { off = 0, temporary = 1, indefinite = 2, }; static ChassisIDState chassisIDState = ChassisIDState::off; constexpr size_t defaultIdentifyTimeOut = 15; std::unique_ptr identifyTimer __attribute__((init_priority(101))); std::unique_ptr matchPtr __attribute__((init_priority(101))); static void registerChassisFunctions() __attribute__((constructor)); static ipmi::ServiceCache LEDService(ledInterface, ledIDBlinkObj); void enclosureIdentifyLed(const char* objName, bool isIdLedOn) { auto bus = getSdBus(); try { std::string service = LEDService.getService(*bus); setDbusProperty(*bus, service, objName, ledInterface, ledProp, isIdLedOn); } catch (const std::exception& e) { log("enclosureIdentifyLed: can't set property", entry("ERR=%s", e.what())); } } bool getIDState(const char* objName, bool& state) { auto bus = getSdBus(); try { std::string service = LEDService.getService(*bus); ipmi::Value enabled = getDbusProperty(*bus, service, objName, ledInterface, ledProp); state = std::get(enabled); } catch (const sdbusplus::exception_t& e) { log("Fail to get property", entry("PATH=%s", objName), entry("ERROR=%s", e.what())); return false; } return true; } void enclosureIdentifyLedBlinkOff() { chassisIDState = ChassisIDState::off; enclosureIdentifyLed(ledIDBlinkObj, false); } void idButtonPropChanged(sdbusplus::message_t& msg) { bool asserted = false; bool buttonPressed = false; std::map props; std::vector inval; std::string iface; msg.read(iface, props, inval); for (const auto& t : props) { auto key = t.first; auto value = t.second; if (key == idButtonProp) { buttonPressed = std::get(value); } break; } if (buttonPressed) { if (identifyTimer->isRunning()) { log("ID timer is running"); } // make sure timer is stopped identifyTimer->stop(); if (!getIDState(ledIDBlinkObj, asserted)) { return; } if (asserted) { // LED is blinking, turn off the LED chassisIDState = ChassisIDState::off; enclosureIdentifyLed(ledIDBlinkObj, false); enclosureIdentifyLed(ledIDOnObj, false); } else { // toggle the IED on/off if (!getIDState(ledIDOnObj, asserted)) { return; } enclosureIdentifyLed(ledIDOnObj, !asserted); } } } void createIdentifyTimer() { if (!identifyTimer) { identifyTimer = std::make_unique(enclosureIdentifyLedBlinkOff); } } ipmi::RspType<> ipmiChassisIdentify(std::optional interval, std::optional force) { uint8_t identifyInterval = interval.value_or(defaultIdentifyTimeOut); bool forceIdentify = force.value_or(0) & 0x01; enclosureIdentifyLed(ledIDOnObj, false); identifyTimer->stop(); if (identifyInterval || forceIdentify) { enclosureIdentifyLed(ledIDBlinkObj, true); if (forceIdentify) { chassisIDState = ChassisIDState::indefinite; return ipmi::responseSuccess(); } chassisIDState = ChassisIDState::temporary; // start the timer auto time = std::chrono::duration_cast( std::chrono::seconds(identifyInterval)); identifyTimer->start(time); } else { chassisIDState = ChassisIDState::off; enclosureIdentifyLed(ledIDBlinkObj, false); } return ipmi::responseSuccess(); } namespace power_policy { /* helper function for Get Chassis Status Command */ std::optional getPowerRestorePolicy() { constexpr const char* powerRestorePath = "/xyz/openbmc_project/control/host0/power_restore_policy"; constexpr const char* powerRestoreIntf = "xyz.openbmc_project.Control.Power.RestorePolicy"; uint2_t restorePolicy = 0; std::shared_ptr busp = getSdBus(); try { auto service = ipmi::getService(*busp, powerRestoreIntf, powerRestorePath); ipmi::Value result = ipmi::getDbusProperty(*busp, service, powerRestorePath, powerRestoreIntf, "PowerRestorePolicy"); auto powerRestore = sdbusplus::xyz::openbmc_project::Control::Power:: server::RestorePolicy::convertPolicyFromString( std::get(result)); using namespace sdbusplus::xyz::openbmc_project::Control::Power::server; switch (powerRestore) { case RestorePolicy::Policy::AlwaysOff: restorePolicy = 0x00; break; case RestorePolicy::Policy::Restore: restorePolicy = 0x01; break; case RestorePolicy::Policy::AlwaysOn: restorePolicy = 0x02; break; default: break; } } catch (const std::exception& e) { log("Failed to fetch PowerRestorePolicy property", entry("ERROR=%s", e.what()), entry("PATH=%s", powerRestorePath), entry("INTERFACE=%s", powerRestoreIntf)); return std::nullopt; } return std::make_optional(restorePolicy); } /* * getPowerStatus * helper function for Get Chassis Status Command * return - optional value for pgood (no value on error) */ std::optional getPowerStatus() { bool powerGood = false; std::shared_ptr busp = getSdBus(); try { constexpr const char* chassisStatePath = "/xyz/openbmc_project/state/chassis0"; constexpr const char* chassisStateIntf = "xyz.openbmc_project.State.Chassis"; auto service = ipmi::getService(*busp, chassisStateIntf, chassisStatePath); ipmi::Value variant = ipmi::getDbusProperty(*busp, service, chassisStatePath, chassisStateIntf, "CurrentPowerState"); std::string powerState = std::get(variant); if (powerState == "xyz.openbmc_project.State.Chassis.PowerState.On") { powerGood = true; } } catch (const std::exception& e) { log("Failed to fetch power state property", entry("ERROR=%s", e.what())); return std::nullopt; } return std::make_optional(powerGood); } /* * getACFailStatus * helper function for Get Chassis Status Command * return - bool value for ACFail (false on error) */ bool getACFailStatus() { constexpr const char* acBootObj = "/xyz/openbmc_project/control/host0/ac_boot"; constexpr const char* acBootIntf = "xyz.openbmc_project.Common.ACBoot"; std::string acFail; std::shared_ptr bus = getSdBus(); try { auto service = ipmi::getService(*bus, acBootIntf, acBootObj); ipmi::Value variant = ipmi::getDbusProperty(*bus, service, acBootObj, acBootIntf, "ACBoot"); acFail = std::get(variant); } catch (const std::exception& e) { log( "Failed to fetch ACBoot property", entry("ERROR=%s", e.what()), entry("PATH=%s", acBootObj), entry("INTERFACE=%s", acBootIntf)); } return acFail == "True"; } } // namespace power_policy static std::optional getButtonEnabled(const std::string& buttonPath) { bool buttonDisabled = false; std::shared_ptr busp = getSdBus(); try { auto service = ipmi::getService(*getSdBus(), buttonIntf, buttonPath); ipmi::Value disabled = ipmi::getDbusProperty( *busp, service, buttonPath, buttonIntf, "ButtonMasked"); buttonDisabled = std::get(disabled); } catch (const sdbusplus::exception_t& e) { log("Fail to get button disabled property", entry("PATH=%s", buttonPath.c_str()), entry("ERROR=%s", e.what())); return std::nullopt; } return std::make_optional(buttonDisabled); } static bool setButtonEnabled(const std::string& buttonPath, const bool disabled) { try { auto service = ipmi::getService(*getSdBus(), buttonIntf, buttonPath); ipmi::setDbusProperty(*getSdBus(), service, buttonPath, buttonIntf, "ButtonMasked", disabled); } catch (const std::exception& e) { log("Failed to set button disabled", entry("EXCEPTION=%s, REQUEST=%x", e.what(), disabled)); return -1; } return 0; } static bool getRestartCause(ipmi::Context::ptr& ctx, std::string& restartCause) { constexpr const char* restartCausePath = "/xyz/openbmc_project/control/host0/restart_cause"; constexpr const char* restartCauseIntf = "xyz.openbmc_project.Control.Host.RestartCause"; std::string service; boost::system::error_code ec = ipmi::getService(ctx, restartCauseIntf, restartCausePath, service); if (!ec) { ec = ipmi::getDbusProperty(ctx, service, restartCausePath, restartCauseIntf, "RestartCause", restartCause); } if (ec) { log("Failed to fetch RestartCause property", entry("ERROR=%s", ec.message().c_str()), entry("PATH=%s", restartCausePath), entry("INTERFACE=%s", restartCauseIntf)); return false; } return true; } static bool checkIPMIRestartCause(ipmi::Context::ptr& ctx, bool& ipmiRestartCause) { std::string restartCause; if (!getRestartCause(ctx, restartCause)) { return false; } ipmiRestartCause = (restartCause == "xyz.openbmc_project.State.Host.RestartCause.IpmiCommand"); return true; } //---------------------------------------------------------------------- // Get Chassis Status commands //---------------------------------------------------------------------- ipmi::RspType ipmiGetChassisStatus(ipmi::Context::ptr ctx) { std::optional restorePolicy = power_policy::getPowerRestorePolicy(); std::optional powerGood = power_policy::getPowerStatus(); if (!restorePolicy || !powerGood) { return ipmi::responseUnspecifiedError(); } // Front Panel Button Capabilities and disable/enable status(Optional) std::optional powerButtonReading = getButtonEnabled(powerButtonPath); // allow disable if the interface is present bool powerButtonDisableAllow = static_cast(powerButtonReading); // default return the button is enabled (not disabled) bool powerButtonDisabled = false; if (powerButtonDisableAllow) { // return the real value of the button status, if present powerButtonDisabled = *powerButtonReading; } std::optional resetButtonReading = getButtonEnabled(resetButtonPath); // allow disable if the interface is present bool resetButtonDisableAllow = static_cast(resetButtonReading); // default return the button is enabled (not disabled) bool resetButtonDisabled = false; if (resetButtonDisableAllow) { // return the real value of the button status, if present resetButtonDisabled = *resetButtonReading; } std::optional interruptButtonReading = getButtonEnabled(interruptButtonPath); // allow disable if the interface is present bool interruptButtonDisableAllow = static_cast(interruptButtonReading); // default return the button is enabled (not disabled) bool interruptButtonDisabled = false; if (interruptButtonDisableAllow) { // return the real value of the button status, if present interruptButtonDisabled = *interruptButtonReading; } bool powerDownAcFailed = power_policy::getACFailStatus(); bool powerStatusIPMI = false; if (!checkIPMIRestartCause(ctx, powerStatusIPMI)) { return ipmi::responseUnspecifiedError(); } bool chassisIntrusionActive = false; constexpr const char* chassisIntrusionObj = "/xyz/openbmc_project/Chassis/Intrusion"; constexpr const char* chassisIntrusionInf = "xyz.openbmc_project.Chassis.Intrusion"; std::string intrusionService; boost::system::error_code ec = ipmi::getService( ctx, chassisIntrusionInf, chassisIntrusionObj, intrusionService); if (ec) { log("Failed to get Chassis Intrusion service", entry("ERROR=%s", ec.message().c_str())); } chassisIntrusionActive = !intrusionService.empty(); // This response has a lot of hard-coded, unsupported fields // They are set to false or 0 constexpr bool powerOverload = false; constexpr bool chassisInterlock = false; constexpr bool powerFault = false; constexpr bool powerControlFault = false; constexpr bool powerDownOverload = false; constexpr bool powerDownInterlock = false; constexpr bool powerDownPowerFault = false; constexpr bool frontPanelLockoutActive = false; constexpr bool driveFault = false; constexpr bool coolingFanFault = false; // chassisIdentifySupport set because this command is implemented constexpr bool chassisIdentifySupport = true; uint2_t chassisIdentifyState = types::enum_cast(chassisIDState); constexpr bool sleepButtonDisabled = false; constexpr bool sleepButtonDisableAllow = false; return ipmi::responseSuccess( *powerGood, powerOverload, chassisInterlock, powerFault, powerControlFault, *restorePolicy, false, // reserved powerDownAcFailed, powerDownOverload, powerDownInterlock, powerDownPowerFault, powerStatusIPMI, uint3_t(0), // reserved chassisIntrusionActive, frontPanelLockoutActive, driveFault, coolingFanFault, chassisIdentifyState, chassisIdentifySupport, false, // reserved powerButtonDisabled, resetButtonDisabled, interruptButtonDisabled, sleepButtonDisabled, powerButtonDisableAllow, resetButtonDisableAllow, interruptButtonDisableAllow, sleepButtonDisableAllow); } static uint4_t getRestartCauseValue(const std::string& cause) { uint4_t restartCauseValue = 0; if (cause == "xyz.openbmc_project.State.Host.RestartCause.Unknown") { restartCauseValue = 0x0; } else if (cause == "xyz.openbmc_project.State.Host.RestartCause.IpmiCommand") { restartCauseValue = 0x1; } else if (cause == "xyz.openbmc_project.State.Host.RestartCause.ResetButton") { restartCauseValue = 0x2; } else if (cause == "xyz.openbmc_project.State.Host.RestartCause.PowerButton") { restartCauseValue = 0x3; } else if (cause == "xyz.openbmc_project.State.Host.RestartCause.WatchdogTimer") { restartCauseValue = 0x4; } else if (cause == "xyz.openbmc_project.State.Host.RestartCause.OEM") { restartCauseValue = 0x5; } else if (cause == "xyz.openbmc_project.State.Host.RestartCause.PowerPolicyAlwaysOn") { restartCauseValue = 0x6; } else if (cause == "xyz.openbmc_project.State.Host.RestartCause." "PowerPolicyPreviousState") { restartCauseValue = 0x7; } else if (cause == "xyz.openbmc_project.State.Host.RestartCause.PEFReset") { restartCauseValue = 0x8; } else if (cause == "xyz.openbmc_project.State.Host.RestartCause.PEFPowerCycle") { restartCauseValue = 0x9; } else if (cause == "xyz.openbmc_project.State.Host.RestartCause.SoftReset") { restartCauseValue = 0xa; } else if (cause == "xyz.openbmc_project.State.Host.RestartCause.RTCWakeup") { restartCauseValue = 0xb; } return restartCauseValue; } ipmi::RspType ipmiGetSystemRestartCause(ipmi::Context::ptr ctx) { std::string restartCauseStr; if (!getRestartCause(ctx, restartCauseStr)) { return ipmi::responseUnspecifiedError(); } constexpr uint4_t reserved = 0; auto channel = static_cast(ctx->channel); return ipmi::responseSuccess(getRestartCauseValue(restartCauseStr), reserved, channel); } ipmi::RspType<> ipmiSetFrontPanelButtonEnables( bool disablePowerButton, bool disableResetButton, bool disableInterruptButton, [[maybe_unused]] bool disableSleepButton, uint4_t reserved) { if (reserved) { return ipmi::responseInvalidFieldRequest(); } bool error = false; error |= setButtonEnabled(powerButtonPath, disablePowerButton); error |= setButtonEnabled(resetButtonPath, disableResetButton); error |= setButtonEnabled(interruptButtonPath, disableInterruptButton); if (error) { return ipmi::responseUnspecifiedError(); } return ipmi::responseSuccess(); } static void registerChassisFunctions(void) { log("Registering Chassis commands"); createIdentifyTimer(); if (matchPtr == nullptr) { using namespace sdbusplus::bus::match::rules; auto bus = getSdBus(); matchPtr = std::make_unique( *bus, sdbusplus::bus::match::rules::propertiesChanged(idButtonPath, buttonIntf), std::bind(idButtonPropChanged, std::placeholders::_1)); } // ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnChassis, ipmi::chassis::cmdChassisIdentify, ipmi::Privilege::Operator, ipmiChassisIdentify); // ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnChassis, ipmi::chassis::cmdGetChassisStatus, ipmi::Privilege::User, ipmiGetChassisStatus); // ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnChassis, ipmi::chassis::cmdGetSystemRestartCause, ipmi::Privilege::User, ipmiGetSystemRestartCause); // ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnChassis, ipmi::chassis::cmdSetFrontPanelButtonEnables, ipmi::Privilege::Admin, ipmiSetFrontPanelButtonEnables); } } // namespace ipmi::chassis