/** * 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 "rule.hpp" #include "utility.hpp" #include #include #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 compatibleIntf = "xyz.openbmc_project.Configuration.IBMCompatibleSystem"; constexpr auto compatibleNamesProp = "Names"; 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)} { // Subscribe to D-Bus interfacesAdded signal from Entity Manager. This // notifies us if the compatible interface becomes available later. std::string matchStr = sdbusplus::bus::match::rules::interfacesAdded() + sdbusplus::bus::match::rules::sender( "xyz.openbmc_project.EntityManager"); std::unique_ptr matchPtr = std::make_unique( bus, matchStr, std::bind(&Manager::interfacesAddedHandler, this, std::placeholders::_1)); signals.emplace_back(std::move(matchPtr)); // Try to find compatible system types using D-Bus compatible interface. // Note that it might not be supported on this system, or the service that // provides the interface might not be running yet. findCompatibleSystemTypes(); // Try to find and load the JSON configuration file 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::interfacesAddedHandler(sdbusplus::message_t& msg) { // Verify message is valid if (!msg) { return; } try { // Read object path for object that was created or had interface added sdbusplus::message::object_path objPath; msg.read(objPath); // Read the dictionary whose keys are interface names and whose values // are dictionaries containing the interface property names and values std::map>>> intfProp; msg.read(intfProp); // Find the compatible interface, if present auto itIntf = intfProp.find(compatibleIntf); if (itIntf != intfProp.cend()) { // Find the Names property of the compatible interface, if present auto itProp = itIntf->second.find(compatibleNamesProp); if (itProp != itIntf->second.cend()) { // Get value of Names property auto propValue = std::get<0>(itProp->second); if (!propValue.empty()) { // Store list of compatible system types compatibleSystemTypes = propValue; // Find and load JSON config file based on system types loadConfigFile(); } } } } catch (const std::exception&) { // Error trying to read interfacesAdded message. One possible cause // could be a property whose value is not a std::vector. } } 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::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(); } } void Manager::findCompatibleSystemTypes() { using namespace phosphor::power::util; try { // Query object mapper for object paths that implement the compatible // interface. Returns a map of object paths to a map of services names // to their interfaces. DbusSubtree subTree = getSubTree(bus, "/xyz/openbmc_project/inventory", compatibleIntf, 0); // Get the first object path auto objectIt = subTree.cbegin(); if (objectIt != subTree.cend()) { std::string objPath = objectIt->first; // Get the first service name auto serviceIt = objectIt->second.cbegin(); if (serviceIt != objectIt->second.cend()) { std::string service = serviceIt->first; if (!service.empty()) { // Get compatible system types property value getProperty(compatibleIntf, compatibleNamesProp, objPath, service, bus, compatibleSystemTypes); } } } } catch (const std::exception&) { // Compatible system types information is not available. The current // system might not support the interface, or the service that // implements the interface might not be running yet. } } 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) { // Replace all spaces and commas in system type name with underscores std::string fileName{systemType}; std::replace(fileName.begin(), fileName.end(), ' ', '_'); std::replace(fileName.begin(), fileName.end(), ',', '_'); // Append .json suffix and add to list fileName.append(".json"); fileNames.emplace_back(fileName); } // 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 findCompatibleSystemTypes(); if (!compatibleSystemTypes.empty()) { // Compatible system types found; try to load config file loadConfigFile(); } else { // 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