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