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