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