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 resp.read(respData); 186 } 187 catch (sdbusplus::exception_t&) 188 { 189 // can't do anything without mapper call data 190 throw std::runtime_error("ObjectMapper Call Failure"); 191 } 192 193 if (respData.empty()) 194 { 195 // can't do anything without mapper call data 196 throw std::runtime_error("No configuration data available from Mapper"); 197 } 198 // create a map of pair of <has pid configuration, ObjectManager path> 199 std::unordered_map<std::string, std::pair<bool, std::string>> owners; 200 // and a map of <path, interface> for sensors 201 std::unordered_map<std::string, std::string> sensors; 202 for (const auto& objectPair : respData) 203 { 204 for (const auto& ownerPair : objectPair.second) 205 { 206 auto& owner = owners[ownerPair.first]; 207 for (const std::string& interface : ownerPair.second) 208 { 209 210 if (interface == objectManagerInterface) 211 { 212 owner.second = objectPair.first; 213 } 214 if (interface == pidConfigurationInterface || 215 interface == pidZoneConfigurationInterface || 216 interface == stepwiseConfigurationInterface) 217 { 218 owner.first = true; 219 } 220 if (interface == sensorInterface || interface == pwmInterface) 221 { 222 // we're not interested in pwm sensors, just pwm control 223 if (interface == sensorInterface && 224 objectPair.first.find("pwm") != std::string::npos) 225 { 226 continue; 227 } 228 sensors[objectPair.first] = interface; 229 } 230 } 231 } 232 } 233 ManagedObjectType configurations; 234 for (const auto& owner : owners) 235 { 236 // skip if no pid configuration (means probably a sensor) 237 if (!owner.second.first) 238 { 239 continue; 240 } 241 auto endpoint = bus.new_method_call( 242 owner.first.c_str(), owner.second.second.c_str(), 243 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 244 ManagedObjectType configuration; 245 try 246 { 247 auto responce = bus.call(endpoint); 248 responce.read(configuration); 249 } 250 catch (sdbusplus::exception_t&) 251 { 252 // this shouldn't happen, probably means daemon crashed 253 throw std::runtime_error("Error getting managed objects from " + 254 owner.first); 255 } 256 257 for (auto& pathPair : configuration) 258 { 259 if (pathPair.second.find(pidConfigurationInterface) != 260 pathPair.second.end() || 261 pathPair.second.find(pidZoneConfigurationInterface) != 262 pathPair.second.end() || 263 pathPair.second.find(stepwiseConfigurationInterface) != 264 pathPair.second.end()) 265 { 266 configurations.emplace(pathPair); 267 } 268 } 269 } 270 271 // on dbus having an index field is a bit strange, so randomly 272 // assign index based on name property 273 std::vector<std::string> zoneIndex; 274 for (const auto& configuration : configurations) 275 { 276 auto findZone = 277 configuration.second.find(pidZoneConfigurationInterface); 278 if (findZone != configuration.second.end()) 279 { 280 const auto& zone = findZone->second; 281 size_t index = 1; 282 const std::string& name = 283 variant_ns::get<std::string>(zone.at("Name")); 284 auto it = std::find(zoneIndex.begin(), zoneIndex.end(), name); 285 if (it == zoneIndex.end()) 286 { 287 zoneIndex.emplace_back(name); 288 index = zoneIndex.size(); 289 } 290 else 291 { 292 index = zoneIndex.end() - it; 293 } 294 295 auto& details = zoneDetailsConfig[index]; 296 details.minthermalrpm = variant_ns::apply_visitor( 297 VariantToDoubleVisitor(), zone.at("MinThermalRpm")); 298 details.failsafepercent = variant_ns::apply_visitor( 299 VariantToDoubleVisitor(), zone.at("FailSafePercent")); 300 } 301 auto findBase = configuration.second.find(pidConfigurationInterface); 302 if (findBase != configuration.second.end()) 303 { 304 305 const auto& base = 306 configuration.second.at(pidConfigurationInterface); 307 const std::vector<std::string>& zones = 308 variant_ns::get<std::vector<std::string>>(base.at("Zones")); 309 for (const std::string& zone : zones) 310 { 311 auto it = std::find(zoneIndex.begin(), zoneIndex.end(), zone); 312 size_t index = 1; 313 if (it == zoneIndex.end()) 314 { 315 zoneIndex.emplace_back(zone); 316 index = zoneIndex.size(); 317 } 318 else 319 { 320 index = zoneIndex.end() - it; 321 } 322 PIDConf& conf = zoneConfig[index]; 323 324 std::vector<std::string> sensorNames = 325 variant_ns::get<std::vector<std::string>>( 326 base.at("Inputs")); 327 auto findOutputs = 328 base.find("Outputs"); // currently only fans have outputs 329 if (findOutputs != base.end()) 330 { 331 std::vector<std::string> outputs = 332 variant_ns::get<std::vector<std::string>>( 333 findOutputs->second); 334 sensorNames.insert(sensorNames.end(), outputs.begin(), 335 outputs.end()); 336 } 337 bool sensorsAvailable = sensorNames.size(); 338 std::vector<std::string> inputs; 339 for (const std::string& sensorName : sensorNames) 340 { 341 std::string name = sensorName; 342 // replace spaces with underscores to be legal on dbus 343 std::replace(name.begin(), name.end(), ' ', '_'); 344 std::pair<std::string, std::string> sensorPathIfacePair; 345 346 if (!findSensor(sensors, name, sensorPathIfacePair)) 347 { 348 sensorsAvailable = false; 349 break; 350 } 351 if (sensorPathIfacePair.second == sensorInterface) 352 { 353 inputs.push_back(name); 354 auto& config = sensorConfig[name]; 355 config.type = 356 variant_ns::get<std::string>(base.at("Class")); 357 config.readpath = sensorPathIfacePair.first; 358 // todo: maybe un-hardcode this if we run into slower 359 // timeouts with sensors 360 if (config.type == "temp") 361 { 362 config.timeout = 500; 363 } 364 } 365 else if (sensorPathIfacePair.second == pwmInterface) 366 { 367 // copy so we can modify it 368 for (std::string otherSensor : sensorNames) 369 { 370 if (otherSensor == sensorName) 371 { 372 continue; 373 } 374 std::replace(otherSensor.begin(), otherSensor.end(), 375 ' ', '_'); 376 auto& config = sensorConfig[otherSensor]; 377 config.writepath = sensorPathIfacePair.first; 378 // todo: un-hardcode this if there are fans with 379 // different ranges 380 config.max = 255; 381 config.min = 0; 382 } 383 } 384 } 385 // if the sensors aren't available in the current state, don't 386 // add them to the configuration. 387 if (!sensorsAvailable) 388 { 389 continue; 390 } 391 struct ControllerInfo& info = 392 conf[variant_ns::get<std::string>(base.at("Name"))]; 393 info.inputs = std::move(inputs); 394 395 info.type = variant_ns::get<std::string>(base.at("Class")); 396 // todo: auto generation yaml -> c script seems to discard this 397 // value for fans, verify this is okay 398 if (info.type == "fan") 399 { 400 info.setpoint = 0; 401 } 402 else 403 { 404 info.setpoint = variant_ns::apply_visitor( 405 VariantToDoubleVisitor(), base.at("SetPoint")); 406 } 407 info.pidInfo.ts = 1.0; // currently unused 408 info.pidInfo.p_c = variant_ns::apply_visitor( 409 VariantToDoubleVisitor(), base.at("PCoefficient")); 410 info.pidInfo.i_c = variant_ns::apply_visitor( 411 VariantToDoubleVisitor(), base.at("ICoefficient")); 412 info.pidInfo.ff_off = variant_ns::apply_visitor( 413 VariantToDoubleVisitor(), base.at("FFOffCoefficient")); 414 info.pidInfo.ff_gain = variant_ns::apply_visitor( 415 VariantToDoubleVisitor(), base.at("FFGainCoefficient")); 416 info.pidInfo.i_lim.max = variant_ns::apply_visitor( 417 VariantToDoubleVisitor(), base.at("ILimitMax")); 418 info.pidInfo.i_lim.min = variant_ns::apply_visitor( 419 VariantToDoubleVisitor(), base.at("ILimitMin")); 420 info.pidInfo.out_lim.max = variant_ns::apply_visitor( 421 VariantToDoubleVisitor(), base.at("OutLimitMax")); 422 info.pidInfo.out_lim.min = variant_ns::apply_visitor( 423 VariantToDoubleVisitor(), base.at("OutLimitMin")); 424 info.pidInfo.slew_neg = variant_ns::apply_visitor( 425 VariantToDoubleVisitor(), base.at("SlewNeg")); 426 info.pidInfo.slew_pos = variant_ns::apply_visitor( 427 VariantToDoubleVisitor(), base.at("SlewPos")); 428 } 429 } 430 auto findStepwise = 431 configuration.second.find(stepwiseConfigurationInterface); 432 if (findStepwise != configuration.second.end()) 433 { 434 const auto& base = findStepwise->second; 435 const std::vector<std::string>& zones = 436 variant_ns::get<std::vector<std::string>>(base.at("Zones")); 437 for (const std::string& zone : zones) 438 { 439 auto it = std::find(zoneIndex.begin(), zoneIndex.end(), zone); 440 size_t index = 1; 441 if (it == zoneIndex.end()) 442 { 443 zoneIndex.emplace_back(zone); 444 index = zoneIndex.size(); 445 } 446 else 447 { 448 index = zoneIndex.end() - it; 449 } 450 PIDConf& conf = zoneConfig[index]; 451 452 std::vector<std::string> inputs; 453 std::vector<std::string> sensorNames = 454 variant_ns::get<std::vector<std::string>>( 455 base.at("Inputs")); 456 457 bool sensorFound = sensorNames.size(); 458 for (const std::string& sensorName : sensorNames) 459 { 460 std::string name = sensorName; 461 // replace spaces with underscores to be legal on dbus 462 std::replace(name.begin(), name.end(), ' ', '_'); 463 std::pair<std::string, std::string> sensorPathIfacePair; 464 465 if (!findSensor(sensors, name, sensorPathIfacePair)) 466 { 467 sensorFound = false; 468 break; 469 } 470 471 inputs.push_back(name); 472 auto& config = sensorConfig[name]; 473 config.readpath = sensorPathIfacePair.first; 474 config.type = "temp"; 475 // todo: maybe un-hardcode this if we run into slower 476 // timeouts with sensors 477 478 config.timeout = 500; 479 } 480 if (!sensorFound) 481 { 482 continue; 483 } 484 struct ControllerInfo& info = 485 conf[variant_ns::get<std::string>(base.at("Name"))]; 486 info.inputs = std::move(inputs); 487 488 info.type = "stepwise"; 489 info.stepwiseInfo.ts = 1.0; // currently unused 490 info.stepwiseInfo.positiveHysteresis = 0.0; 491 info.stepwiseInfo.negativeHysteresis = 0.0; 492 auto findPosHyst = base.find("PositiveHysteresis"); 493 auto findNegHyst = base.find("NegativeHysteresis"); 494 if (findPosHyst != base.end()) 495 { 496 info.stepwiseInfo.positiveHysteresis = 497 variant_ns::apply_visitor(VariantToDoubleVisitor(), 498 findPosHyst->second); 499 } 500 if (findNegHyst != base.end()) 501 { 502 info.stepwiseInfo.positiveHysteresis = 503 variant_ns::apply_visitor(VariantToDoubleVisitor(), 504 findNegHyst->second); 505 } 506 std::vector<double> readings = 507 variant_ns::get<std::vector<double>>(base.at("Reading")); 508 if (readings.size() > ec::maxStepwisePoints) 509 { 510 throw std::invalid_argument("Too many stepwise points."); 511 } 512 if (readings.empty()) 513 { 514 throw std::invalid_argument( 515 "Must have one stepwise point."); 516 } 517 std::copy(readings.begin(), readings.end(), 518 info.stepwiseInfo.reading); 519 if (readings.size() < ec::maxStepwisePoints) 520 { 521 info.stepwiseInfo.reading[readings.size()] = 522 std::numeric_limits<double>::quiet_NaN(); 523 } 524 std::vector<double> outputs = 525 variant_ns::get<std::vector<double>>(base.at("Output")); 526 if (readings.size() != outputs.size()) 527 { 528 throw std::invalid_argument( 529 "Outputs size must match readings"); 530 } 531 std::copy(outputs.begin(), outputs.end(), 532 info.stepwiseInfo.output); 533 if (outputs.size() < ec::maxStepwisePoints) 534 { 535 info.stepwiseInfo.output[outputs.size()] = 536 std::numeric_limits<double>::quiet_NaN(); 537 } 538 } 539 } 540 } 541 if (DEBUG) 542 { 543 debugPrint(); 544 } 545 if (zoneConfig.empty() || zoneDetailsConfig.empty()) 546 { 547 std::cerr << "No fan zones, application pausing until reboot\n"; 548 while (1) 549 { 550 bus.process_discard(); 551 bus.wait(); 552 } 553 } 554 } 555 } // namespace dbus_configuration 556