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