1 /** 2 * Copyright © 2020 IBM Corporation 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #include "manager.hpp" 18 19 #include "chassis.hpp" 20 #include "config_file_parser.hpp" 21 #include "exception_utils.hpp" 22 #include "format_utils.hpp" 23 #include "rule.hpp" 24 #include "utility.hpp" 25 26 #include <xyz/openbmc_project/Common/error.hpp> 27 #include <xyz/openbmc_project/State/Chassis/server.hpp> 28 29 #include <chrono> 30 #include <exception> 31 #include <functional> 32 #include <span> 33 #include <thread> 34 #include <tuple> 35 #include <utility> 36 37 namespace phosphor::power::regulators 38 { 39 40 namespace fs = std::filesystem; 41 42 constexpr auto busName = "xyz.openbmc_project.Power.Regulators"; 43 constexpr auto managerObjPath = "/xyz/openbmc_project/power/regulators/manager"; 44 constexpr auto chassisStatePath = "/xyz/openbmc_project/state/chassis0"; 45 constexpr auto chassisStateIntf = "xyz.openbmc_project.State.Chassis"; 46 constexpr auto chassisStateProp = "CurrentPowerState"; 47 constexpr std::chrono::minutes maxTimeToWaitForCompatTypes{5}; 48 49 using PowerState = 50 sdbusplus::xyz::openbmc_project::State::server::Chassis::PowerState; 51 52 /** 53 * Default configuration file name. This is used when the system does not 54 * implement the D-Bus compatible interface. 55 */ 56 constexpr auto defaultConfigFileName = "config.json"; 57 58 /** 59 * Standard configuration file directory. This directory is part of the 60 * firmware install image. It contains the standard version of the config file. 61 */ 62 const fs::path standardConfigFileDir{"/usr/share/phosphor-regulators"}; 63 64 /** 65 * Test configuration file directory. This directory can contain a test version 66 * of the config file. The test version will override the standard version. 67 */ 68 const fs::path testConfigFileDir{"/etc/phosphor-regulators"}; 69 Manager(sdbusplus::bus_t & bus,const sdeventplus::Event & event)70 Manager::Manager(sdbusplus::bus_t& bus, const sdeventplus::Event& event) : 71 ManagerObject{bus, managerObjPath}, bus{bus}, eventLoop{event}, 72 services{bus}, 73 phaseFaultTimer{event, std::bind(&Manager::phaseFaultTimerExpired, this)}, 74 sensorTimer{event, std::bind(&Manager::sensorTimerExpired, this)} 75 { 76 // Create object to find compatible system types for current system. 77 // Note that some systems do not provide this information. 78 compatSysTypesFinder = std::make_unique<util::CompatibleSystemTypesFinder>( 79 bus, std::bind_front(&Manager::compatibleSystemTypesFound, this)); 80 81 // If no system types found so far, try to load default config file 82 if (compatibleSystemTypes.empty()) 83 { 84 loadConfigFile(); 85 } 86 87 // Obtain D-Bus service name 88 bus.request_name(busName); 89 90 // If system is already powered on, enable monitoring 91 if (isSystemPoweredOn()) 92 { 93 monitor(true); 94 } 95 } 96 configure()97 void Manager::configure() 98 { 99 // Clear any cached data or error history related to hardware devices 100 clearHardwareData(); 101 102 // Wait until the config file has been loaded or hit max wait time 103 waitUntilConfigFileLoaded(); 104 105 // Verify config file has been loaded and System object is valid 106 if (isConfigFileLoaded()) 107 { 108 // Configure the regulator devices in the system 109 system->configure(services); 110 } 111 else 112 { 113 // Write error message to journal 114 services.getJournal().logError("Unable to configure regulator devices: " 115 "Configuration file not loaded"); 116 117 // Log critical error since regulators could not be configured. Could 118 // cause hardware damage if default regulator settings are very wrong. 119 services.getErrorLogging().logConfigFileError(Entry::Level::Critical, 120 services.getJournal()); 121 122 // Throw InternalFailure to propogate error status to D-Bus client 123 throw sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure{}; 124 } 125 } 126 monitor(bool enable)127 void Manager::monitor(bool enable) 128 { 129 // Check whether already in the requested monitoring state 130 if (enable == isMonitoringEnabled) 131 { 132 return; 133 } 134 135 isMonitoringEnabled = enable; 136 if (isMonitoringEnabled) 137 { 138 services.getJournal().logDebug("Monitoring enabled"); 139 140 // Restart phase fault detection timer with repeating 15 second interval 141 phaseFaultTimer.restart(std::chrono::seconds(15)); 142 143 // Restart sensor monitoring timer with repeating 1 second interval 144 sensorTimer.restart(std::chrono::seconds(1)); 145 146 // Enable sensors service; put all sensors in an active state 147 services.getSensors().enable(); 148 } 149 else 150 { 151 services.getJournal().logDebug("Monitoring disabled"); 152 153 // Disable timers 154 phaseFaultTimer.setEnabled(false); 155 sensorTimer.setEnabled(false); 156 157 // Disable sensors service; put all sensors in an inactive state 158 services.getSensors().disable(); 159 160 // Verify config file has been loaded and System object is valid 161 if (isConfigFileLoaded()) 162 { 163 // Close the regulator devices in the system. Monitoring is 164 // normally disabled because the system is being powered off. The 165 // devices should be closed in case hardware is removed or replaced 166 // while the system is powered off. 167 system->closeDevices(services); 168 } 169 } 170 } 171 compatibleSystemTypesFound(const std::vector<std::string> & types)172 void Manager::compatibleSystemTypesFound(const std::vector<std::string>& types) 173 { 174 // If we don't already have compatible system types 175 if (compatibleSystemTypes.empty()) 176 { 177 std::string typesStr = format_utils::toString(std::span{types}); 178 services.getJournal().logInfo( 179 std::format("Compatible system types found: {}", typesStr)); 180 181 // Store compatible system types 182 compatibleSystemTypes = types; 183 184 // Find and load JSON config file based on system types 185 loadConfigFile(); 186 } 187 } 188 phaseFaultTimerExpired()189 void Manager::phaseFaultTimerExpired() 190 { 191 // Verify config file has been loaded and System object is valid 192 if (isConfigFileLoaded()) 193 { 194 // Detect redundant phase faults in regulator devices in the system 195 system->detectPhaseFaults(services); 196 } 197 } 198 sensorTimerExpired()199 void Manager::sensorTimerExpired() 200 { 201 // Notify sensors service that a sensor monitoring cycle is starting 202 services.getSensors().startCycle(); 203 204 // Verify config file has been loaded and System object is valid 205 if (isConfigFileLoaded()) 206 { 207 // Monitor sensors for the voltage rails in the system 208 system->monitorSensors(services); 209 } 210 211 // Notify sensors service that current sensor monitoring cycle has ended 212 services.getSensors().endCycle(); 213 } 214 sighupHandler(sdeventplus::source::Signal &,const struct signalfd_siginfo *)215 void Manager::sighupHandler(sdeventplus::source::Signal& /*sigSrc*/, 216 const struct signalfd_siginfo* /*sigInfo*/) 217 { 218 // Reload the JSON configuration file 219 loadConfigFile(); 220 } 221 clearHardwareData()222 void Manager::clearHardwareData() 223 { 224 // Clear any cached hardware presence data and VPD values 225 services.getPresenceService().clearCache(); 226 services.getVPD().clearCache(); 227 228 // Verify config file has been loaded and System object is valid 229 if (isConfigFileLoaded()) 230 { 231 // Clear any cached hardware data in the System object 232 system->clearCache(); 233 234 // Clear error history related to hardware devices in the System object 235 system->clearErrorHistory(); 236 } 237 } 238 findConfigFile()239 fs::path Manager::findConfigFile() 240 { 241 // Build list of possible base file names 242 std::vector<std::string> fileNames{}; 243 244 // Add possible file names based on compatible system types (if any) 245 for (const std::string& systemType : compatibleSystemTypes) 246 { 247 // Look for file name that is entire system type + ".json" 248 // Example: com.acme.Hardware.Chassis.Model.MegaServer.json 249 fileNames.emplace_back(systemType + ".json"); 250 251 // Look for file name that is last node of system type + ".json" 252 // Example: MegaServer.json 253 std::string::size_type pos = systemType.rfind('.'); 254 if ((pos != std::string::npos) && ((systemType.size() - pos) > 1)) 255 { 256 fileNames.emplace_back(systemType.substr(pos + 1) + ".json"); 257 } 258 } 259 260 // Add default file name for systems that don't use compatible interface 261 fileNames.emplace_back(defaultConfigFileName); 262 263 // Look for a config file with one of the possible base names 264 for (const std::string& fileName : fileNames) 265 { 266 // Check if file exists in test directory 267 fs::path pathName{testConfigFileDir / fileName}; 268 if (fs::exists(pathName)) 269 { 270 return pathName; 271 } 272 273 // Check if file exists in standard directory 274 pathName = standardConfigFileDir / fileName; 275 if (fs::exists(pathName)) 276 { 277 return pathName; 278 } 279 } 280 281 // No config file found; return empty path 282 return fs::path{}; 283 } 284 isSystemPoweredOn()285 bool Manager::isSystemPoweredOn() 286 { 287 bool isOn{false}; 288 289 try 290 { 291 // Get D-Bus property that contains the current power state for 292 // chassis0, which represents the entire system (all chassis) 293 using namespace phosphor::power::util; 294 auto service = getService(chassisStatePath, chassisStateIntf, bus); 295 if (!service.empty()) 296 { 297 PowerState currentPowerState; 298 getProperty(chassisStateIntf, chassisStateProp, chassisStatePath, 299 service, bus, currentPowerState); 300 if (currentPowerState == PowerState::On) 301 { 302 isOn = true; 303 } 304 } 305 } 306 catch (const std::exception& e) 307 { 308 // Current power state might not be available yet. The regulators 309 // application can start before the power state is published on D-Bus. 310 } 311 312 return isOn; 313 } 314 loadConfigFile()315 void Manager::loadConfigFile() 316 { 317 try 318 { 319 // Find the absolute path to the config file 320 fs::path pathName = findConfigFile(); 321 if (!pathName.empty()) 322 { 323 // Log info message in journal; config file path is important 324 services.getJournal().logInfo( 325 "Loading configuration file " + pathName.string()); 326 327 // Parse the config file 328 std::vector<std::unique_ptr<Rule>> rules{}; 329 std::vector<std::unique_ptr<Chassis>> chassis{}; 330 std::tie(rules, chassis) = config_file_parser::parse(pathName); 331 332 // Store config file information in a new System object. The old 333 // System object, if any, is automatically deleted. 334 system = 335 std::make_unique<System>(std::move(rules), std::move(chassis)); 336 } 337 } 338 catch (const std::exception& e) 339 { 340 // Log error messages in journal 341 services.getJournal().logError(exception_utils::getMessages(e)); 342 services.getJournal().logError("Unable to load configuration file"); 343 344 // Log error 345 services.getErrorLogging().logConfigFileError(Entry::Level::Error, 346 services.getJournal()); 347 } 348 } 349 waitUntilConfigFileLoaded()350 void Manager::waitUntilConfigFileLoaded() 351 { 352 // If config file not loaded and list of compatible system types is empty 353 if (!isConfigFileLoaded() && compatibleSystemTypes.empty()) 354 { 355 // Loop until compatible system types found or waited max amount of time 356 auto start = std::chrono::system_clock::now(); 357 std::chrono::system_clock::duration timeWaited{0}; 358 while (compatibleSystemTypes.empty() && 359 (timeWaited <= maxTimeToWaitForCompatTypes)) 360 { 361 // Try to find list of compatible system types. Force finder object 362 // to re-find system types on D-Bus because we are not receiving 363 // InterfacesAdded signals within this while loop. 364 compatSysTypesFinder->refind(); 365 if (compatibleSystemTypes.empty()) 366 { 367 // Not found; sleep 5 seconds 368 using namespace std::chrono_literals; 369 std::this_thread::sleep_for(5s); 370 } 371 timeWaited = std::chrono::system_clock::now() - start; 372 } 373 } 374 } 375 376 } // namespace phosphor::power::regulators 377