1 /* 2 // Copyright (c) 2018 Intel 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 <chrono> 18 #include <conf.hpp> 19 #include <dbus/util.hpp> 20 #include <functional> 21 #include <iostream> 22 #include <sdbusplus/bus.hpp> 23 #include <sdbusplus/bus/match.hpp> 24 #include <sdbusplus/exception.hpp> 25 #include <set> 26 #include <thread> 27 #include <unordered_map> 28 29 static constexpr bool DEBUG = false; // enable to print found configuration 30 31 std::map<std::string, struct sensor> SensorConfig = {}; 32 std::map<int64_t, PIDConf> ZoneConfig = {}; 33 std::map<int64_t, struct zone> ZoneDetailsConfig = {}; 34 35 constexpr const char* pidConfigurationInterface = 36 "xyz.openbmc_project.Configuration.Pid"; 37 constexpr const char* objectManagerInterface = 38 "org.freedesktop.DBus.ObjectManager"; 39 constexpr const char* pidZoneConfigurationInterface = 40 "xyz.openbmc_project.Configuration.Pid.Zone"; 41 constexpr const char* stepwiseConfigurationInterface = 42 "xyz.openbmc_project.Configuration.Stepwise"; 43 constexpr const char* sensorInterface = "xyz.openbmc_project.Sensor.Value"; 44 constexpr const char* pwmInterface = "xyz.openbmc_project.Control.FanPwm"; 45 46 namespace dbus_configuration 47 { 48 49 bool findSensor(const std::unordered_map<std::string, std::string>& sensors, 50 const std::string& search, 51 std::pair<std::string, std::string>& sensor) 52 { 53 for (const auto& s : sensors) 54 { 55 if (s.first.find(search) != std::string::npos) 56 { 57 sensor = s; 58 return true; 59 } 60 } 61 return false; 62 } 63 64 // this function prints the configuration into a form similar to the cpp 65 // generated code to help in verification, should be turned off during normal 66 // use 67 void debugPrint(void) 68 { 69 // print sensor config 70 std::cout << "sensor config:\n"; 71 std::cout << "{\n"; 72 for (auto& pair : SensorConfig) 73 { 74 75 std::cout << "\t{" << pair.first << ",\n\t\t{"; 76 std::cout << pair.second.type << ", "; 77 std::cout << pair.second.readpath << ", "; 78 std::cout << pair.second.writepath << ", "; 79 std::cout << pair.second.min << ", "; 80 std::cout << pair.second.max << ", "; 81 std::cout << pair.second.timeout << "},\n\t},\n"; 82 } 83 std::cout << "}\n\n"; 84 std::cout << "ZoneDetailsConfig\n"; 85 std::cout << "{\n"; 86 for (auto& zone : ZoneDetailsConfig) 87 { 88 std::cout << "\t{" << zone.first << ",\n"; 89 std::cout << "\t\t{" << zone.second.minthermalrpm << ", "; 90 std::cout << zone.second.failsafepercent << "}\n\t},\n"; 91 } 92 std::cout << "}\n\n"; 93 std::cout << "ZoneConfig\n"; 94 std::cout << "{\n"; 95 for (auto& zone : ZoneConfig) 96 { 97 std::cout << "\t{" << zone.first << "\n"; 98 for (auto& pidconf : zone.second) 99 { 100 std::cout << "\t\t{" << pidconf.first << ",\n"; 101 std::cout << "\t\t\t{" << pidconf.second.type << ",\n"; 102 std::cout << "\t\t\t{"; 103 for (auto& input : pidconf.second.inputs) 104 { 105 std::cout << "\n\t\t\t" << input << ",\n"; 106 } 107 std::cout << "\t\t\t}\n"; 108 std::cout << "\t\t\t" << pidconf.second.setpoint << ",\n"; 109 std::cout << "\t\t\t{" << pidconf.second.pidInfo.ts << ",\n"; 110 std::cout << "\t\t\t" << pidconf.second.pidInfo.p_c << ",\n"; 111 std::cout << "\t\t\t" << pidconf.second.pidInfo.i_c << ",\n"; 112 std::cout << "\t\t\t" << pidconf.second.pidInfo.ff_off << ",\n"; 113 std::cout << "\t\t\t" << pidconf.second.pidInfo.ff_gain << ",\n"; 114 std::cout << "\t\t\t{" << pidconf.second.pidInfo.i_lim.min << "," 115 << pidconf.second.pidInfo.i_lim.max << "},\n"; 116 std::cout << "\t\t\t{" << pidconf.second.pidInfo.out_lim.min << "," 117 << pidconf.second.pidInfo.out_lim.max << "},\n"; 118 std::cout << "\t\t\t" << pidconf.second.pidInfo.slew_neg << ",\n"; 119 std::cout << "\t\t\t" << pidconf.second.pidInfo.slew_pos << ",\n"; 120 std::cout << "\t\t\t}\n\t\t}\n"; 121 } 122 std::cout << "\t},\n"; 123 } 124 std::cout << "}\n\n"; 125 } 126 127 void init(sdbusplus::bus::bus& bus) 128 { 129 using DbusVariantType = 130 sdbusplus::message::variant<uint64_t, int64_t, double, std::string, 131 std::vector<std::string>, 132 std::vector<double>>; 133 134 using ManagedObjectType = std::unordered_map< 135 sdbusplus::message::object_path, 136 std::unordered_map<std::string, 137 std::unordered_map<std::string, DbusVariantType>>>; 138 139 // install watch for properties changed 140 std::function<void(sdbusplus::message::message & message)> eventHandler = 141 [](const sdbusplus::message::message&) { 142 // do a brief sleep as we tend to get a bunch of these events at 143 // once 144 std::this_thread::sleep_for(std::chrono::seconds(5)); 145 std::cout << "New configuration detected, restarting\n."; 146 std::exit(EXIT_SUCCESS); // service file should make us restart 147 }; 148 149 static sdbusplus::bus::match::match match( 150 bus, 151 "type='signal',member='PropertiesChanged',arg0namespace='" + 152 std::string(pidConfigurationInterface) + "'", 153 eventHandler); 154 155 auto mapper = 156 bus.new_method_call("xyz.openbmc_project.ObjectMapper", 157 "/xyz/openbmc_project/object_mapper", 158 "xyz.openbmc_project.ObjectMapper", "GetSubTree"); 159 mapper.append("", 0, 160 std::array<const char*, 6>{objectManagerInterface, 161 pidConfigurationInterface, 162 pidZoneConfigurationInterface, 163 stepwiseConfigurationInterface, 164 sensorInterface, pwmInterface}); 165 std::unordered_map< 166 std::string, std::unordered_map<std::string, std::vector<std::string>>> 167 respData; 168 try 169 { 170 auto resp = bus.call(mapper); 171 if (resp.is_method_error()) 172 { 173 throw std::runtime_error("ObjectMapper Call Failure"); 174 } 175 resp.read(respData); 176 } 177 catch (sdbusplus::exception_t&) 178 { 179 // can't do anything without mapper call data 180 throw std::runtime_error("ObjectMapper Call Failure"); 181 } 182 183 if (respData.empty()) 184 { 185 // can't do anything without mapper call data 186 throw std::runtime_error("No configuration data available from Mapper"); 187 } 188 // create a map of pair of <has pid configuration, ObjectManager path> 189 std::unordered_map<std::string, std::pair<bool, std::string>> owners; 190 // and a map of <path, interface> for sensors 191 std::unordered_map<std::string, std::string> sensors; 192 for (const auto& objectPair : respData) 193 { 194 for (const auto& ownerPair : objectPair.second) 195 { 196 auto& owner = owners[ownerPair.first]; 197 for (const std::string& interface : ownerPair.second) 198 { 199 200 if (interface == objectManagerInterface) 201 { 202 owner.second = objectPair.first; 203 } 204 if (interface == pidConfigurationInterface || 205 interface == pidZoneConfigurationInterface || 206 interface == stepwiseConfigurationInterface) 207 { 208 owner.first = true; 209 } 210 if (interface == sensorInterface || interface == pwmInterface) 211 { 212 // we're not interested in pwm sensors, just pwm control 213 if (interface == sensorInterface && 214 objectPair.first.find("pwm") != std::string::npos) 215 { 216 continue; 217 } 218 sensors[objectPair.first] = interface; 219 } 220 } 221 } 222 } 223 ManagedObjectType configurations; 224 for (const auto& owner : owners) 225 { 226 // skip if no pid configuration (means probably a sensor) 227 if (!owner.second.first) 228 { 229 continue; 230 } 231 auto endpoint = bus.new_method_call( 232 owner.first.c_str(), owner.second.second.c_str(), 233 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 234 ManagedObjectType configuration; 235 try 236 { 237 auto responce = bus.call(endpoint); 238 if (responce.is_method_error()) 239 { 240 throw std::runtime_error("Error getting managed objects from " + 241 owner.first); 242 } 243 responce.read(configuration); 244 } 245 catch (sdbusplus::exception_t&) 246 { 247 // this shouldn't happen, probably means daemon crashed 248 throw std::runtime_error("Error getting managed objects from " + 249 owner.first); 250 } 251 252 for (auto& pathPair : configuration) 253 { 254 if (pathPair.second.find(pidConfigurationInterface) != 255 pathPair.second.end() || 256 pathPair.second.find(pidZoneConfigurationInterface) != 257 pathPair.second.end() || 258 pathPair.second.find(stepwiseConfigurationInterface) != 259 pathPair.second.end()) 260 { 261 configurations.emplace(pathPair); 262 } 263 } 264 } 265 266 // on dbus having an index field is a bit strange, so randomly 267 // assign index based on name property 268 std::vector<std::string> zoneIndex; 269 for (const auto& configuration : configurations) 270 { 271 auto findZone = 272 configuration.second.find(pidZoneConfigurationInterface); 273 if (findZone != configuration.second.end()) 274 { 275 const auto& zone = findZone->second; 276 size_t index = 1; 277 const std::string& name = 278 sdbusplus::message::variant_ns::get<std::string>( 279 zone.at("Name")); 280 auto it = std::find(zoneIndex.begin(), zoneIndex.end(), name); 281 if (it == zoneIndex.end()) 282 { 283 zoneIndex.emplace_back(name); 284 index = zoneIndex.size(); 285 } 286 else 287 { 288 index = zoneIndex.end() - it; 289 } 290 291 auto& details = ZoneDetailsConfig[index]; 292 details.minthermalrpm = mapbox::util::apply_visitor( 293 VariantToFloatVisitor(), zone.at("MinThermalRpm")); 294 details.failsafepercent = mapbox::util::apply_visitor( 295 VariantToFloatVisitor(), zone.at("FailSafePercent")); 296 } 297 auto findBase = configuration.second.find(pidConfigurationInterface); 298 if (findBase != configuration.second.end()) 299 { 300 301 const auto& base = 302 configuration.second.at(pidConfigurationInterface); 303 const std::vector<std::string>& zones = 304 sdbusplus::message::variant_ns::get<std::vector<std::string>>( 305 base.at("Zones")); 306 for (const std::string& zone : zones) 307 { 308 auto it = std::find(zoneIndex.begin(), zoneIndex.end(), zone); 309 size_t index = 1; 310 if (it == zoneIndex.end()) 311 { 312 zoneIndex.emplace_back(zone); 313 index = zoneIndex.size(); 314 } 315 else 316 { 317 index = zoneIndex.end() - it; 318 } 319 PIDConf& conf = ZoneConfig[index]; 320 struct controller_info& info = 321 conf[sdbusplus::message::variant_ns::get<std::string>( 322 base.at("Name"))]; 323 info.type = sdbusplus::message::variant_ns::get<std::string>( 324 base.at("Class")); 325 // todo: auto generation yaml -> c script seems to discard this 326 // value for fans, verify this is okay 327 if (info.type == "fan") 328 { 329 info.setpoint = 0; 330 } 331 else 332 { 333 info.setpoint = mapbox::util::apply_visitor( 334 VariantToFloatVisitor(), base.at("SetPoint")); 335 } 336 info.pidInfo.ts = 1.0; // currently unused 337 info.pidInfo.p_c = mapbox::util::apply_visitor( 338 VariantToFloatVisitor(), base.at("PCoefficient")); 339 info.pidInfo.i_c = mapbox::util::apply_visitor( 340 VariantToFloatVisitor(), base.at("ICoefficient")); 341 info.pidInfo.ff_off = mapbox::util::apply_visitor( 342 VariantToFloatVisitor(), base.at("FFOffCoefficient")); 343 info.pidInfo.ff_gain = mapbox::util::apply_visitor( 344 VariantToFloatVisitor(), base.at("FFGainCoefficient")); 345 info.pidInfo.i_lim.max = mapbox::util::apply_visitor( 346 VariantToFloatVisitor(), base.at("ILimitMax")); 347 info.pidInfo.i_lim.min = mapbox::util::apply_visitor( 348 VariantToFloatVisitor(), base.at("ILimitMin")); 349 info.pidInfo.out_lim.max = mapbox::util::apply_visitor( 350 VariantToFloatVisitor(), base.at("OutLimitMax")); 351 info.pidInfo.out_lim.min = mapbox::util::apply_visitor( 352 VariantToFloatVisitor(), base.at("OutLimitMin")); 353 info.pidInfo.slew_neg = mapbox::util::apply_visitor( 354 VariantToFloatVisitor(), base.at("SlewNeg")); 355 info.pidInfo.slew_pos = mapbox::util::apply_visitor( 356 VariantToFloatVisitor(), base.at("SlewPos")); 357 358 std::vector<std::string> sensorNames = 359 sdbusplus::message::variant_ns::get< 360 std::vector<std::string>>(base.at("Inputs")); 361 auto findOutputs = 362 base.find("Outputs"); // currently only fans have outputs 363 if (findOutputs != base.end()) 364 { 365 std::vector<std::string> outputs = 366 sdbusplus::message::variant_ns::get< 367 std::vector<std::string>>(findOutputs->second); 368 sensorNames.insert(sensorNames.end(), outputs.begin(), 369 outputs.end()); 370 } 371 for (const std::string& sensorName : sensorNames) 372 { 373 std::string name = sensorName; 374 // replace spaces with underscores to be legal on dbus 375 std::replace(name.begin(), name.end(), ' ', '_'); 376 std::pair<std::string, std::string> sensorPathIfacePair; 377 378 if (!findSensor(sensors, name, sensorPathIfacePair)) 379 { 380 throw std::runtime_error( 381 "Could not map configuration to sensor " + name); 382 } 383 if (sensorPathIfacePair.second == sensorInterface) 384 { 385 info.inputs.push_back(name); 386 auto& config = SensorConfig[name]; 387 config.type = 388 sdbusplus::message::variant_ns::get<std::string>( 389 base.at("Class")); 390 config.readpath = sensorPathIfacePair.first; 391 // todo: maybe un-hardcode this if we run into slower 392 // timeouts with sensors 393 if (config.type == "temp") 394 { 395 config.timeout = 500; 396 } 397 } 398 if (sensorPathIfacePair.second == pwmInterface) 399 { 400 // copy so we can modify it 401 for (std::string otherSensor : sensorNames) 402 { 403 if (otherSensor == sensorName) 404 { 405 continue; 406 } 407 std::replace(otherSensor.begin(), otherSensor.end(), 408 ' ', '_'); 409 auto& config = SensorConfig[otherSensor]; 410 config.writepath = sensorPathIfacePair.first; 411 // todo: un-hardcode this if there are fans with 412 // different ranges 413 config.max = 255; 414 config.min = 0; 415 } 416 } 417 } 418 } 419 } 420 auto findStepwise = 421 configuration.second.find(stepwiseConfigurationInterface); 422 if (findStepwise != configuration.second.end()) 423 { 424 const auto& base = findStepwise->second; 425 const std::vector<std::string>& zones = 426 sdbusplus::message::variant_ns::get<std::vector<std::string>>( 427 base.at("Zones")); 428 for (const std::string& zone : zones) 429 { 430 auto it = std::find(zoneIndex.begin(), zoneIndex.end(), zone); 431 size_t index = 1; 432 if (it == zoneIndex.end()) 433 { 434 zoneIndex.emplace_back(zone); 435 index = zoneIndex.size(); 436 } 437 else 438 { 439 index = zoneIndex.end() - it; 440 } 441 PIDConf& conf = ZoneConfig[index]; 442 struct controller_info& info = 443 conf[sdbusplus::message::variant_ns::get<std::string>( 444 base.at("Name"))]; 445 info.type = "stepwise"; 446 info.stepwiseInfo.ts = 1.0; // currently unused 447 std::vector<double> readings = 448 sdbusplus::message::variant_ns::get<std::vector<double>>( 449 base.at("Reading")); 450 if (readings.size() > ec::maxStepwisePoints) 451 { 452 throw std::invalid_argument("Too many stepwise points."); 453 } 454 if (readings.empty()) 455 { 456 throw std::invalid_argument( 457 "Must have one stepwise point."); 458 } 459 std::copy(readings.begin(), readings.end(), 460 info.stepwiseInfo.reading); 461 if (readings.size() < ec::maxStepwisePoints) 462 { 463 info.stepwiseInfo.reading[readings.size()] = 464 std::numeric_limits<float>::quiet_NaN(); 465 } 466 std::vector<double> outputs = 467 sdbusplus::message::variant_ns::get<std::vector<double>>( 468 base.at("Output")); 469 if (readings.size() != outputs.size()) 470 { 471 throw std::invalid_argument( 472 "Outputs size must match readings"); 473 } 474 std::copy(outputs.begin(), outputs.end(), 475 info.stepwiseInfo.output); 476 if (outputs.size() < ec::maxStepwisePoints) 477 { 478 info.stepwiseInfo.output[outputs.size()] = 479 std::numeric_limits<float>::quiet_NaN(); 480 } 481 482 std::vector<std::string> sensorNames = 483 sdbusplus::message::variant_ns::get< 484 std::vector<std::string>>(base.at("Inputs")); 485 486 for (const std::string& sensorName : sensorNames) 487 { 488 std::string name = sensorName; 489 // replace spaces with underscores to be legal on dbus 490 std::replace(name.begin(), name.end(), ' ', '_'); 491 std::pair<std::string, std::string> sensorPathIfacePair; 492 493 if (!findSensor(sensors, name, sensorPathIfacePair)) 494 { 495 // todo(james): if we can't find a sensor, delete it 496 // from config, we'll find it on rescan 497 throw std::runtime_error( 498 "Could not map configuration to sensor " + name); 499 } 500 501 info.inputs.push_back(name); 502 auto& config = SensorConfig[name]; 503 config.readpath = sensorPathIfacePair.first; 504 config.type = "temp"; 505 // todo: maybe un-hardcode this if we run into slower 506 // timeouts with sensors 507 508 config.timeout = 500; 509 } 510 } 511 } 512 } 513 if (DEBUG) 514 { 515 debugPrint(); 516 } 517 } 518 } // namespace dbus_configuration 519