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.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.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.minThermalRpm = 303 std::visit(VariantToDoubleVisitor(), zone.at("MinThermalRpm")); 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 } 370 else if (sensorPathIfacePair.second == pwmInterface) 371 { 372 // copy so we can modify it 373 for (std::string otherSensor : sensorNames) 374 { 375 std::replace(otherSensor.begin(), otherSensor.end(), 376 ' ', '_'); 377 if (sensorPathIfacePair.first.find(otherSensor) != 378 std::string::npos) 379 { 380 continue; 381 } 382 383 auto& config = sensorConfig[otherSensor]; 384 config.writePath = sensorPathIfacePair.first; 385 // todo: un-hardcode this if there are fans with 386 // different ranges 387 config.max = 255; 388 config.min = 0; 389 } 390 } 391 } 392 393 struct ControllerInfo& info = 394 conf[std::get<std::string>(base.at("Name"))]; 395 info.inputs = std::move(inputs); 396 397 info.type = std::get<std::string>(base.at("Class")); 398 // todo: auto generation yaml -> c script seems to discard this 399 // value for fans, verify this is okay 400 if (info.type == "fan") 401 { 402 info.setpoint = 0; 403 } 404 else 405 { 406 info.setpoint = std::visit(VariantToDoubleVisitor(), 407 base.at("SetPoint")); 408 } 409 info.pidInfo.ts = 1.0; // currently unused 410 info.pidInfo.proportionalCoeff = std::visit( 411 VariantToDoubleVisitor(), base.at("PCoefficient")); 412 info.pidInfo.integralCoeff = std::visit( 413 VariantToDoubleVisitor(), base.at("ICoefficient")); 414 info.pidInfo.feedFwdOffset = std::visit( 415 VariantToDoubleVisitor(), base.at("FFOffCoefficient")); 416 info.pidInfo.feedFwdGain = std::visit( 417 VariantToDoubleVisitor(), base.at("FFGainCoefficient")); 418 info.pidInfo.integralLimit.max = 419 std::visit(VariantToDoubleVisitor(), base.at("ILimitMax")); 420 info.pidInfo.integralLimit.min = 421 std::visit(VariantToDoubleVisitor(), base.at("ILimitMin")); 422 info.pidInfo.outLim.max = std::visit(VariantToDoubleVisitor(), 423 base.at("OutLimitMax")); 424 info.pidInfo.outLim.min = std::visit(VariantToDoubleVisitor(), 425 base.at("OutLimitMin")); 426 info.pidInfo.slewNeg = 427 std::visit(VariantToDoubleVisitor(), base.at("SlewNeg")); 428 info.pidInfo.slewPos = 429 std::visit(VariantToDoubleVisitor(), base.at("SlewPos")); 430 double negativeHysteresis = 0; 431 double positiveHysteresis = 0; 432 433 auto findNeg = base.find("NegativeHysteresis"); 434 auto findPos = base.find("PositiveHysteresis"); 435 if (findNeg != base.end()) 436 { 437 negativeHysteresis = 438 std::visit(VariantToDoubleVisitor(), findNeg->second); 439 } 440 441 if (findPos != base.end()) 442 { 443 positiveHysteresis = 444 std::visit(VariantToDoubleVisitor(), findPos->second); 445 } 446 info.pidInfo.negativeHysteresis = negativeHysteresis; 447 info.pidInfo.positiveHysteresis = positiveHysteresis; 448 } 449 } 450 auto findStepwise = 451 configuration.second.find(stepwiseConfigurationInterface); 452 if (findStepwise != configuration.second.end()) 453 { 454 const auto& base = findStepwise->second; 455 const std::vector<std::string>& zones = 456 std::get<std::vector<std::string>>(base.at("Zones")); 457 for (const std::string& zone : zones) 458 { 459 size_t index = getZoneIndex(zone, foundZones); 460 PIDConf& conf = zoneConfig[index]; 461 462 std::vector<std::string> inputs; 463 std::vector<std::string> sensorNames = 464 std::get<std::vector<std::string>>(base.at("Inputs")); 465 466 bool sensorFound = false; 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::vector<std::pair<std::string, std::string>> 473 sensorPathIfacePairs; 474 475 if (!findSensors(sensors, name, sensorPathIfacePairs)) 476 { 477 break; 478 } 479 480 for (const auto& sensorPathIfacePair : sensorPathIfacePairs) 481 { 482 size_t idx = 483 sensorPathIfacePair.first.find_last_of("/") + 1; 484 std::string shortName = 485 sensorPathIfacePair.first.substr(idx); 486 487 inputs.push_back(shortName); 488 auto& config = sensorConfig[shortName]; 489 config.readPath = sensorPathIfacePair.first; 490 config.type = "temp"; 491 // todo: maybe un-hardcode this if we run into slower 492 // timeouts with sensors 493 494 config.timeout = 0; 495 sensorFound = true; 496 } 497 } 498 if (!sensorFound) 499 { 500 continue; 501 } 502 struct ControllerInfo& info = 503 conf[std::get<std::string>(base.at("Name"))]; 504 info.inputs = std::move(inputs); 505 506 info.type = "stepwise"; 507 info.stepwiseInfo.ts = 1.0; // currently unused 508 info.stepwiseInfo.positiveHysteresis = 0.0; 509 info.stepwiseInfo.negativeHysteresis = 0.0; 510 auto findPosHyst = base.find("PositiveHysteresis"); 511 auto findNegHyst = base.find("NegativeHysteresis"); 512 if (findPosHyst != base.end()) 513 { 514 info.stepwiseInfo.positiveHysteresis = std::visit( 515 VariantToDoubleVisitor(), findPosHyst->second); 516 } 517 if (findNegHyst != base.end()) 518 { 519 info.stepwiseInfo.positiveHysteresis = std::visit( 520 VariantToDoubleVisitor(), findNegHyst->second); 521 } 522 std::vector<double> readings = 523 std::get<std::vector<double>>(base.at("Reading")); 524 if (readings.size() > ec::maxStepwisePoints) 525 { 526 throw std::invalid_argument("Too many stepwise points."); 527 } 528 if (readings.empty()) 529 { 530 throw std::invalid_argument( 531 "Must have one stepwise point."); 532 } 533 std::copy(readings.begin(), readings.end(), 534 info.stepwiseInfo.reading); 535 if (readings.size() < ec::maxStepwisePoints) 536 { 537 info.stepwiseInfo.reading[readings.size()] = 538 std::numeric_limits<double>::quiet_NaN(); 539 } 540 std::vector<double> outputs = 541 std::get<std::vector<double>>(base.at("Output")); 542 if (readings.size() != outputs.size()) 543 { 544 throw std::invalid_argument( 545 "Outputs size must match readings"); 546 } 547 std::copy(outputs.begin(), outputs.end(), 548 info.stepwiseInfo.output); 549 if (outputs.size() < ec::maxStepwisePoints) 550 { 551 info.stepwiseInfo.output[outputs.size()] = 552 std::numeric_limits<double>::quiet_NaN(); 553 } 554 } 555 } 556 } 557 if (DEBUG) 558 { 559 debugPrint(); 560 } 561 if (zoneConfig.empty() || zoneDetailsConfig.empty()) 562 { 563 std::cerr << "No fan zones, application pausing until reboot\n"; 564 while (1) 565 { 566 bus.process_discard(); 567 bus.wait(); 568 } 569 } 570 } 571 } // namespace dbus_configuration 572