/** * Copyright © 2020 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 "manager.hpp" #include "chassis.hpp" #include "config_file_parser.hpp" #include "exception_utils.hpp" #include "format_utils.hpp" #include "rule.hpp" #include "utility.hpp" #include #include #include #include #include #include #include #include #include namespace phosphor::power::regulators { namespace fs = std::filesystem; constexpr auto busName = "xyz.openbmc_project.Power.Regulators"; constexpr auto managerObjPath = "/xyz/openbmc_project/power/regulators/manager"; constexpr auto chassisStatePath = "/xyz/openbmc_project/state/chassis0"; constexpr auto chassisStateIntf = "xyz.openbmc_project.State.Chassis"; constexpr auto chassisStateProp = "CurrentPowerState"; constexpr std::chrono::minutes maxTimeToWaitForCompatTypes{5}; using PowerState = sdbusplus::xyz::openbmc_project::State::server::Chassis::PowerState; /** * Default configuration file name. This is used when the system does not * implement the D-Bus compatible interface. */ constexpr auto defaultConfigFileName = "config.json"; /** * Standard configuration file directory. This directory is part of the * firmware install image. It contains the standard version of the config file. */ const fs::path standardConfigFileDir{"/usr/share/phosphor-regulators"}; /** * Test configuration file directory. This directory can contain a test version * of the config file. The test version will override the standard version. */ const fs::path testConfigFileDir{"/etc/phosphor-regulators"}; Manager::Manager(sdbusplus::bus_t& bus, const sdeventplus::Event& event) : ManagerObject{bus, managerObjPath}, bus{bus}, eventLoop{event}, services{bus}, phaseFaultTimer{event, std::bind(&Manager::phaseFaultTimerExpired, this)}, sensorTimer{event, std::bind(&Manager::sensorTimerExpired, this)} { // Create object to find compatible system types for current system. // Note that some systems do not provide this information. compatSysTypesFinder = std::make_unique( bus, std::bind_front(&Manager::compatibleSystemTypesFound, this)); // If no system types found so far, try to load default config file if (compatibleSystemTypes.empty()) { loadConfigFile(); } // Obtain D-Bus service name bus.request_name(busName); // If system is already powered on, enable monitoring if (isSystemPoweredOn()) { monitor(true); } } void Manager::configure() { // Clear any cached data or error history related to hardware devices clearHardwareData(); // Wait until the config file has been loaded or hit max wait time waitUntilConfigFileLoaded(); // Verify config file has been loaded and System object is valid if (isConfigFileLoaded()) { // Configure the regulator devices in the system system->configure(services); } else { // Write error message to journal services.getJournal().logError("Unable to configure regulator devices: " "Configuration file not loaded"); // Log critical error since regulators could not be configured. Could // cause hardware damage if default regulator settings are very wrong. services.getErrorLogging().logConfigFileError(Entry::Level::Critical, services.getJournal()); // Throw InternalFailure to propogate error status to D-Bus client throw sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure{}; } } void Manager::monitor(bool enable) { // Check whether already in the requested monitoring state if (enable == isMonitoringEnabled) { return; } isMonitoringEnabled = enable; if (isMonitoringEnabled) { services.getJournal().logDebug("Monitoring enabled"); // Restart phase fault detection timer with repeating 15 second interval phaseFaultTimer.restart(std::chrono::seconds(15)); // Restart sensor monitoring timer with repeating 1 second interval sensorTimer.restart(std::chrono::seconds(1)); // Enable sensors service; put all sensors in an active state services.getSensors().enable(); } else { services.getJournal().logDebug("Monitoring disabled"); // Disable timers phaseFaultTimer.setEnabled(false); sensorTimer.setEnabled(false); // Disable sensors service; put all sensors in an inactive state services.getSensors().disable(); // Verify config file has been loaded and System object is valid if (isConfigFileLoaded()) { // Close the regulator devices in the system. Monitoring is // normally disabled because the system is being powered off. The // devices should be closed in case hardware is removed or replaced // while the system is powered off. system->closeDevices(services); } } } void Manager::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.getJournal().logInfo( std::format("Compatible system types found: {}", typesStr)); // Store compatible system types compatibleSystemTypes = types; // Find and load JSON config file based on system types loadConfigFile(); } } void Manager::phaseFaultTimerExpired() { // Verify config file has been loaded and System object is valid if (isConfigFileLoaded()) { // Detect redundant phase faults in regulator devices in the system system->detectPhaseFaults(services); } } void Manager::sensorTimerExpired() { // Notify sensors service that a sensor monitoring cycle is starting services.getSensors().startCycle(); // Verify config file has been loaded and System object is valid if (isConfigFileLoaded()) { // Monitor sensors for the voltage rails in the system system->monitorSensors(services); } // Notify sensors service that current sensor monitoring cycle has ended services.getSensors().endCycle(); } void Manager::sighupHandler(sdeventplus::source::Signal& /*sigSrc*/, const struct signalfd_siginfo* /*sigInfo*/) { // Reload the JSON configuration file loadConfigFile(); } void Manager::clearHardwareData() { // Clear any cached hardware presence data and VPD values services.getPresenceService().clearCache(); services.getVPD().clearCache(); // Verify config file has been loaded and System object is valid if (isConfigFileLoaded()) { // Clear any cached hardware data in the System object system->clearCache(); // Clear error history related to hardware devices in the System object system->clearErrorHistory(); } } fs::path Manager::findConfigFile() { // Build list of possible base file names std::vector fileNames{}; // Add possible file names based on compatible system types (if any) for (const std::string& systemType : compatibleSystemTypes) { // Look for file name that is entire system type + ".json" // Example: com.acme.Hardware.Chassis.Model.MegaServer.json fileNames.emplace_back(systemType + ".json"); // Look for file name that is last node of system type + ".json" // Example: MegaServer.json std::string::size_type pos = systemType.rfind('.'); if ((pos != std::string::npos) && ((systemType.size() - pos) > 1)) { fileNames.emplace_back(systemType.substr(pos + 1) + ".json"); } } // Add default file name for systems that don't use compatible interface fileNames.emplace_back(defaultConfigFileName); // Look for a config file with one of the possible base names for (const std::string& fileName : fileNames) { // Check if file exists in test directory fs::path pathName{testConfigFileDir / fileName}; if (fs::exists(pathName)) { return pathName; } // Check if file exists in standard directory pathName = standardConfigFileDir / fileName; if (fs::exists(pathName)) { return pathName; } } // No config file found; return empty path return fs::path{}; } bool Manager::isSystemPoweredOn() { bool isOn{false}; try { // Get D-Bus property that contains the current power state for // chassis0, which represents the entire system (all chassis) using namespace phosphor::power::util; auto service = getService(chassisStatePath, chassisStateIntf, bus); if (!service.empty()) { PowerState currentPowerState; getProperty(chassisStateIntf, chassisStateProp, chassisStatePath, service, bus, currentPowerState); if (currentPowerState == PowerState::On) { isOn = true; } } } catch (const std::exception& e) { // Current power state might not be available yet. The regulators // application can start before the power state is published on D-Bus. } return isOn; } void Manager::loadConfigFile() { try { // Find the absolute path to the config file fs::path pathName = findConfigFile(); if (!pathName.empty()) { // Log info message in journal; config file path is important services.getJournal().logInfo( "Loading configuration file " + pathName.string()); // Parse the config file std::vector> rules{}; std::vector> chassis{}; std::tie(rules, chassis) = config_file_parser::parse(pathName); // Store config file information in a new System object. The old // System object, if any, is automatically deleted. system = std::make_unique(std::move(rules), std::move(chassis)); } } catch (const std::exception& e) { // Log error messages in journal services.getJournal().logError(exception_utils::getMessages(e)); services.getJournal().logError("Unable to load configuration file"); // Log error services.getErrorLogging().logConfigFileError(Entry::Level::Error, services.getJournal()); } } void Manager::waitUntilConfigFileLoaded() { // If config file not loaded and list of compatible system types is empty if (!isConfigFileLoaded() && compatibleSystemTypes.empty()) { // Loop until compatible system types found or waited max amount of time auto start = std::chrono::system_clock::now(); std::chrono::system_clock::duration timeWaited{0}; while (compatibleSystemTypes.empty() && (timeWaited <= maxTimeToWaitForCompatTypes)) { // Try to find list of compatible system types. Force finder object // to re-find system types on D-Bus because we are not receiving // InterfacesAdded signals within this while loop. compatSysTypesFinder->refind(); if (compatibleSystemTypes.empty()) { // Not found; sleep 5 seconds using namespace std::chrono_literals; std::this_thread::sleep_for(5s); } timeWaited = std::chrono::system_clock::now() - start; } } } } // namespace phosphor::power::regulators