1 /* 2 // Copyright (c) 2017 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 "PwmSensor.hpp" 18 #include "TachSensor.hpp" 19 #include "Thresholds.hpp" 20 #include "Utils.hpp" 21 #include "VariantVisitors.hpp" 22 23 #include <boost/algorithm/string/replace.hpp> 24 #include <boost/asio/error.hpp> 25 #include <boost/asio/io_context.hpp> 26 #include <boost/asio/post.hpp> 27 #include <boost/asio/steady_timer.hpp> 28 #include <boost/container/flat_map.hpp> 29 #include <boost/container/flat_set.hpp> 30 #include <sdbusplus/asio/connection.hpp> 31 #include <sdbusplus/asio/object_server.hpp> 32 #include <sdbusplus/bus.hpp> 33 #include <sdbusplus/bus/match.hpp> 34 #include <sdbusplus/message.hpp> 35 36 #include <array> 37 #include <chrono> 38 #include <cstddef> 39 #include <cstdint> 40 #include <filesystem> 41 #include <fstream> 42 #include <functional> 43 #include <ios> 44 #include <iostream> 45 #include <map> 46 #include <memory> 47 #include <optional> 48 #include <regex> 49 #include <string> 50 #include <system_error> 51 #include <utility> 52 #include <variant> 53 #include <vector> 54 55 namespace fs = std::filesystem; 56 57 // The following two structures need to be consistent 58 static auto sensorTypes{std::to_array<const char*>( 59 {"AspeedFan", "I2CFan", "NuvotonFan", "HPEFan"})}; 60 61 enum FanTypes 62 { 63 aspeed = 0, 64 i2c, 65 nuvoton, 66 hpe, 67 max, 68 }; 69 70 static_assert(std::tuple_size<decltype(sensorTypes)>::value == FanTypes::max, 71 "sensorTypes element number is not equal to FanTypes number"); 72 73 constexpr const char* redundancyConfiguration = 74 "xyz.openbmc_project.Configuration.FanRedundancy"; 75 static std::regex inputRegex(R"(fan(\d+)_input)"); 76 77 // todo: power supply fan redundancy 78 std::optional<RedundancySensor> systemRedundancy; 79 80 static const std::map<std::string, FanTypes> compatibleFanTypes = { 81 {"aspeed,ast2400-pwm-tacho", FanTypes::aspeed}, 82 {"aspeed,ast2500-pwm-tacho", FanTypes::aspeed}, 83 {"aspeed,ast2600-pwm-tach", FanTypes::aspeed}, 84 {"nuvoton,npcm750-pwm-fan", FanTypes::nuvoton}, 85 {"nuvoton,npcm845-pwm-fan", FanTypes::nuvoton}, 86 {"hpe,gxp-fan-ctrl", FanTypes::hpe} 87 // add compatible string here for new fan type 88 }; 89 90 FanTypes getFanType(const fs::path& parentPath) 91 { 92 fs::path linkPath = parentPath / "of_node"; 93 if (!fs::exists(linkPath)) 94 { 95 return FanTypes::i2c; 96 } 97 98 std::string canonical = fs::canonical(linkPath); 99 std::string compatiblePath = canonical + "/compatible"; 100 std::ifstream compatibleStream(compatiblePath); 101 102 if (!compatibleStream) 103 { 104 std::cerr << "Error opening " << compatiblePath << "\n"; 105 return FanTypes::i2c; 106 } 107 108 std::string compatibleString; 109 while (std::getline(compatibleStream, compatibleString)) 110 { 111 compatibleString.pop_back(); // trim EOL before comparisons 112 113 std::map<std::string, FanTypes>::const_iterator compatibleIterator = 114 compatibleFanTypes.find(compatibleString); 115 116 if (compatibleIterator != compatibleFanTypes.end()) 117 { 118 return compatibleIterator->second; 119 } 120 } 121 122 return FanTypes::i2c; 123 } 124 void enablePwm(const fs::path& filePath) 125 { 126 std::fstream enableFile(filePath, std::ios::in | std::ios::out); 127 if (!enableFile.good()) 128 { 129 std::cerr << "Error read/write " << filePath << "\n"; 130 return; 131 } 132 133 std::string regulateMode; 134 std::getline(enableFile, regulateMode); 135 if (regulateMode == "0") 136 { 137 enableFile << 1; 138 } 139 } 140 bool findPwmfanPath(unsigned int configPwmfanIndex, fs::path& pwmPath) 141 { 142 /* Search PWM since pwm-fan had separated 143 * PWM from tach directory and 1 channel only*/ 144 std::vector<fs::path> pwmfanPaths; 145 std::string pwnfanDevName("pwm-fan"); 146 147 pwnfanDevName += std::to_string(configPwmfanIndex); 148 149 if (!findFiles(fs::path("/sys/class/hwmon"), R"(pwm\d+)", pwmfanPaths)) 150 { 151 std::cerr << "No PWMs are found!\n"; 152 return false; 153 } 154 for (const auto& path : pwmfanPaths) 155 { 156 std::error_code ec; 157 fs::path link = fs::read_symlink(path.parent_path() / "device", ec); 158 159 if (ec) 160 { 161 std::cerr << "read_symlink() failed: " << ec.message() << " (" 162 << ec.value() << ")\n"; 163 continue; 164 } 165 166 if (link.filename().string() == pwnfanDevName) 167 { 168 pwmPath = path; 169 return true; 170 } 171 } 172 return false; 173 } 174 bool findPwmPath(const fs::path& directory, unsigned int pwm, fs::path& pwmPath) 175 { 176 std::error_code ec; 177 178 /* Assuming PWM file is appeared in the same directory as fanX_input */ 179 auto path = directory / ("pwm" + std::to_string(pwm + 1)); 180 bool exists = fs::exists(path, ec); 181 182 if (ec || !exists) 183 { 184 /* PWM file not exist or error happened */ 185 if (ec) 186 { 187 std::cerr << "exists() failed: " << ec.message() << " (" 188 << ec.value() << ")\n"; 189 } 190 /* try search form pwm-fanX directory */ 191 return findPwmfanPath(pwm, pwmPath); 192 } 193 194 pwmPath = path; 195 return true; 196 } 197 198 // The argument to this function should be the fanN_input file that we want to 199 // enable. The function will locate the corresponding fanN_enable file if it 200 // exists. Note that some drivers don't provide this file if the sensors are 201 // always enabled. 202 void enableFanInput(const fs::path& fanInputPath) 203 { 204 std::error_code ec; 205 std::string path(fanInputPath.string()); 206 boost::replace_last(path, "input", "enable"); 207 208 bool exists = fs::exists(path, ec); 209 if (ec || !exists) 210 { 211 return; 212 } 213 214 std::fstream enableFile(path, std::ios::out); 215 if (!enableFile.good()) 216 { 217 return; 218 } 219 enableFile << 1; 220 } 221 222 void createRedundancySensor( 223 const boost::container::flat_map<std::string, std::shared_ptr<TachSensor>>& 224 sensors, 225 const std::shared_ptr<sdbusplus::asio::connection>& conn, 226 sdbusplus::asio::object_server& objectServer) 227 { 228 conn->async_method_call( 229 [&objectServer, &sensors](boost::system::error_code& ec, 230 const ManagedObjectType& managedObj) { 231 if (ec) 232 { 233 std::cerr << "Error calling entity manager \n"; 234 return; 235 } 236 for (const auto& [path, interfaces] : managedObj) 237 { 238 for (const auto& [intf, cfg] : interfaces) 239 { 240 if (intf == redundancyConfiguration) 241 { 242 // currently only support one 243 auto findCount = cfg.find("AllowedFailures"); 244 if (findCount == cfg.end()) 245 { 246 std::cerr << "Malformed redundancy record \n"; 247 return; 248 } 249 std::vector<std::string> sensorList; 250 251 for (const auto& [name, sensor] : sensors) 252 { 253 sensorList.push_back( 254 "/xyz/openbmc_project/sensors/fan_tach/" + 255 sensor->name); 256 } 257 systemRedundancy.reset(); 258 systemRedundancy.emplace(RedundancySensor( 259 std::get<uint64_t>(findCount->second), sensorList, 260 objectServer, path)); 261 262 return; 263 } 264 } 265 } 266 }, 267 "xyz.openbmc_project.EntityManager", "/xyz/openbmc_project/inventory", 268 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 269 } 270 271 void createSensors( 272 boost::asio::io_context& io, sdbusplus::asio::object_server& objectServer, 273 boost::container::flat_map<std::string, std::shared_ptr<TachSensor>>& 274 tachSensors, 275 boost::container::flat_map<std::string, std::unique_ptr<PwmSensor>>& 276 pwmSensors, 277 boost::container::flat_map<std::string, std::weak_ptr<PresenceSensor>>& 278 presenceSensors, 279 std::shared_ptr<sdbusplus::asio::connection>& dbusConnection, 280 const std::shared_ptr<boost::container::flat_set<std::string>>& 281 sensorsChanged, 282 size_t retries = 0) 283 { 284 auto getter = std::make_shared<GetSensorConfiguration>( 285 dbusConnection, 286 [&io, &objectServer, &tachSensors, &pwmSensors, &presenceSensors, 287 &dbusConnection, 288 sensorsChanged](const ManagedObjectType& sensorConfigurations) { 289 bool firstScan = sensorsChanged == nullptr; 290 std::vector<fs::path> paths; 291 if (!findFiles(fs::path("/sys/class/hwmon"), R"(fan\d+_input)", 292 paths)) 293 { 294 std::cerr << "No fan sensors in system\n"; 295 return; 296 } 297 298 // iterate through all found fan sensors, and try to match them with 299 // configuration 300 for (const auto& path : paths) 301 { 302 std::smatch match; 303 std::string pathStr = path.string(); 304 305 std::regex_search(pathStr, match, inputRegex); 306 std::string indexStr = *(match.begin() + 1); 307 308 fs::path directory = path.parent_path(); 309 FanTypes fanType = getFanType(directory); 310 std::string cfgIntf = configInterfaceName(sensorTypes[fanType]); 311 312 // convert to 0 based 313 size_t index = std::stoul(indexStr) - 1; 314 315 const char* baseType = nullptr; 316 const SensorData* sensorData = nullptr; 317 const std::string* interfacePath = nullptr; 318 const SensorBaseConfiguration* baseConfiguration = nullptr; 319 for (const auto& [path, cfgData] : sensorConfigurations) 320 { 321 // find the base of the configuration to see if indexes 322 // match 323 auto sensorBaseFind = cfgData.find(cfgIntf); 324 if (sensorBaseFind == cfgData.end()) 325 { 326 continue; 327 } 328 329 baseConfiguration = &(*sensorBaseFind); 330 interfacePath = &path.str; 331 baseType = sensorTypes[fanType]; 332 333 auto findIndex = baseConfiguration->second.find("Index"); 334 if (findIndex == baseConfiguration->second.end()) 335 { 336 std::cerr 337 << baseConfiguration->first << " missing index\n"; 338 continue; 339 } 340 unsigned int configIndex = std::visit( 341 VariantToUnsignedIntVisitor(), findIndex->second); 342 if (configIndex != index) 343 { 344 continue; 345 } 346 if (fanType == FanTypes::aspeed || 347 fanType == FanTypes::nuvoton || 348 fanType == FanTypes::hpe) 349 { 350 // there will be only 1 aspeed or nuvoton or hpe sensor 351 // object in sysfs, we found the fan 352 sensorData = &cfgData; 353 break; 354 } 355 if (fanType == FanTypes::i2c) 356 { 357 std::string deviceName = 358 fs::read_symlink(directory / "device").filename(); 359 360 size_t bus = 0; 361 size_t addr = 0; 362 if (!getDeviceBusAddr(deviceName, bus, addr)) 363 { 364 continue; 365 } 366 367 auto findBus = baseConfiguration->second.find("Bus"); 368 auto findAddress = 369 baseConfiguration->second.find("Address"); 370 if (findBus == baseConfiguration->second.end() || 371 findAddress == baseConfiguration->second.end()) 372 { 373 std::cerr << baseConfiguration->first 374 << " missing bus or address\n"; 375 continue; 376 } 377 unsigned int configBus = std::visit( 378 VariantToUnsignedIntVisitor(), findBus->second); 379 unsigned int configAddress = std::visit( 380 VariantToUnsignedIntVisitor(), findAddress->second); 381 382 if (configBus == bus && configAddress == addr) 383 { 384 sensorData = &cfgData; 385 break; 386 } 387 } 388 } 389 if (sensorData == nullptr) 390 { 391 std::cerr 392 << "failed to find match for " << path.string() << "\n"; 393 continue; 394 } 395 396 auto findSensorName = baseConfiguration->second.find("Name"); 397 398 if (findSensorName == baseConfiguration->second.end()) 399 { 400 std::cerr << "could not determine configuration name for " 401 << path.string() << "\n"; 402 continue; 403 } 404 std::string sensorName = 405 std::get<std::string>(findSensorName->second); 406 407 // on rescans, only update sensors we were signaled by 408 auto findSensor = tachSensors.find(sensorName); 409 if (!firstScan && findSensor != tachSensors.end()) 410 { 411 bool found = false; 412 for (auto it = sensorsChanged->begin(); 413 it != sensorsChanged->end(); it++) 414 { 415 if (it->ends_with(findSensor->second->name)) 416 { 417 sensorsChanged->erase(it); 418 findSensor->second = nullptr; 419 found = true; 420 break; 421 } 422 } 423 if (!found) 424 { 425 continue; 426 } 427 } 428 std::vector<thresholds::Threshold> sensorThresholds; 429 if (!parseThresholdsFromConfig(*sensorData, sensorThresholds)) 430 { 431 std::cerr << "error populating thresholds for " 432 << sensorName << "\n"; 433 } 434 435 auto presenceConfig = 436 sensorData->find(cfgIntf + std::string(".Presence")); 437 438 std::shared_ptr<PresenceSensor> presenceSensor(nullptr); 439 440 // presence sensors are optional 441 if (presenceConfig != sensorData->end()) 442 { 443 auto findPolarity = presenceConfig->second.find("Polarity"); 444 auto findPinName = presenceConfig->second.find("PinName"); 445 446 if (findPinName == presenceConfig->second.end() || 447 findPolarity == presenceConfig->second.end()) 448 { 449 std::cerr << "Malformed Presence Configuration\n"; 450 } 451 else 452 { 453 bool inverted = std::get<std::string>( 454 findPolarity->second) == "Low"; 455 const auto* pinName = 456 std::get_if<std::string>(&findPinName->second); 457 458 if (pinName != nullptr) 459 { 460 auto findPresenceSensor = 461 presenceSensors.find(*pinName); 462 if (findPresenceSensor != presenceSensors.end()) 463 { 464 auto p = findPresenceSensor->second.lock(); 465 if (p) 466 { 467 presenceSensor = p; 468 } 469 } 470 if (!presenceSensor) 471 { 472 presenceSensor = 473 std::make_shared<PresenceSensor>( 474 *pinName, inverted, io, sensorName); 475 presenceSensors[*pinName] = presenceSensor; 476 } 477 } 478 else 479 { 480 std::cerr 481 << "Malformed Presence pinName for sensor " 482 << sensorName << " \n"; 483 } 484 } 485 } 486 std::optional<RedundancySensor>* redundancy = nullptr; 487 if (fanType == FanTypes::aspeed) 488 { 489 redundancy = &systemRedundancy; 490 } 491 492 PowerState powerState = 493 getPowerState(baseConfiguration->second); 494 495 constexpr double defaultMaxReading = 25000; 496 constexpr double defaultMinReading = 0; 497 std::pair<double, double> limits = 498 std::make_pair(defaultMinReading, defaultMaxReading); 499 500 auto connector = 501 sensorData->find(cfgIntf + std::string(".Connector")); 502 503 std::optional<std::string> led; 504 std::string pwmName; 505 fs::path pwmPath; 506 507 // The Mutable parameter is optional, defaulting to false 508 bool isValueMutable = false; 509 if (connector != sensorData->end()) 510 { 511 auto findPwm = connector->second.find("Pwm"); 512 if (findPwm != connector->second.end()) 513 { 514 size_t pwm = std::visit(VariantToUnsignedIntVisitor(), 515 findPwm->second); 516 if (!findPwmPath(directory, pwm, pwmPath)) 517 { 518 std::cerr << "Connector for " << sensorName 519 << " no pwm channel found!\n"; 520 continue; 521 } 522 523 fs::path pwmEnableFile = 524 "pwm" + std::to_string(pwm + 1) + "_enable"; 525 fs::path enablePath = 526 pwmPath.parent_path() / pwmEnableFile; 527 enablePwm(enablePath); 528 529 /* use pwm name override if found in configuration else 530 * use default */ 531 auto findOverride = connector->second.find("PwmName"); 532 if (findOverride != connector->second.end()) 533 { 534 pwmName = std::visit(VariantToStringVisitor(), 535 findOverride->second); 536 } 537 else 538 { 539 pwmName = "Pwm_" + std::to_string(pwm + 1); 540 } 541 542 // Check PWM sensor mutability 543 auto findMutable = connector->second.find("Mutable"); 544 if (findMutable != connector->second.end()) 545 { 546 const auto* ptrMutable = 547 std::get_if<bool>(&(findMutable->second)); 548 if (ptrMutable != nullptr) 549 { 550 isValueMutable = *ptrMutable; 551 } 552 } 553 } 554 else 555 { 556 std::cerr << "Connector for " << sensorName 557 << " missing pwm!\n"; 558 } 559 560 auto findLED = connector->second.find("LED"); 561 if (findLED != connector->second.end()) 562 { 563 const auto* ledName = 564 std::get_if<std::string>(&(findLED->second)); 565 if (ledName == nullptr) 566 { 567 std::cerr << "Wrong format for LED of " 568 << sensorName << "\n"; 569 } 570 else 571 { 572 led = *ledName; 573 } 574 } 575 } 576 577 findLimits(limits, baseConfiguration); 578 579 enableFanInput(path); 580 581 auto& tachSensor = tachSensors[sensorName]; 582 tachSensor = nullptr; 583 tachSensor = std::make_shared<TachSensor>( 584 path.string(), baseType, objectServer, dbusConnection, 585 presenceSensor, redundancy, io, sensorName, 586 std::move(sensorThresholds), *interfacePath, limits, 587 powerState, led); 588 tachSensor->setupRead(); 589 590 if (!pwmPath.empty() && fs::exists(pwmPath) && 591 (pwmSensors.count(pwmPath) == 0U)) 592 { 593 pwmSensors[pwmPath] = std::make_unique<PwmSensor>( 594 pwmName, pwmPath, dbusConnection, objectServer, 595 *interfacePath, "Fan", isValueMutable); 596 } 597 } 598 599 createRedundancySensor(tachSensors, dbusConnection, objectServer); 600 }); 601 getter->getConfiguration( 602 std::vector<std::string>{sensorTypes.begin(), sensorTypes.end()}, 603 retries); 604 } 605 606 int main() 607 { 608 boost::asio::io_context io; 609 auto systemBus = std::make_shared<sdbusplus::asio::connection>(io); 610 sdbusplus::asio::object_server objectServer(systemBus, true); 611 612 objectServer.add_manager("/xyz/openbmc_project/sensors"); 613 objectServer.add_manager("/xyz/openbmc_project/control"); 614 objectServer.add_manager("/xyz/openbmc_project/inventory"); 615 systemBus->request_name("xyz.openbmc_project.FanSensor"); 616 boost::container::flat_map<std::string, std::shared_ptr<TachSensor>> 617 tachSensors; 618 boost::container::flat_map<std::string, std::unique_ptr<PwmSensor>> 619 pwmSensors; 620 boost::container::flat_map<std::string, std::weak_ptr<PresenceSensor>> 621 presenceSensors; 622 auto sensorsChanged = 623 std::make_shared<boost::container::flat_set<std::string>>(); 624 625 boost::asio::post(io, [&]() { 626 createSensors(io, objectServer, tachSensors, pwmSensors, 627 presenceSensors, systemBus, nullptr); 628 }); 629 630 boost::asio::steady_timer filterTimer(io); 631 std::function<void(sdbusplus::message_t&)> eventHandler = 632 [&](sdbusplus::message_t& message) { 633 if (message.is_method_error()) 634 { 635 std::cerr << "callback method error\n"; 636 return; 637 } 638 sensorsChanged->insert(message.get_path()); 639 // this implicitly cancels the timer 640 filterTimer.expires_after(std::chrono::seconds(1)); 641 642 filterTimer.async_wait([&](const boost::system::error_code& ec) { 643 if (ec == boost::asio::error::operation_aborted) 644 { 645 /* we were canceled*/ 646 return; 647 } 648 if (ec) 649 { 650 std::cerr << "timer error\n"; 651 return; 652 } 653 createSensors(io, objectServer, tachSensors, pwmSensors, 654 presenceSensors, systemBus, sensorsChanged, 5); 655 }); 656 }; 657 658 std::vector<std::unique_ptr<sdbusplus::bus::match_t>> matches = 659 setupPropertiesChangedMatches(*systemBus, sensorTypes, eventHandler); 660 661 // redundancy sensor 662 std::function<void(sdbusplus::message_t&)> redundancyHandler = 663 [&tachSensors, &systemBus, &objectServer](sdbusplus::message_t&) { 664 createRedundancySensor(tachSensors, systemBus, objectServer); 665 }; 666 auto match = std::make_unique<sdbusplus::bus::match_t>( 667 static_cast<sdbusplus::bus_t&>(*systemBus), 668 "type='signal',member='PropertiesChanged',path_namespace='" + 669 std::string(inventoryPath) + "',arg0namespace='" + 670 redundancyConfiguration + "'", 671 std::move(redundancyHandler)); 672 matches.emplace_back(std::move(match)); 673 674 setupManufacturingModeMatch(*systemBus); 675 io.run(); 676 return 0; 677 } 678