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