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