| /** |
| * Copyright © 2021 IBM 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 "power_control.hpp" |
| |
| #include "types.hpp" |
| #include "ucd90320_monitor.hpp" |
| |
| #include <fmt/chrono.h> |
| #include <fmt/format.h> |
| |
| #include <phosphor-logging/elog-errors.hpp> |
| #include <phosphor-logging/elog.hpp> |
| #include <phosphor-logging/log.hpp> |
| #include <xyz/openbmc_project/Common/error.hpp> |
| |
| #include <exception> |
| #include <string> |
| |
| using namespace phosphor::logging; |
| |
| namespace phosphor::power::sequencer |
| { |
| |
| const std::string interfaceName = "xyz.openbmc_project.Configuration.UCD90320"; |
| const std::string addressPropertyName = "Address"; |
| const std::string busPropertyName = "Bus"; |
| const std::string namePropertyName = "Name"; |
| |
| PowerControl::PowerControl(sdbusplus::bus::bus& bus, |
| const sdeventplus::Event& event) : |
| PowerObject{bus, POWER_OBJ_PATH, PowerObject::action::defer_emit}, |
| bus{bus}, device{std::make_unique<PowerSequencerMonitor>(bus)}, |
| match{bus, |
| sdbusplus::bus::match::rules::interfacesAdded() + |
| sdbusplus::bus::match::rules::sender( |
| "xyz.openbmc_project.EntityManager"), |
| std::bind(&PowerControl::interfacesAddedHandler, this, |
| std::placeholders::_1)}, |
| powerOnAllowedTime{std::chrono::steady_clock::now() + minimumColdStartTime}, |
| timer{event, std::bind(&PowerControl::pollPgood, this), pollInterval} |
| { |
| // Obtain dbus service name |
| bus.request_name(POWER_IFACE); |
| |
| setUpDevice(); |
| setUpGpio(); |
| } |
| |
| void PowerControl::getDeviceProperties(util::DbusPropertyMap& properties) |
| { |
| uint64_t* i2cBus = nullptr; |
| uint64_t* i2cAddress = nullptr; |
| std::string* name = nullptr; |
| |
| for (const auto& property : properties) |
| { |
| try |
| { |
| if (property.first == busPropertyName) |
| { |
| i2cBus = std::get_if<uint64_t>(&properties[busPropertyName]); |
| } |
| else if (property.first == addressPropertyName) |
| { |
| i2cAddress = |
| std::get_if<uint64_t>(&properties[addressPropertyName]); |
| } |
| else if (property.first == namePropertyName) |
| { |
| name = std::get_if<std::string>(&properties[namePropertyName]); |
| } |
| } |
| catch (const std::exception&) |
| {} |
| } |
| |
| if (i2cBus && i2cAddress && name && !name->empty()) |
| { |
| log<level::DEBUG>( |
| fmt::format( |
| "Found power sequencer device properties, name: {}, bus: {} addr: {:#02x} ", |
| *name, *i2cBus, *i2cAddress) |
| .c_str()); |
| // Create device object |
| device = std::make_unique<UCD90320Monitor>(bus, *i2cBus, *i2cAddress); |
| deviceFound = true; |
| } |
| } |
| |
| int PowerControl::getPgood() const |
| { |
| return pgood; |
| } |
| |
| int PowerControl::getPgoodTimeout() const |
| { |
| return timeout.count(); |
| } |
| |
| int PowerControl::getState() const |
| { |
| return state; |
| } |
| |
| void PowerControl::interfacesAddedHandler(sdbusplus::message::message& msg) |
| { |
| // Only continue if message is valid and device has not already been found |
| if (!msg || deviceFound) |
| { |
| return; |
| } |
| |
| try |
| { |
| // Read the dbus message |
| sdbusplus::message::object_path objPath; |
| std::map<std::string, std::map<std::string, util::DbusVariant>> |
| interfaces; |
| msg.read(objPath, interfaces); |
| |
| // Find the device interface, if present |
| auto itIntf = interfaces.find(interfaceName); |
| if (itIntf != interfaces.cend()) |
| { |
| log<level::INFO>( |
| fmt::format("InterfacesAdded for: {}", interfaceName).c_str()); |
| getDeviceProperties(itIntf->second); |
| } |
| } |
| catch (const std::exception&) |
| { |
| // Error trying to read interfacesAdded message. |
| } |
| } |
| |
| void PowerControl::pollPgood() |
| { |
| if (inStateTransition) |
| { |
| // In transition between power on and off, check for timeout |
| const auto now = std::chrono::steady_clock::now(); |
| if (now > pgoodTimeoutTime) |
| { |
| log<level::ERR>( |
| fmt::format("Power state transition timeout, state: {}", state) |
| .c_str()); |
| inStateTransition = false; |
| |
| if (state) |
| { |
| // Time out powering on |
| device->onFailure(true, powerSupplyError); |
| } |
| else |
| { |
| // Time out powering off |
| std::map<std::string, std::string> additionalData{}; |
| device->logError( |
| "xyz.openbmc_project.Power.Error.PowerOffTimeout", |
| additionalData); |
| } |
| |
| failureFound = true; |
| return; |
| } |
| } |
| |
| int pgoodState = pgoodLine.get_value(); |
| if (pgoodState != pgood) |
| { |
| // Power good has changed since last read |
| pgood = pgoodState; |
| if (pgoodState == 0) |
| { |
| emitPowerLostSignal(); |
| } |
| else |
| { |
| emitPowerGoodSignal(); |
| // Clear any errors on the transition to power on |
| powerSupplyError.clear(); |
| failureFound = false; |
| } |
| emitPropertyChangedSignal("pgood"); |
| } |
| if (pgoodState == state) |
| { |
| // Power good matches requested state |
| inStateTransition = false; |
| } |
| else if (!inStateTransition && (pgoodState == 0) && !failureFound) |
| { |
| // Not in power off state, not changing state, and power good is off |
| log<level::ERR>("Chassis pgood failure"); |
| device->onFailure(false, powerSupplyError); |
| failureFound = true; |
| |
| // Power good has failed, call for chassis hard power off |
| auto method = |
| bus.new_method_call(util::SYSTEMD_SERVICE, util::SYSTEMD_ROOT, |
| util::SYSTEMD_INTERFACE, "StartUnit"); |
| method.append(util::POWEROFF_TARGET); |
| method.append("replace"); |
| bus.call_noreply(method); |
| } |
| } |
| |
| void PowerControl::setPgoodTimeout(int t) |
| { |
| if (timeout.count() != t) |
| { |
| timeout = std::chrono::seconds(t); |
| emitPropertyChangedSignal("pgood_timeout"); |
| } |
| } |
| |
| void PowerControl::setPowerSupplyError(const std::string& error) |
| { |
| powerSupplyError = error; |
| } |
| |
| void PowerControl::setState(int s) |
| { |
| if (state == s) |
| { |
| log<level::INFO>( |
| fmt::format("Power already at requested state: {}", state).c_str()); |
| return; |
| } |
| if (s == 0) |
| { |
| // Wait for two seconds when powering down. This is to allow host and |
| // other BMC applications time to complete power off processing |
| std::this_thread::sleep_for(std::chrono::seconds(2)); |
| } |
| else |
| { |
| // If minimum power off time has not passed, wait |
| if (powerOnAllowedTime > std::chrono::steady_clock::now()) |
| { |
| log<level::INFO>( |
| fmt::format( |
| "Waiting {} seconds until power on allowed", |
| std::chrono::duration_cast<std::chrono::seconds>( |
| powerOnAllowedTime - std::chrono::steady_clock::now()) |
| .count()) |
| .c_str()); |
| } |
| std::this_thread::sleep_until(powerOnAllowedTime); |
| } |
| |
| log<level::INFO>(fmt::format("setState: {}", s).c_str()); |
| powerControlLine.request( |
| {"phosphor-power-control", gpiod::line_request::DIRECTION_OUTPUT, 0}); |
| powerControlLine.set_value(s); |
| powerControlLine.release(); |
| |
| if (s == 0) |
| { |
| // Set a minimum amount of time to wait before next power on |
| powerOnAllowedTime = |
| std::chrono::steady_clock::now() + minimumPowerOffTime; |
| } |
| |
| pgoodTimeoutTime = std::chrono::steady_clock::now() + timeout; |
| inStateTransition = true; |
| state = s; |
| emitPropertyChangedSignal("state"); |
| } |
| |
| void PowerControl::setUpDevice() |
| { |
| try |
| { |
| auto objects = util::getSubTree(bus, "/", interfaceName, 0); |
| |
| // Search for matching interface in returned objects |
| for (const auto& [path, services] : objects) |
| { |
| auto service = services.begin()->first; |
| |
| if (path.empty() || service.empty()) |
| { |
| continue; |
| } |
| |
| // Get the properties for the device interface |
| auto properties = |
| util::getAllProperties(bus, path, interfaceName, service); |
| |
| getDeviceProperties(properties); |
| } |
| } |
| catch (const std::exception&) |
| { |
| // Interface or property not found. Let the Interfaces Added callback |
| // process the information once the interfaces are added to D-Bus. |
| } |
| } |
| |
| void PowerControl::setUpGpio() |
| { |
| const std::string powerControlLineName = "power-chassis-control"; |
| const std::string pgoodLineName = "power-chassis-good"; |
| |
| pgoodLine = gpiod::find_line(pgoodLineName); |
| if (!pgoodLine) |
| { |
| std::string errorString{"GPIO line name not found: " + pgoodLineName}; |
| log<level::ERR>(errorString.c_str()); |
| report< |
| sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure>(); |
| throw std::runtime_error(errorString); |
| } |
| powerControlLine = gpiod::find_line(powerControlLineName); |
| if (!powerControlLine) |
| { |
| std::string errorString{"GPIO line name not found: " + |
| powerControlLineName}; |
| log<level::ERR>(errorString.c_str()); |
| report< |
| sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure>(); |
| throw std::runtime_error(errorString); |
| } |
| |
| pgoodLine.request( |
| {"phosphor-power-control", gpiod::line_request::DIRECTION_INPUT, 0}); |
| int pgoodState = pgoodLine.get_value(); |
| pgood = pgoodState; |
| state = pgoodState; |
| log<level::INFO>(fmt::format("Pgood state: {}", pgoodState).c_str()); |
| } |
| |
| } // namespace phosphor::power::sequencer |