/** * 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 "config_file_parser.hpp" #include "format_utils.hpp" #include "types.hpp" #include "ucd90160_device.hpp" #include "ucd90320_device.hpp" #include "utility.hpp" #include #include #include #include #include #include #include #include namespace phosphor::power::sequencer { const std::string powerOnTimeoutError = "xyz.openbmc_project.Power.Error.PowerOnTimeout"; const std::string powerOffTimeoutError = "xyz.openbmc_project.Power.Error.PowerOffTimeout"; const std::string shutdownError = "xyz.openbmc_project.Power.Error.Shutdown"; PowerControl::PowerControl(sdbusplus::bus_t& bus, const sdeventplus::Event& event) : PowerObject{bus, POWER_OBJ_PATH, PowerObject::action::defer_emit}, bus{bus}, services{bus}, pgoodWaitTimer{event, std::bind(&PowerControl::onFailureCallback, this)}, powerOnAllowedTime{std::chrono::steady_clock::now() + minimumColdStartTime}, timer{event, std::bind(&PowerControl::pollPgood, this), pollInterval} { // Obtain dbus service name bus.request_name(POWER_IFACE); compatSysTypesFinder = std::make_unique( bus, std::bind_front(&PowerControl::compatibleSystemTypesFound, this)); deviceFinder = std::make_unique( bus, std::bind_front(&PowerControl::deviceFound, this)); setUpGpio(); } int PowerControl::getPgood() const { return pgood; } int PowerControl::getPgoodTimeout() const { return timeout.count(); } int PowerControl::getState() const { return state; } void PowerControl::onFailureCallback() { services.logInfoMsg("After onFailure wait"); onFailure(false); // 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::onFailure(bool wasTimeOut) { std::string error; std::map additionalData{}; // Check if pgood fault occurred on rail monitored by power sequencer device if (device) { try { error = device->findPgoodFault(services, powerSupplyError, additionalData); } catch (const std::exception& e) { services.logErrorMsg(e.what()); additionalData.emplace("ERROR", e.what()); } } // If fault was not isolated to a voltage rail, select a more generic error if (error.empty()) { if (!powerSupplyError.empty()) { error = powerSupplyError; } else if (wasTimeOut) { error = powerOnTimeoutError; } else { error = shutdownError; } } services.logError(error, Entry::Level::Critical, additionalData); if (!wasTimeOut) { services.createBMCDump(); } } 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) { services.logErrorMsg(std::format( "Power state transition timeout, state: {}", state)); inStateTransition = false; if (state) { // Time out powering on onFailure(true); } else { // Time out powering off std::map additionalData{}; services.logError(powerOffTimeoutError, Entry::Level::Critical, 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 services.logErrorMsg("Chassis pgood failure"); pgoodWaitTimer.restartOnce(std::chrono::seconds(7)); failureFound = true; } } 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) { services.logInfoMsg( std::format("Power already at requested state: {}", state)); 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()) { services.logInfoMsg(std::format( "Waiting {} seconds until power on allowed", std::chrono::duration_cast( powerOnAllowedTime - std::chrono::steady_clock::now()) .count())); } std::this_thread::sleep_until(powerOnAllowedTime); } services.logInfoMsg(std::format("setState: {}", s)); services.logInfoMsg(std::format("Powering chassis {}", (s ? "on" : "off"))); 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::compatibleSystemTypesFound( const std::vector& types) { // If we don't already have compatible system types if (compatibleSystemTypes.empty()) { std::string typesStr = format_utils::toString(std::span{types}); services.logInfoMsg( std::format("Compatible system types found: {}", typesStr)); // Store compatible system types compatibleSystemTypes = types; // Load config file and create device object if possible loadConfigFileAndCreateDevice(); } } void PowerControl::deviceFound(const DeviceProperties& properties) { // If we don't already have device properties if (!deviceProperties) { services.logInfoMsg(std::format( "Power sequencer device found: type={}, name={}, bus={:d}, address={:#02x}", properties.type, properties.name, properties.bus, properties.address)); // Store device properties deviceProperties = properties; // Load config file and create device object if possible loadConfigFileAndCreateDevice(); } } 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}; services.logErrorMsg(errorString); throw std::runtime_error(errorString); } powerControlLine = gpiod::find_line(powerControlLineName); if (!powerControlLine) { std::string errorString{"GPIO line name not found: " + powerControlLineName}; services.logErrorMsg(errorString); 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; services.logInfoMsg(std::format("Pgood state: {}", pgoodState)); } void PowerControl::loadConfigFileAndCreateDevice() { // If compatible system types and device properties have been found if (!compatibleSystemTypes.empty() && deviceProperties) { // Find the JSON configuration file std::filesystem::path configFile = findConfigFile(); if (!configFile.empty()) { // Parse the JSON configuration file std::vector> rails; if (parseConfigFile(configFile, rails)) { // Create the power sequencer device object createDevice(std::move(rails)); } } } } std::filesystem::path PowerControl::findConfigFile() { // Find config file for current system based on compatible system types std::filesystem::path configFile; if (!compatibleSystemTypes.empty()) { try { configFile = config_file_parser::find(compatibleSystemTypes); if (!configFile.empty()) { services.logInfoMsg(std::format( "JSON configuration file found: {}", configFile.string())); } } catch (const std::exception& e) { services.logErrorMsg(std::format( "Unable to find JSON configuration file: {}", e.what())); } } return configFile; } bool PowerControl::parseConfigFile(const std::filesystem::path& configFile, std::vector>& rails) { // Parse JSON configuration file bool wasParsed{false}; try { rails = config_file_parser::parse(configFile); wasParsed = true; } catch (const std::exception& e) { services.logErrorMsg(std::format( "Unable to parse JSON configuration file: {}", e.what())); } return wasParsed; } void PowerControl::createDevice(std::vector> rails) { // Create power sequencer device based on device properties if (deviceProperties) { try { if (deviceProperties->type == UCD90160Device::deviceName) { device = std::make_unique( std::move(rails), services, deviceProperties->bus, deviceProperties->address); } else if (deviceProperties->type == UCD90320Device::deviceName) { device = std::make_unique( std::move(rails), services, deviceProperties->bus, deviceProperties->address); } else { throw std::runtime_error{std::format( "Unsupported device type: {}", deviceProperties->type)}; } services.logInfoMsg(std::format( "Power sequencer device created: {}", device->getName())); } catch (const std::exception& e) { services.logErrorMsg( std::format("Unable to create device object: {}", e.what())); } } } } // namespace phosphor::power::sequencer