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