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