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