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 "rule.hpp" 23 #include "utility.hpp" 24 25 #include <xyz/openbmc_project/Common/error.hpp> 26 #include <xyz/openbmc_project/State/Chassis/server.hpp> 27 28 #include <algorithm> 29 #include <chrono> 30 #include <exception> 31 #include <functional> 32 #include <map> 33 #include <thread> 34 #include <tuple> 35 #include <utility> 36 #include <variant> 37 38 namespace phosphor::power::regulators 39 { 40 41 namespace fs = std::filesystem; 42 43 constexpr auto busName = "xyz.openbmc_project.Power.Regulators"; 44 constexpr auto managerObjPath = "/xyz/openbmc_project/power/regulators/manager"; 45 constexpr auto compatibleIntf = 46 "xyz.openbmc_project.Configuration.IBMCompatibleSystem"; 47 constexpr auto compatibleNamesProp = "Names"; 48 constexpr auto chassisStatePath = "/xyz/openbmc_project/state/chassis0"; 49 constexpr auto chassisStateIntf = "xyz.openbmc_project.State.Chassis"; 50 constexpr auto chassisStateProp = "CurrentPowerState"; 51 constexpr std::chrono::minutes maxTimeToWaitForCompatTypes{5}; 52 53 using PowerState = 54 sdbusplus::xyz::openbmc_project::State::server::Chassis::PowerState; 55 56 /** 57 * Default configuration file name. This is used when the system does not 58 * implement the D-Bus compatible interface. 59 */ 60 constexpr auto defaultConfigFileName = "config.json"; 61 62 /** 63 * Standard configuration file directory. This directory is part of the 64 * firmware install image. It contains the standard version of the config file. 65 */ 66 const fs::path standardConfigFileDir{"/usr/share/phosphor-regulators"}; 67 68 /** 69 * Test configuration file directory. This directory can contain a test version 70 * of the config file. The test version will override the standard version. 71 */ 72 const fs::path testConfigFileDir{"/etc/phosphor-regulators"}; 73 74 Manager::Manager(sdbusplus::bus::bus& bus, const sdeventplus::Event& event) : 75 ManagerObject{bus, managerObjPath, true}, bus{bus}, eventLoop{event}, 76 services{bus}, phaseFaultTimer{event, 77 std::bind(&Manager::phaseFaultTimerExpired, 78 this)}, 79 sensorTimer{event, std::bind(&Manager::sensorTimerExpired, this)} 80 { 81 // Subscribe to D-Bus interfacesAdded signal from Entity Manager. This 82 // notifies us if the compatible interface becomes available later. 83 std::string matchStr = sdbusplus::bus::match::rules::interfacesAdded() + 84 sdbusplus::bus::match::rules::sender( 85 "xyz.openbmc_project.EntityManager"); 86 std::unique_ptr<sdbusplus::server::match::match> matchPtr = 87 std::make_unique<sdbusplus::server::match::match>( 88 bus, matchStr, 89 std::bind(&Manager::interfacesAddedHandler, this, 90 std::placeholders::_1)); 91 signals.emplace_back(std::move(matchPtr)); 92 93 // Try to find compatible system types using D-Bus compatible interface. 94 // Note that it might not be supported on this system, or the service that 95 // provides the interface might not be running yet. 96 findCompatibleSystemTypes(); 97 98 // Try to find and load the JSON configuration file 99 loadConfigFile(); 100 101 // Obtain D-Bus service name 102 bus.request_name(busName); 103 104 // If system is already powered on, enable monitoring 105 if (isSystemPoweredOn()) 106 { 107 monitor(true); 108 } 109 } 110 111 void Manager::configure() 112 { 113 // Clear any cached data or error history related to hardware devices 114 clearHardwareData(); 115 116 // Wait until the config file has been loaded or hit max wait time 117 waitUntilConfigFileLoaded(); 118 119 // Verify config file has been loaded and System object is valid 120 if (isConfigFileLoaded()) 121 { 122 // Configure the regulator devices in the system 123 system->configure(services); 124 } 125 else 126 { 127 // Write error message to journal 128 services.getJournal().logError("Unable to configure regulator devices: " 129 "Configuration file not loaded"); 130 131 // Log critical error since regulators could not be configured. Could 132 // cause hardware damage if default regulator settings are very wrong. 133 services.getErrorLogging().logConfigFileError(Entry::Level::Critical, 134 services.getJournal()); 135 136 // Throw InternalFailure to propogate error status to D-Bus client 137 throw sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure{}; 138 } 139 } 140 141 void Manager::interfacesAddedHandler(sdbusplus::message::message& msg) 142 { 143 // Verify message is valid 144 if (!msg) 145 { 146 return; 147 } 148 149 try 150 { 151 // Read object path for object that was created or had interface added 152 sdbusplus::message::object_path objPath; 153 msg.read(objPath); 154 155 // Read the dictionary whose keys are interface names and whose values 156 // are dictionaries containing the interface property names and values 157 std::map<std::string, 158 std::map<std::string, std::variant<std::vector<std::string>>>> 159 intfProp; 160 msg.read(intfProp); 161 162 // Find the compatible interface, if present 163 auto itIntf = intfProp.find(compatibleIntf); 164 if (itIntf != intfProp.cend()) 165 { 166 // Find the Names property of the compatible interface, if present 167 auto itProp = itIntf->second.find(compatibleNamesProp); 168 if (itProp != itIntf->second.cend()) 169 { 170 // Get value of Names property 171 auto propValue = std::get<0>(itProp->second); 172 if (!propValue.empty()) 173 { 174 // Store list of compatible system types 175 compatibleSystemTypes = propValue; 176 177 // Find and load JSON config file based on system types 178 loadConfigFile(); 179 } 180 } 181 } 182 } 183 catch (const std::exception&) 184 { 185 // Error trying to read interfacesAdded message. One possible cause 186 // could be a property whose value is not a std::vector<std::string>. 187 } 188 } 189 190 void Manager::monitor(bool enable) 191 { 192 // Check whether already in the requested monitoring state 193 if (enable == isMonitoringEnabled) 194 { 195 return; 196 } 197 198 isMonitoringEnabled = enable; 199 if (isMonitoringEnabled) 200 { 201 services.getJournal().logDebug("Monitoring enabled"); 202 203 // Restart phase fault detection timer with repeating 15 second interval 204 phaseFaultTimer.restart(std::chrono::seconds(15)); 205 206 // Restart sensor monitoring timer with repeating 1 second interval 207 sensorTimer.restart(std::chrono::seconds(1)); 208 209 // Enable sensors service; put all sensors in an active state 210 services.getSensors().enable(); 211 } 212 else 213 { 214 services.getJournal().logDebug("Monitoring disabled"); 215 216 // Disable timers 217 phaseFaultTimer.setEnabled(false); 218 sensorTimer.setEnabled(false); 219 220 // Disable sensors service; put all sensors in an inactive state 221 services.getSensors().disable(); 222 223 // Verify config file has been loaded and System object is valid 224 if (isConfigFileLoaded()) 225 { 226 // Close the regulator devices in the system. Monitoring is 227 // normally disabled because the system is being powered off. The 228 // devices should be closed in case hardware is removed or replaced 229 // while the system is powered off. 230 system->closeDevices(services); 231 } 232 } 233 } 234 235 void Manager::phaseFaultTimerExpired() 236 { 237 // Verify config file has been loaded and System object is valid 238 if (isConfigFileLoaded()) 239 { 240 // Detect redundant phase faults in regulator devices in the system 241 system->detectPhaseFaults(services); 242 } 243 } 244 245 void Manager::sensorTimerExpired() 246 { 247 // Notify sensors service that a sensor monitoring cycle is starting 248 services.getSensors().startCycle(); 249 250 // Verify config file has been loaded and System object is valid 251 if (isConfigFileLoaded()) 252 { 253 // Monitor sensors for the voltage rails in the system 254 system->monitorSensors(services); 255 } 256 257 // Notify sensors service that current sensor monitoring cycle has ended 258 services.getSensors().endCycle(); 259 } 260 261 void Manager::sighupHandler(sdeventplus::source::Signal& /*sigSrc*/, 262 const struct signalfd_siginfo* /*sigInfo*/) 263 { 264 // Reload the JSON configuration file 265 loadConfigFile(); 266 } 267 268 void Manager::clearHardwareData() 269 { 270 // Clear any cached hardware presence data and VPD values 271 services.getPresenceService().clearCache(); 272 services.getVPD().clearCache(); 273 274 // Verify config file has been loaded and System object is valid 275 if (isConfigFileLoaded()) 276 { 277 // Clear any cached hardware data in the System object 278 system->clearCache(); 279 280 // Clear error history related to hardware devices in the System object 281 system->clearErrorHistory(); 282 } 283 } 284 285 void Manager::findCompatibleSystemTypes() 286 { 287 using namespace phosphor::power::util; 288 289 try 290 { 291 // Query object mapper for object paths that implement the compatible 292 // interface. Returns a map of object paths to a map of services names 293 // to their interfaces. 294 DbusSubtree subTree = getSubTree(bus, "/xyz/openbmc_project/inventory", 295 compatibleIntf, 0); 296 297 // Get the first object path 298 auto objectIt = subTree.cbegin(); 299 if (objectIt != subTree.cend()) 300 { 301 std::string objPath = objectIt->first; 302 303 // Get the first service name 304 auto serviceIt = objectIt->second.cbegin(); 305 if (serviceIt != objectIt->second.cend()) 306 { 307 std::string service = serviceIt->first; 308 if (!service.empty()) 309 { 310 // Get compatible system types property value 311 getProperty(compatibleIntf, compatibleNamesProp, objPath, 312 service, bus, compatibleSystemTypes); 313 } 314 } 315 } 316 } 317 catch (const std::exception&) 318 { 319 // Compatible system types information is not available. The current 320 // system might not support the interface, or the service that 321 // implements the interface might not be running yet. 322 } 323 } 324 325 fs::path Manager::findConfigFile() 326 { 327 // Build list of possible base file names 328 std::vector<std::string> fileNames{}; 329 330 // Add possible file names based on compatible system types (if any) 331 for (const std::string& systemType : compatibleSystemTypes) 332 { 333 // Replace all spaces and commas in system type name with underscores 334 std::string fileName{systemType}; 335 std::replace(fileName.begin(), fileName.end(), ' ', '_'); 336 std::replace(fileName.begin(), fileName.end(), ',', '_'); 337 338 // Append .json suffix and add to list 339 fileName.append(".json"); 340 fileNames.emplace_back(fileName); 341 } 342 343 // Add default file name for systems that don't use compatible interface 344 fileNames.emplace_back(defaultConfigFileName); 345 346 // Look for a config file with one of the possible base names 347 for (const std::string& fileName : fileNames) 348 { 349 // Check if file exists in test directory 350 fs::path pathName{testConfigFileDir / fileName}; 351 if (fs::exists(pathName)) 352 { 353 return pathName; 354 } 355 356 // Check if file exists in standard directory 357 pathName = standardConfigFileDir / fileName; 358 if (fs::exists(pathName)) 359 { 360 return pathName; 361 } 362 } 363 364 // No config file found; return empty path 365 return fs::path{}; 366 } 367 368 bool Manager::isSystemPoweredOn() 369 { 370 bool isOn{false}; 371 372 try 373 { 374 // Get D-Bus property that contains the current power state for 375 // chassis0, which represents the entire system (all chassis) 376 using namespace phosphor::power::util; 377 auto service = getService(chassisStatePath, chassisStateIntf, bus); 378 if (!service.empty()) 379 { 380 PowerState currentPowerState; 381 getProperty(chassisStateIntf, chassisStateProp, chassisStatePath, 382 service, bus, currentPowerState); 383 if (currentPowerState == PowerState::On) 384 { 385 isOn = true; 386 } 387 } 388 } 389 catch (const std::exception& e) 390 { 391 // Current power state might not be available yet. The regulators 392 // application can start before the power state is published on D-Bus. 393 } 394 395 return isOn; 396 } 397 398 void Manager::loadConfigFile() 399 { 400 try 401 { 402 // Find the absolute path to the config file 403 fs::path pathName = findConfigFile(); 404 if (!pathName.empty()) 405 { 406 // Log info message in journal; config file path is important 407 services.getJournal().logInfo("Loading configuration file " + 408 pathName.string()); 409 410 // Parse the config file 411 std::vector<std::unique_ptr<Rule>> rules{}; 412 std::vector<std::unique_ptr<Chassis>> chassis{}; 413 std::tie(rules, chassis) = config_file_parser::parse(pathName); 414 415 // Store config file information in a new System object. The old 416 // System object, if any, is automatically deleted. 417 system = 418 std::make_unique<System>(std::move(rules), std::move(chassis)); 419 } 420 } 421 catch (const std::exception& e) 422 { 423 // Log error messages in journal 424 services.getJournal().logError(exception_utils::getMessages(e)); 425 services.getJournal().logError("Unable to load configuration file"); 426 427 // Log error 428 services.getErrorLogging().logConfigFileError(Entry::Level::Error, 429 services.getJournal()); 430 } 431 } 432 433 void Manager::waitUntilConfigFileLoaded() 434 { 435 // If config file not loaded and list of compatible system types is empty 436 if (!isConfigFileLoaded() && compatibleSystemTypes.empty()) 437 { 438 // Loop until compatible system types found or waited max amount of time 439 auto start = std::chrono::system_clock::now(); 440 std::chrono::system_clock::duration timeWaited{0}; 441 while (compatibleSystemTypes.empty() && 442 (timeWaited <= maxTimeToWaitForCompatTypes)) 443 { 444 // Try to find list of compatible system types 445 findCompatibleSystemTypes(); 446 if (!compatibleSystemTypes.empty()) 447 { 448 // Compatible system types found; try to load config file 449 loadConfigFile(); 450 } 451 else 452 { 453 // Sleep 5 seconds 454 using namespace std::chrono_literals; 455 std::this_thread::sleep_for(5s); 456 } 457 timeWaited = std::chrono::system_clock::now() - start; 458 } 459 } 460 } 461 462 } // namespace phosphor::power::regulators 463