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 27 #include <algorithm> 28 #include <chrono> 29 #include <exception> 30 #include <functional> 31 #include <map> 32 #include <tuple> 33 #include <utility> 34 #include <variant> 35 36 namespace phosphor::power::regulators 37 { 38 39 namespace fs = std::filesystem; 40 41 constexpr auto busName = "xyz.openbmc_project.Power.Regulators"; 42 constexpr auto managerObjPath = "/xyz/openbmc_project/power/regulators/manager"; 43 constexpr auto compatibleIntf = 44 "xyz.openbmc_project.Configuration.IBMCompatibleSystem"; 45 constexpr auto compatibleNamesProp = "Names"; 46 47 /** 48 * Default configuration file name. This is used when the system does not 49 * implement the D-Bus compatible interface. 50 */ 51 constexpr auto defaultConfigFileName = "config.json"; 52 53 /** 54 * Standard configuration file directory. This directory is part of the 55 * firmware install image. It contains the standard version of the config file. 56 */ 57 const fs::path standardConfigFileDir{"/usr/share/phosphor-regulators"}; 58 59 /** 60 * Test configuration file directory. This directory can contain a test version 61 * of the config file. The test version will override the standard version. 62 */ 63 const fs::path testConfigFileDir{"/etc/phosphor-regulators"}; 64 65 Manager::Manager(sdbusplus::bus::bus& bus, const sdeventplus::Event& event) : 66 ManagerObject{bus, managerObjPath, true}, bus{bus}, eventLoop{event}, 67 services{bus} 68 { 69 // Subscribe to D-Bus interfacesAdded signal from Entity Manager. This 70 // notifies us if the compatible interface becomes available later. 71 std::string matchStr = sdbusplus::bus::match::rules::interfacesAdded() + 72 sdbusplus::bus::match::rules::sender( 73 "xyz.openbmc_project.EntityManager"); 74 std::unique_ptr<sdbusplus::server::match::match> matchPtr = 75 std::make_unique<sdbusplus::server::match::match>( 76 bus, matchStr, 77 std::bind(&Manager::interfacesAddedHandler, this, 78 std::placeholders::_1)); 79 signals.emplace_back(std::move(matchPtr)); 80 81 // Try to find compatible system types using D-Bus compatible interface. 82 // Note that it might not be supported on this system, or the service that 83 // provides the interface might not be running yet. 84 findCompatibleSystemTypes(); 85 86 // Try to find and load the JSON configuration file 87 loadConfigFile(); 88 89 // Obtain dbus service name 90 bus.request_name(busName); 91 } 92 93 void Manager::configure() 94 { 95 // Clear any cached data or error history related to hardware devices 96 clearHardwareData(); 97 98 // Verify System object exists; this means config file has been loaded 99 if (system) 100 { 101 // Configure the regulator devices in the system 102 system->configure(services); 103 } 104 else 105 { 106 // Write error message to journal 107 services.getJournal().logError("Unable to configure regulator devices: " 108 "Configuration file not loaded"); 109 110 // Log critical error since regulators could not be configured. Could 111 // cause hardware damage if default regulator settings are very wrong. 112 services.getErrorLogging().logConfigFileError(Entry::Level::Critical, 113 services.getJournal()); 114 115 // Throw InternalFailure to propogate error status to D-Bus client 116 throw sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure{}; 117 } 118 } 119 120 void Manager::interfacesAddedHandler(sdbusplus::message::message& msg) 121 { 122 // Verify message is valid 123 if (!msg) 124 { 125 return; 126 } 127 128 try 129 { 130 // Read object path for object that was created or had interface added 131 sdbusplus::message::object_path objPath; 132 msg.read(objPath); 133 134 // Read the dictionary whose keys are interface names and whose values 135 // are dictionaries containing the interface property names and values 136 std::map<std::string, 137 std::map<std::string, std::variant<std::vector<std::string>>>> 138 intfProp; 139 msg.read(intfProp); 140 141 // Find the compatible interface, if present 142 auto itIntf = intfProp.find(compatibleIntf); 143 if (itIntf != intfProp.cend()) 144 { 145 // Find the Names property of the compatible interface, if present 146 auto itProp = itIntf->second.find(compatibleNamesProp); 147 if (itProp != itIntf->second.cend()) 148 { 149 // Get value of Names property 150 auto propValue = std::get<0>(itProp->second); 151 if (!propValue.empty()) 152 { 153 // Store list of compatible system types 154 compatibleSystemTypes = propValue; 155 156 // Find and load JSON config file based on system types 157 loadConfigFile(); 158 } 159 } 160 } 161 } 162 catch (const std::exception&) 163 { 164 // Error trying to read interfacesAdded message. One possible cause 165 // could be a property whose value is not a std::vector<std::string>. 166 } 167 } 168 169 void Manager::monitor(bool enable) 170 { 171 if (enable) 172 { 173 /* Temporarily comment out until monitoring is supported. 174 Timer timer(eventLoop, std::bind(&Manager::timerExpired, this)); 175 // Set timer as a repeating 1sec timer 176 timer.restart(std::chrono::milliseconds(1000)); 177 timers.emplace_back(std::move(timer)); 178 */ 179 } 180 else 181 { 182 /* Temporarily comment out until monitoring is supported. 183 // Delete all timers to disable monitoring 184 timers.clear(); 185 */ 186 187 // Verify System object exists; this means config file has been loaded 188 if (system) 189 { 190 // Close the regulator devices in the system. Monitoring is 191 // normally disabled because the system is being powered off. The 192 // devices should be closed in case hardware is removed or replaced 193 // while the system is powered off. 194 system->closeDevices(services); 195 } 196 } 197 } 198 199 void Manager::sighupHandler(sdeventplus::source::Signal& /*sigSrc*/, 200 const struct signalfd_siginfo* /*sigInfo*/) 201 { 202 // Reload the JSON configuration file 203 loadConfigFile(); 204 } 205 206 void Manager::timerExpired() 207 { 208 // TODO Analyze, refresh sensor status, and 209 // collect/update telemetry for each regulator 210 } 211 212 void Manager::clearHardwareData() 213 { 214 // Clear any cached hardware presence data and VPD values 215 services.getPresenceService().clearCache(); 216 services.getVPD().clearCache(); 217 218 // Verify System object exists; this means config file has been loaded 219 if (system) 220 { 221 // Clear any cached hardware data in the System object 222 system->clearCache(); 223 } 224 225 // TODO: Clear error history related to hardware devices 226 } 227 228 void Manager::findCompatibleSystemTypes() 229 { 230 using namespace phosphor::power::util; 231 232 try 233 { 234 // Query object mapper for object paths that implement the compatible 235 // interface. Returns a map of object paths to a map of services names 236 // to their interfaces. 237 DbusSubtree subTree = getSubTree(bus, "/xyz/openbmc_project/inventory", 238 compatibleIntf, 0); 239 240 // Get the first object path 241 auto objectIt = subTree.cbegin(); 242 if (objectIt != subTree.cend()) 243 { 244 std::string objPath = objectIt->first; 245 246 // Get the first service name 247 auto serviceIt = objectIt->second.cbegin(); 248 if (serviceIt != objectIt->second.cend()) 249 { 250 std::string service = serviceIt->first; 251 if (!service.empty()) 252 { 253 // Get compatible system types property value 254 getProperty(compatibleIntf, compatibleNamesProp, objPath, 255 service, bus, compatibleSystemTypes); 256 } 257 } 258 } 259 } 260 catch (const std::exception&) 261 { 262 // Compatible system types information is not available. The current 263 // system might not support the interface, or the service that 264 // implements the interface might not be running yet. 265 } 266 } 267 268 fs::path Manager::findConfigFile() 269 { 270 // Build list of possible base file names 271 std::vector<std::string> fileNames{}; 272 273 // Add possible file names based on compatible system types (if any) 274 for (const std::string& systemType : compatibleSystemTypes) 275 { 276 // Replace all spaces and commas in system type name with underscores 277 std::string fileName{systemType}; 278 std::replace(fileName.begin(), fileName.end(), ' ', '_'); 279 std::replace(fileName.begin(), fileName.end(), ',', '_'); 280 281 // Append .json suffix and add to list 282 fileName.append(".json"); 283 fileNames.emplace_back(fileName); 284 } 285 286 // Add default file name for systems that don't use compatible interface 287 fileNames.emplace_back(defaultConfigFileName); 288 289 // Look for a config file with one of the possible base names 290 for (const std::string& fileName : fileNames) 291 { 292 // Check if file exists in test directory 293 fs::path pathName{testConfigFileDir / fileName}; 294 if (fs::exists(pathName)) 295 { 296 return pathName; 297 } 298 299 // Check if file exists in standard directory 300 pathName = standardConfigFileDir / fileName; 301 if (fs::exists(pathName)) 302 { 303 return pathName; 304 } 305 } 306 307 // No config file found; return empty path 308 return fs::path{}; 309 } 310 311 void Manager::loadConfigFile() 312 { 313 try 314 { 315 // Find the absolute path to the config file 316 fs::path pathName = findConfigFile(); 317 if (!pathName.empty()) 318 { 319 // Log info message in journal; config file path is important 320 services.getJournal().logInfo("Loading configuration file " + 321 pathName.string()); 322 323 // Parse the config file 324 std::vector<std::unique_ptr<Rule>> rules{}; 325 std::vector<std::unique_ptr<Chassis>> chassis{}; 326 std::tie(rules, chassis) = config_file_parser::parse(pathName); 327 328 // Store config file information in a new System object. The old 329 // System object, if any, is automatically deleted. 330 system = 331 std::make_unique<System>(std::move(rules), std::move(chassis)); 332 } 333 } 334 catch (const std::exception& e) 335 { 336 // Log error messages in journal 337 services.getJournal().logError(exception_utils::getMessages(e)); 338 services.getJournal().logError("Unable to load configuration file"); 339 340 // Log error 341 services.getErrorLogging().logConfigFileError(Entry::Level::Error, 342 services.getJournal()); 343 } 344 } 345 346 } // namespace phosphor::power::regulators 347