#include "bmc_state_manager.hpp" #include "utils.hpp" #include "xyz/openbmc_project/Common/error.hpp" #include #include #include #include #include #include #include namespace phosphor { namespace state { namespace manager { PHOSPHOR_LOG2_USING; // When you see server:: you know we're referencing our base class namespace server = sdbusplus::server::xyz::openbmc_project::state; using namespace phosphor::logging; using sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure; constexpr auto obmcQuiesceTarget = "obmc-bmc-service-quiesce@0.target"; constexpr auto obmcStandbyTarget = "multi-user.target"; constexpr auto signalDone = "done"; constexpr auto activeState = "active"; /* Map a transition to it's systemd target */ const std::map SYSTEMD_TABLE = { {server::BMC::Transition::Reboot, "reboot.target"}}; constexpr auto SYSTEMD_SERVICE = "org.freedesktop.systemd1"; constexpr auto SYSTEMD_OBJ_PATH = "/org/freedesktop/systemd1"; constexpr auto SYSTEMD_INTERFACE = "org.freedesktop.systemd1.Manager"; constexpr auto SYSTEMD_PRP_INTERFACE = "org.freedesktop.DBus.Properties"; std::string BMC::getUnitState(const std::string& unitToCheck) { std::variant currentState; sdbusplus::message::object_path unitTargetPath; auto method = this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH, SYSTEMD_INTERFACE, "GetUnit"); method.append(unitToCheck); try { auto result = this->bus.call(method); result.read(unitTargetPath); } catch (const sdbusplus::exception_t& e) { // Not all input units will have been loaded yet so just return an // empty string if an exception is caught in this path info("Unit {UNIT} not found: {ERROR}", "UNIT", unitToCheck, "ERROR", e); return std::string{}; } method = this->bus.new_method_call( SYSTEMD_SERVICE, static_cast(unitTargetPath).c_str(), SYSTEMD_PRP_INTERFACE, "Get"); method.append("org.freedesktop.systemd1.Unit", "ActiveState"); try { auto result = this->bus.call(method); // Is input target active or inactive? result.read(currentState); } catch (const sdbusplus::exception_t& e) { info("Error in ActiveState Get: {ERROR}", "ERROR", e); return std::string{}; } return (std::get(currentState)); } void BMC::discoverInitialState() { // First look to see if the BMC quiesce target is active auto currentStateStr = getUnitState(obmcQuiesceTarget); if (currentStateStr == activeState) { info("Setting the BMCState field to BMC_QUIESCED"); this->currentBMCState(BMCState::Quiesced); return; } // If not quiesced, then check standby target currentStateStr = getUnitState(obmcStandbyTarget); if (currentStateStr == activeState) { info("Setting the BMCState field to BMC_READY"); this->currentBMCState(BMCState::Ready); } else { info("Setting the BMCState field to BMC_NOTREADY"); this->currentBMCState(BMCState::NotReady); } return; } void BMC::executeTransition(const Transition tranReq) { // HardReboot does not shutdown any services and immediately transitions // into the reboot process if (server::BMC::Transition::HardReboot == tranReq) { // Put BMC state not NotReady when issuing a BMC reboot // and stop monitoring for state changes this->currentBMCState(BMCState::NotReady); this->stateSignal.reset(); auto method = this->bus.new_method_call( SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH, SYSTEMD_INTERFACE, "Reboot"); try { this->bus.call(method); } catch (const sdbusplus::exception_t& e) { info("Error in HardReboot: {ERROR}", "ERROR", e); } } else { // Check to make sure it can be found auto iter = SYSTEMD_TABLE.find(tranReq); if (iter == SYSTEMD_TABLE.end()) { return; } const auto& sysdUnit = iter->second; auto method = this->bus.new_method_call( SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH, SYSTEMD_INTERFACE, "StartUnit"); // The only valid transition is reboot and that // needs to be irreversible once started method.append(sysdUnit, "replace-irreversibly"); // Put BMC state not NotReady when issuing a BMC reboot // and stop monitoring for state changes this->currentBMCState(BMCState::NotReady); this->stateSignal.reset(); try { this->bus.call(method); } catch (const sdbusplus::exception_t& e) { info("Error in StartUnit - replace-irreversibly: {ERROR}", "ERROR", e); } } return; } int BMC::bmcStateChange(sdbusplus::message_t& msg) { uint32_t newStateID{}; sdbusplus::message::object_path newStateObjPath; std::string newStateUnit{}; std::string newStateResult{}; // Read the msg and populate each variable msg.read(newStateID, newStateObjPath, newStateUnit, newStateResult); if ((newStateUnit == obmcQuiesceTarget) && (newStateResult == signalDone)) { error("BMC has entered BMC_QUIESCED state"); this->currentBMCState(BMCState::Quiesced); // There is no getting out of Quiesced once entered (other then BMC // reboot) so stop watching for signals auto method = this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH, SYSTEMD_INTERFACE, "Unsubscribe"); try { this->bus.call(method); } catch (const sdbusplus::exception_t& e) { info("Error in Unsubscribe: {ERROR}", "ERROR", e); } // disable the system state change object as well this->stateSignal.reset(); return 0; } // Caught the signal that indicates the BMC is now BMC_READY if ((newStateUnit == obmcStandbyTarget) && (newStateResult == signalDone)) { info("BMC_READY"); this->currentBMCState(BMCState::Ready); } return 0; } BMC::Transition BMC::requestedBMCTransition(Transition value) { info("Setting the RequestedBMCTransition field to " "{REQUESTED_BMC_TRANSITION}", "REQUESTED_BMC_TRANSITION", value); executeTransition(value); return server::BMC::requestedBMCTransition(value); } BMC::BMCState BMC::currentBMCState(BMCState value) { info("Setting the BMCState field to {CURRENT_BMC_STATE}", "CURRENT_BMC_STATE", value); return server::BMC::currentBMCState(value); } BMC::RebootCause BMC::lastRebootCause(RebootCause value) { info("Setting the RebootCause field to {LAST_REBOOT_CAUSE}", "LAST_REBOOT_CAUSE", value); return server::BMC::lastRebootCause(value); } void BMC::updateLastRebootTime() { using namespace std::chrono; struct sysinfo info; auto rc = sysinfo(&info); assert(rc == 0); // Since uptime is in seconds, also get the current time in seconds. auto now = time_point_cast(system_clock::now()); auto rebootTimeTs = now - seconds(info.uptime); rebootTime = duration_cast(rebootTimeTs.time_since_epoch()).count(); server::BMC::lastRebootTime(rebootTime); } uint64_t BMC::lastRebootTime() const { return rebootTime; } void BMC::discoverLastRebootCause() { uint64_t bootReason = 0; std::ifstream file; const auto* bootstatusPath = "/sys/class/watchdog/watchdog0/bootstatus"; file.exceptions(std::ifstream::failbit | std::ifstream::badbit | std::ifstream::eofbit); try { file.open(bootstatusPath); file >> bootReason; } catch (const std::exception& e) { auto rc = errno; error("Failed to read sysfs file {FILE} with errno {ERRNO}", "FILE", bootstatusPath, "ERRNO", rc); } switch (bootReason) { case WDIOF_EXTERN1: this->lastRebootCause(RebootCause::Watchdog); return; case WDIOF_CARDRESET: this->lastRebootCause(RebootCause::POR); return; default: this->lastRebootCause(RebootCause::Unknown); // Continue below to see if more details can be found // on reason for reboot break; } // If the above code could not detect a reason, look for a the // reset-cause-pinhole gpio to see if it is the reason for the reboot auto gpioval = phosphor::state::manager::utils::getGpioValue("reset-cause-pinhole"); // A 0 indicates a pinhole reset occurred if (0 == gpioval) { info("The BMC reset was caused by a pinhole reset"); this->lastRebootCause(RebootCause::PinholeReset); // Generate log telling user a pinhole reset has occurred const std::string errorMsg = "xyz.openbmc_project.State.PinholeReset"; phosphor::state::manager::utils::createError( this->bus, errorMsg, sdbusplus::server::xyz::openbmc_project::logging::Entry::Level:: Notice); return; } // If we still haven't found a reason, see if we lost AC power // Note that a pinhole reset will remove AC power to the chassis // on some systems so we always want to look for the pinhole reset // first as that would be the main reason AC power was lost. size_t chassisId = 0; if (phosphor::state::manager::utils::checkACLoss(chassisId)) { this->lastRebootCause(RebootCause::POR); } return; } } // namespace manager } // namespace state } // namespace phosphor