1 /* 2 // Copyright (c) 2019 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 "DeviceMgmt.hpp" 18 #include "PSUEvent.hpp" 19 #include "PSUSensor.hpp" 20 #include "PwmSensor.hpp" 21 #include "SensorPaths.hpp" 22 #include "Thresholds.hpp" 23 #include "Utils.hpp" 24 #include "VariantVisitors.hpp" 25 26 #include <boost/algorithm/string/case_conv.hpp> 27 #include <boost/algorithm/string/replace.hpp> 28 #include <boost/asio/error.hpp> 29 #include <boost/asio/io_context.hpp> 30 #include <boost/asio/post.hpp> 31 #include <boost/asio/steady_timer.hpp> 32 #include <boost/container/flat_map.hpp> 33 #include <boost/container/flat_set.hpp> 34 #include <sdbusplus/asio/connection.hpp> 35 #include <sdbusplus/asio/object_server.hpp> 36 #include <sdbusplus/bus.hpp> 37 #include <sdbusplus/bus/match.hpp> 38 #include <sdbusplus/exception.hpp> 39 #include <sdbusplus/message.hpp> 40 #include <sdbusplus/message/native_types.hpp> 41 42 #include <algorithm> 43 #include <array> 44 #include <cctype> 45 #include <chrono> 46 #include <cmath> 47 #include <cstddef> 48 #include <cstdint> 49 #include <exception> 50 #include <filesystem> 51 #include <fstream> 52 #include <functional> 53 #include <iostream> 54 #include <memory> 55 #include <regex> 56 #include <stdexcept> 57 #include <string> 58 #include <string_view> 59 #include <utility> 60 #include <variant> 61 #include <vector> 62 63 static constexpr bool debug = false; 64 65 static const I2CDeviceTypeMap sensorTypes{ 66 {"ADC128D818", I2CDeviceType{"adc128d818", true}}, 67 {"ADM1266", I2CDeviceType{"adm1266", true}}, 68 {"ADM1272", I2CDeviceType{"adm1272", true}}, 69 {"ADM1275", I2CDeviceType{"adm1275", true}}, 70 {"ADM1278", I2CDeviceType{"adm1278", true}}, 71 {"ADM1293", I2CDeviceType{"adm1293", true}}, 72 {"ADS1015", I2CDeviceType{"ads1015", true}}, 73 {"ADS7830", I2CDeviceType{"ads7830", true}}, 74 {"AHE50DC_FAN", I2CDeviceType{"ahe50dc_fan", true}}, 75 {"BMR490", I2CDeviceType{"bmr490", true}}, 76 {"cffps", I2CDeviceType{"cffps", true}}, 77 {"cffps1", I2CDeviceType{"cffps", true}}, 78 {"cffps2", I2CDeviceType{"cffps", true}}, 79 {"cffps3", I2CDeviceType{"cffps", true}}, 80 {"DPS800", I2CDeviceType{"dps800", true}}, 81 {"INA219", I2CDeviceType{"ina219", true}}, 82 {"INA230", I2CDeviceType{"ina230", true}}, 83 {"INA238", I2CDeviceType{"ina238", true}}, 84 {"IPSPS1", I2CDeviceType{"ipsps1", true}}, 85 {"IR38060", I2CDeviceType{"ir38060", true}}, 86 {"IR38164", I2CDeviceType{"ir38164", true}}, 87 {"IR38263", I2CDeviceType{"ir38263", true}}, 88 {"ISL68137", I2CDeviceType{"isl68137", true}}, 89 {"ISL68220", I2CDeviceType{"isl68220", true}}, 90 {"ISL68223", I2CDeviceType{"isl68223", true}}, 91 {"ISL69225", I2CDeviceType{"isl69225", true}}, 92 {"ISL69243", I2CDeviceType{"isl69243", true}}, 93 {"ISL69260", I2CDeviceType{"isl69260", true}}, 94 {"LM25066", I2CDeviceType{"lm25066", true}}, 95 {"LTC2945", I2CDeviceType{"ltc2945", true}}, 96 {"LTC4286", I2CDeviceType{"ltc4286", true}}, 97 {"LTC4287", I2CDeviceType{"ltc4287", true}}, 98 {"MAX5970", I2CDeviceType{"max5970", true}}, 99 {"MAX11607", I2CDeviceType{"max11607", false}}, 100 {"MAX11617", I2CDeviceType{"max11617", false}}, 101 {"MAX16601", I2CDeviceType{"max16601", true}}, 102 {"MAX20710", I2CDeviceType{"max20710", true}}, 103 {"MAX20730", I2CDeviceType{"max20730", true}}, 104 {"MAX20734", I2CDeviceType{"max20734", true}}, 105 {"MAX20796", I2CDeviceType{"max20796", true}}, 106 {"MAX34451", I2CDeviceType{"max34451", true}}, 107 {"MP2856", I2CDeviceType{"mp2856", true}}, 108 {"MP2857", I2CDeviceType{"mp2857", true}}, 109 {"MP2971", I2CDeviceType{"mp2971", true}}, 110 {"MP2973", I2CDeviceType{"mp2973", true}}, 111 {"MP2975", I2CDeviceType{"mp2975", true}}, 112 {"MP5023", I2CDeviceType{"mp5023", true}}, 113 {"MP5990", I2CDeviceType{"mp5990", true}}, 114 {"MPQ8785", I2CDeviceType{"mpq8785", true}}, 115 {"NCP4200", I2CDeviceType{"ncp4200", true}}, 116 {"PLI1209BC", I2CDeviceType{"pli1209bc", true}}, 117 {"pmbus", I2CDeviceType{"pmbus", true}}, 118 {"PXE1610", I2CDeviceType{"pxe1610", true}}, 119 {"RAA228000", I2CDeviceType{"raa228000", true}}, 120 {"RAA228228", I2CDeviceType{"raa228228", true}}, 121 {"RAA228620", I2CDeviceType{"raa228620", true}}, 122 {"RAA229001", I2CDeviceType{"raa229001", true}}, 123 {"RAA229004", I2CDeviceType{"raa229004", true}}, 124 {"RAA229126", I2CDeviceType{"raa229126", true}}, 125 {"SBRMI", I2CDeviceType{"sbrmi", true}}, 126 {"TDA38640", I2CDeviceType{"tda38640", true}}, 127 {"TPS53679", I2CDeviceType{"tps53679", true}}, 128 {"TPS546D24", I2CDeviceType{"tps546d24", true}}, 129 {"XDP710", I2CDeviceType{"xdp710", true}}, 130 {"XDPE11280", I2CDeviceType{"xdpe11280", true}}, 131 {"XDPE12284", I2CDeviceType{"xdpe12284", true}}, 132 {"XDPE152C4", I2CDeviceType{"xdpe152c4", true}}, 133 }; 134 135 enum class DevTypes 136 { 137 Unknown = 0, 138 HWMON, 139 IIO 140 }; 141 142 struct DevParams 143 { 144 unsigned int matchIndex = 0; 145 std::string matchRegEx; 146 std::string nameRegEx; 147 }; 148 149 namespace fs = std::filesystem; 150 151 static boost::container::flat_map<std::string, std::shared_ptr<PSUSensor>> 152 sensors; 153 static boost::container::flat_map<std::string, std::unique_ptr<PSUCombineEvent>> 154 combineEvents; 155 static boost::container::flat_map<std::string, std::unique_ptr<PwmSensor>> 156 pwmSensors; 157 static boost::container::flat_map<std::string, std::string> sensorTable; 158 static boost::container::flat_map<std::string, PSUProperty> labelMatch; 159 static EventPathList eventMatch; 160 static EventPathList limitEventMatch; 161 162 static boost::container::flat_map<size_t, bool> cpuPresence; 163 static boost::container::flat_map<DevTypes, DevParams> devParamMap; 164 165 // Function CheckEvent will check each attribute from eventMatch table in the 166 // sysfs. If the attributes exists in sysfs, then store the complete path 167 // of the attribute into eventPathList. 168 void checkEvent(const std::string& directory, const EventPathList& eventMatch, 169 EventPathList& eventPathList) 170 { 171 for (const auto& match : eventMatch) 172 { 173 const std::vector<std::string>& eventAttrs = match.second; 174 const std::string& eventName = match.first; 175 for (const auto& eventAttr : eventAttrs) 176 { 177 std::string eventPath = directory; 178 eventPath += "/"; 179 eventPath += eventAttr; 180 181 std::ifstream eventFile(eventPath); 182 if (!eventFile.good()) 183 { 184 continue; 185 } 186 187 eventPathList[eventName].push_back(eventPath); 188 } 189 } 190 } 191 192 // Check Group Events which contains more than one targets in each combine 193 // events. 194 void checkGroupEvent(const std::string& directory, 195 GroupEventPathList& groupEventPathList) 196 { 197 EventPathList pathList; 198 std::vector<fs::path> eventPaths; 199 if (!findFiles(fs::path(directory), R"(fan\d+_(alarm|fault))", eventPaths)) 200 { 201 return; 202 } 203 204 for (const auto& eventPath : eventPaths) 205 { 206 std::string attrName = eventPath.filename(); 207 pathList[attrName.substr(0, attrName.find('_'))].push_back(eventPath); 208 } 209 groupEventPathList["FanFault"] = pathList; 210 } 211 212 // Function checkEventLimits will check all the psu related xxx_input attributes 213 // in sysfs to see if xxx_crit_alarm xxx_lcrit_alarm xxx_max_alarm 214 // xxx_min_alarm exist, then store the existing paths of the alarm attributes 215 // to eventPathList. 216 void checkEventLimits(const std::string& sensorPathStr, 217 const EventPathList& limitEventMatch, 218 EventPathList& eventPathList) 219 { 220 auto attributePartPos = sensorPathStr.find_last_of('_'); 221 if (attributePartPos == std::string::npos) 222 { 223 // There is no '_' in the string, skip it 224 return; 225 } 226 auto attributePart = 227 std::string_view(sensorPathStr).substr(attributePartPos + 1); 228 if (attributePart != "input") 229 { 230 // If the sensor is not xxx_input, skip it 231 return; 232 } 233 234 auto prefixPart = sensorPathStr.substr(0, attributePartPos + 1); 235 for (const auto& limitMatch : limitEventMatch) 236 { 237 const std::vector<std::string>& limitEventAttrs = limitMatch.second; 238 const std::string& eventName = limitMatch.first; 239 for (const auto& limitEventAttr : limitEventAttrs) 240 { 241 auto limitEventPath = prefixPart + limitEventAttr; 242 std::ifstream eventFile(limitEventPath); 243 if (!eventFile.good()) 244 { 245 continue; 246 } 247 eventPathList[eventName].push_back(limitEventPath); 248 } 249 } 250 } 251 252 static void 253 checkPWMSensor(const fs::path& sensorPath, std::string& labelHead, 254 const std::string& interfacePath, 255 std::shared_ptr<sdbusplus::asio::connection>& dbusConnection, 256 sdbusplus::asio::object_server& objectServer, 257 const std::string& psuName) 258 { 259 if (!labelHead.starts_with("fan")) 260 { 261 return; 262 } 263 std::string labelHeadIndex = labelHead.substr(3); 264 265 const std::string& sensorPathStr = sensorPath.string(); 266 const std::string& pwmPathStr = boost::replace_all_copy(sensorPathStr, 267 "input", "target"); 268 std::ifstream pwmFile(pwmPathStr); 269 if (!pwmFile.good()) 270 { 271 return; 272 } 273 274 auto findPWMSensor = pwmSensors.find(psuName + labelHead); 275 if (findPWMSensor != pwmSensors.end()) 276 { 277 return; 278 } 279 280 std::string name = "Pwm_"; 281 name += psuName; 282 name += "_Fan_"; 283 name += labelHeadIndex; 284 285 std::string objPath = interfacePath; 286 objPath += "_Fan_"; 287 objPath += labelHeadIndex; 288 289 pwmSensors[psuName + labelHead] = std::make_unique<PwmSensor>( 290 name, pwmPathStr, dbusConnection, objectServer, objPath, "PSU"); 291 } 292 293 static void createSensorsCallback( 294 boost::asio::io_context& io, sdbusplus::asio::object_server& objectServer, 295 std::shared_ptr<sdbusplus::asio::connection>& dbusConnection, 296 const ManagedObjectType& sensorConfigs, 297 const std::shared_ptr<boost::container::flat_set<std::string>>& 298 sensorsChanged, 299 bool activateOnly) 300 { 301 int numCreated = 0; 302 bool firstScan = sensorsChanged == nullptr; 303 304 auto devices = instantiateDevices(sensorConfigs, sensors, sensorTypes); 305 306 std::vector<fs::path> pmbusPaths; 307 findFiles(fs::path("/sys/bus/iio/devices"), "name", pmbusPaths); 308 findFiles(fs::path("/sys/class/hwmon"), "name", pmbusPaths); 309 if (pmbusPaths.empty()) 310 { 311 std::cerr << "No PSU sensors in system\n"; 312 return; 313 } 314 315 boost::container::flat_set<std::string> directories; 316 for (const auto& pmbusPath : pmbusPaths) 317 { 318 EventPathList eventPathList; 319 GroupEventPathList groupEventPathList; 320 321 std::ifstream nameFile(pmbusPath); 322 if (!nameFile.good()) 323 { 324 std::cerr << "Failure finding pmbus path " << pmbusPath << "\n"; 325 continue; 326 } 327 328 std::string pmbusName; 329 std::getline(nameFile, pmbusName); 330 nameFile.close(); 331 332 if (sensorTypes.find(pmbusName) == sensorTypes.end()) 333 { 334 // To avoid this error message, add your driver name to 335 // the pmbusNames vector at the top of this file. 336 std::cerr << "Driver name " << pmbusName 337 << " not found in sensor whitelist\n"; 338 continue; 339 } 340 341 auto directory = pmbusPath.parent_path(); 342 343 auto ret = directories.insert(directory.string()); 344 if (!ret.second) 345 { 346 std::cerr << "Duplicate path " << directory.string() << "\n"; 347 continue; // check if path has already been searched 348 } 349 350 DevTypes devType = DevTypes::HWMON; 351 std::string deviceName; 352 if (directory.parent_path() == "/sys/class/hwmon") 353 { 354 deviceName = fs::canonical(directory / "device").stem(); 355 } 356 else 357 { 358 deviceName = fs::canonical(directory).parent_path().stem(); 359 devType = DevTypes::IIO; 360 } 361 362 size_t bus = 0; 363 size_t addr = 0; 364 if (!getDeviceBusAddr(deviceName, bus, addr)) 365 { 366 continue; 367 } 368 369 const SensorBaseConfigMap* baseConfig = nullptr; 370 const SensorData* sensorData = nullptr; 371 const std::string* interfacePath = nullptr; 372 std::string sensorType; 373 size_t thresholdConfSize = 0; 374 375 for (const auto& [path, cfgData] : sensorConfigs) 376 { 377 sensorData = &cfgData; 378 for (const auto& [type, dt] : sensorTypes) 379 { 380 auto sensorBase = sensorData->find(configInterfaceName(type)); 381 if (sensorBase != sensorData->end()) 382 { 383 baseConfig = &sensorBase->second; 384 sensorType = type; 385 break; 386 } 387 } 388 if (baseConfig == nullptr) 389 { 390 std::cerr << "error finding base configuration for " 391 << deviceName << "\n"; 392 continue; 393 } 394 395 auto configBus = baseConfig->find("Bus"); 396 auto configAddress = baseConfig->find("Address"); 397 398 if (configBus == baseConfig->end() || 399 configAddress == baseConfig->end()) 400 { 401 std::cerr << "error finding necessary entry in configuration\n"; 402 continue; 403 } 404 405 const uint64_t* confBus = 406 std::get_if<uint64_t>(&(configBus->second)); 407 const uint64_t* confAddr = 408 std::get_if<uint64_t>(&(configAddress->second)); 409 if (confBus == nullptr || confAddr == nullptr) 410 { 411 std::cerr 412 << "Cannot get bus or address, invalid configuration\n"; 413 continue; 414 } 415 416 if ((*confBus != bus) || (*confAddr != addr)) 417 { 418 if constexpr (debug) 419 { 420 std::cerr << "Configuration skipping " << *confBus << "-" 421 << *confAddr << " because not " << bus << "-" 422 << addr << "\n"; 423 } 424 continue; 425 } 426 427 std::vector<thresholds::Threshold> confThresholds; 428 if (!parseThresholdsFromConfig(*sensorData, confThresholds)) 429 { 430 std::cerr << "error populating total thresholds\n"; 431 } 432 thresholdConfSize = confThresholds.size(); 433 434 interfacePath = &path.str; 435 break; 436 } 437 if (interfacePath == nullptr) 438 { 439 // To avoid this error message, add your export map entry, 440 // from Entity Manager, to sensorTypes at the top of this file. 441 std::cerr << "failed to find match for " << deviceName << "\n"; 442 continue; 443 } 444 445 auto findI2CDev = devices.find(*interfacePath); 446 447 std::shared_ptr<I2CDevice> i2cDev; 448 if (findI2CDev != devices.end()) 449 { 450 if (activateOnly && !findI2CDev->second.second) 451 { 452 continue; 453 } 454 i2cDev = findI2CDev->second.first; 455 } 456 457 auto findPSUName = baseConfig->find("Name"); 458 if (findPSUName == baseConfig->end()) 459 { 460 std::cerr << "could not determine configuration name for " 461 << deviceName << "\n"; 462 continue; 463 } 464 const std::string* psuName = 465 std::get_if<std::string>(&(findPSUName->second)); 466 if (psuName == nullptr) 467 { 468 std::cerr << "Cannot find psu name, invalid configuration\n"; 469 continue; 470 } 471 472 auto findCPU = baseConfig->find("CPURequired"); 473 if (findCPU != baseConfig->end()) 474 { 475 size_t index = std::visit(VariantToIntVisitor(), findCPU->second); 476 auto presenceFind = cpuPresence.find(index); 477 if (presenceFind == cpuPresence.end() || !presenceFind->second) 478 { 479 continue; 480 } 481 } 482 483 // on rescans, only update sensors we were signaled by 484 if (!firstScan) 485 { 486 std::string psuNameStr = "/" + escapeName(*psuName); 487 auto it = std::find_if(sensorsChanged->begin(), 488 sensorsChanged->end(), 489 [psuNameStr](std::string& s) { 490 return s.ends_with(psuNameStr); 491 }); 492 493 if (it == sensorsChanged->end()) 494 { 495 continue; 496 } 497 sensorsChanged->erase(it); 498 } 499 checkEvent(directory.string(), eventMatch, eventPathList); 500 checkGroupEvent(directory.string(), groupEventPathList); 501 502 PowerState readState = getPowerState(*baseConfig); 503 504 /* Check if there are more sensors in the same interface */ 505 int i = 1; 506 std::vector<std::string> psuNames; 507 do 508 { 509 // Individual string fields: Name, Name1, Name2, Name3, ... 510 psuNames.push_back( 511 escapeName(std::get<std::string>(findPSUName->second))); 512 findPSUName = baseConfig->find("Name" + std::to_string(i++)); 513 } while (findPSUName != baseConfig->end()); 514 515 std::vector<fs::path> sensorPaths; 516 if (!findFiles(directory, devParamMap[devType].matchRegEx, sensorPaths, 517 0)) 518 { 519 std::cerr << "No PSU non-label sensor in PSU\n"; 520 continue; 521 } 522 523 /* read max value in sysfs for in, curr, power, temp, ... */ 524 if (!findFiles(directory, R"(\w\d+_max$)", sensorPaths, 0)) 525 { 526 if constexpr (debug) 527 { 528 std::cerr << "No max name in PSU \n"; 529 } 530 } 531 532 float pollRate = getPollRate(*baseConfig, PSUSensor::defaultSensorPoll); 533 534 /* Find array of labels to be exposed if it is defined in config */ 535 std::vector<std::string> findLabels; 536 auto findLabelObj = baseConfig->find("Labels"); 537 if (findLabelObj != baseConfig->end()) 538 { 539 findLabels = 540 std::get<std::vector<std::string>>(findLabelObj->second); 541 } 542 543 std::regex sensorNameRegEx(devParamMap[devType].nameRegEx); 544 std::smatch matches; 545 546 for (const auto& sensorPath : sensorPaths) 547 { 548 bool maxLabel = false; 549 std::string labelHead; 550 std::string sensorPathStr = sensorPath.string(); 551 std::string sensorNameStr = sensorPath.filename(); 552 std::string sensorNameSubStr; 553 if (std::regex_search(sensorNameStr, matches, sensorNameRegEx)) 554 { 555 // hwmon *_input filename without number: 556 // in, curr, power, temp, ... 557 // iio in_*_raw filename without number: 558 // voltage, temp, pressure, ... 559 sensorNameSubStr = matches[devParamMap[devType].matchIndex]; 560 } 561 else 562 { 563 std::cerr << "Could not extract the alpha prefix from " 564 << sensorNameStr; 565 continue; 566 } 567 568 std::string labelPath; 569 570 if (devType == DevTypes::HWMON) 571 { 572 /* find and differentiate _max and _input to replace "label" */ 573 size_t pos = sensorPathStr.find('_'); 574 if (pos != std::string::npos) 575 { 576 std::string sensorPathStrMax = sensorPathStr.substr(pos); 577 if (sensorPathStrMax == "_max") 578 { 579 labelPath = boost::replace_all_copy(sensorPathStr, 580 "max", "label"); 581 maxLabel = true; 582 } 583 else 584 { 585 labelPath = boost::replace_all_copy(sensorPathStr, 586 "input", "label"); 587 maxLabel = false; 588 } 589 } 590 else 591 { 592 continue; 593 } 594 595 std::ifstream labelFile(labelPath); 596 if (!labelFile.good()) 597 { 598 if constexpr (debug) 599 { 600 std::cerr << "Input file " << sensorPath 601 << " has no corresponding label file\n"; 602 } 603 // hwmon *_input filename with number: 604 // temp1, temp2, temp3, ... 605 labelHead = sensorNameStr.substr(0, 606 sensorNameStr.find('_')); 607 } 608 else 609 { 610 std::string label; 611 std::getline(labelFile, label); 612 labelFile.close(); 613 auto findSensor = sensors.find(label); 614 if (findSensor != sensors.end()) 615 { 616 continue; 617 } 618 619 // hwmon corresponding *_label file contents: 620 // vin1, vout1, ... 621 labelHead = label.substr(0, label.find(' ')); 622 } 623 624 /* append "max" for labelMatch */ 625 if (maxLabel) 626 { 627 labelHead.insert(0, "max"); 628 } 629 630 checkPWMSensor(sensorPath, labelHead, *interfacePath, 631 dbusConnection, objectServer, psuNames[0]); 632 } 633 else if (devType == DevTypes::IIO) 634 { 635 auto findIIOHyphen = sensorNameStr.find_last_of('_'); 636 labelHead = sensorNameStr.substr(0, findIIOHyphen); 637 } 638 639 if constexpr (debug) 640 { 641 std::cerr << "Sensor type=\"" << sensorNameSubStr 642 << "\" label=\"" << labelHead << "\"\n"; 643 } 644 645 if (!findLabels.empty()) 646 { 647 /* Check if this labelHead is enabled in config file */ 648 if (std::find(findLabels.begin(), findLabels.end(), 649 labelHead) == findLabels.end()) 650 { 651 if constexpr (debug) 652 { 653 std::cerr << "could not find " << labelHead 654 << " in the Labels list\n"; 655 } 656 continue; 657 } 658 } 659 660 auto findProperty = labelMatch.find(sensorNameSubStr); 661 if (findProperty == labelMatch.end()) 662 { 663 if constexpr (debug) 664 { 665 std::cerr << "Could not find matching default property for " 666 << sensorNameSubStr << "\n"; 667 } 668 continue; 669 } 670 671 // Protect the hardcoded labelMatch list from changes, 672 // by making a copy and modifying that instead. 673 // Avoid bleedthrough of one device's customizations to 674 // the next device, as each should be independently customizable. 675 PSUProperty psuProperty = findProperty->second; 676 677 // Use label head as prefix for reading from config file, 678 // example if temp1: temp1_Name, temp1_Scale, temp1_Min, ... 679 std::string keyName = labelHead + "_Name"; 680 std::string keyScale = labelHead + "_Scale"; 681 std::string keyMin = labelHead + "_Min"; 682 std::string keyMax = labelHead + "_Max"; 683 std::string keyOffset = labelHead + "_Offset"; 684 std::string keyPowerState = labelHead + "_PowerState"; 685 686 bool customizedName = false; 687 auto findCustomName = baseConfig->find(keyName); 688 if (findCustomName != baseConfig->end()) 689 { 690 try 691 { 692 psuProperty.labelTypeName = std::visit( 693 VariantToStringVisitor(), findCustomName->second); 694 } 695 catch (const std::invalid_argument&) 696 { 697 std::cerr << "Unable to parse " << keyName << "\n"; 698 continue; 699 } 700 701 // All strings are valid, including empty string 702 customizedName = true; 703 } 704 705 bool customizedScale = false; 706 auto findCustomScale = baseConfig->find(keyScale); 707 if (findCustomScale != baseConfig->end()) 708 { 709 try 710 { 711 psuProperty.sensorScaleFactor = std::visit( 712 VariantToUnsignedIntVisitor(), findCustomScale->second); 713 } 714 catch (const std::invalid_argument&) 715 { 716 std::cerr << "Unable to parse " << keyScale << "\n"; 717 continue; 718 } 719 720 // Avoid later division by zero 721 if (psuProperty.sensorScaleFactor > 0) 722 { 723 customizedScale = true; 724 } 725 else 726 { 727 std::cerr << "Unable to accept " << keyScale << "\n"; 728 continue; 729 } 730 } 731 732 auto findCustomMin = baseConfig->find(keyMin); 733 if (findCustomMin != baseConfig->end()) 734 { 735 try 736 { 737 psuProperty.minReading = std::visit( 738 VariantToDoubleVisitor(), findCustomMin->second); 739 } 740 catch (const std::invalid_argument&) 741 { 742 std::cerr << "Unable to parse " << keyMin << "\n"; 743 continue; 744 } 745 } 746 747 auto findCustomMax = baseConfig->find(keyMax); 748 if (findCustomMax != baseConfig->end()) 749 { 750 try 751 { 752 psuProperty.maxReading = std::visit( 753 VariantToDoubleVisitor(), findCustomMax->second); 754 } 755 catch (const std::invalid_argument&) 756 { 757 std::cerr << "Unable to parse " << keyMax << "\n"; 758 continue; 759 } 760 } 761 762 auto findCustomOffset = baseConfig->find(keyOffset); 763 if (findCustomOffset != baseConfig->end()) 764 { 765 try 766 { 767 psuProperty.sensorOffset = std::visit( 768 VariantToDoubleVisitor(), findCustomOffset->second); 769 } 770 catch (const std::invalid_argument&) 771 { 772 std::cerr << "Unable to parse " << keyOffset << "\n"; 773 continue; 774 } 775 } 776 777 // if we find label head power state set ,override the powerstate. 778 auto findPowerState = baseConfig->find(keyPowerState); 779 if (findPowerState != baseConfig->end()) 780 { 781 std::string powerState = std::visit(VariantToStringVisitor(), 782 findPowerState->second); 783 setReadState(powerState, readState); 784 } 785 if (!(psuProperty.minReading < psuProperty.maxReading)) 786 { 787 std::cerr << "Min must be less than Max\n"; 788 continue; 789 } 790 791 // If the sensor name is being customized by config file, 792 // then prefix/suffix composition becomes not necessary, 793 // and in fact not wanted, because it gets in the way. 794 std::string psuNameFromIndex; 795 std::string nameIndexStr = "1"; 796 if (!customizedName) 797 { 798 /* Find out sensor name index for this label */ 799 std::regex rgx("[A-Za-z]+([0-9]+)"); 800 size_t nameIndex{0}; 801 if (std::regex_search(labelHead, matches, rgx)) 802 { 803 nameIndexStr = matches[1]; 804 nameIndex = std::stoi(nameIndexStr); 805 806 // Decrement to preserve alignment, because hwmon 807 // human-readable filenames and labels use 1-based 808 // numbering, but the "Name", "Name1", "Name2", etc. naming 809 // convention (the psuNames vector) uses 0-based numbering. 810 if (nameIndex > 0) 811 { 812 --nameIndex; 813 } 814 } 815 else 816 { 817 nameIndex = 0; 818 } 819 820 if (psuNames.size() <= nameIndex) 821 { 822 std::cerr << "Could not pair " << labelHead 823 << " with a Name field\n"; 824 continue; 825 } 826 827 psuNameFromIndex = psuNames[nameIndex]; 828 829 if constexpr (debug) 830 { 831 std::cerr << "Sensor label head " << labelHead 832 << " paired with " << psuNameFromIndex 833 << " at index " << nameIndex << "\n"; 834 } 835 } 836 837 if (devType == DevTypes::HWMON) 838 { 839 checkEventLimits(sensorPathStr, limitEventMatch, eventPathList); 840 } 841 842 // Similarly, if sensor scaling factor is being customized, 843 // then the below power-of-10 constraint becomes unnecessary, 844 // as config should be able to specify an arbitrary divisor. 845 unsigned int factor = psuProperty.sensorScaleFactor; 846 if (!customizedScale) 847 { 848 // Preserve existing usage of hardcoded labelMatch table below 849 factor = std::pow(10.0, factor); 850 851 /* Change first char of substring to uppercase */ 852 char firstChar = 853 static_cast<char>(std::toupper(sensorNameSubStr[0])); 854 std::string strScaleFactor = 855 firstChar + sensorNameSubStr.substr(1) + "ScaleFactor"; 856 857 // Preserve existing configs by accepting earlier syntax, 858 // example CurrScaleFactor, PowerScaleFactor, ... 859 auto findScaleFactor = baseConfig->find(strScaleFactor); 860 if (findScaleFactor != baseConfig->end()) 861 { 862 factor = std::visit(VariantToIntVisitor(), 863 findScaleFactor->second); 864 } 865 866 if constexpr (debug) 867 { 868 std::cerr << "Sensor scaling factor " << factor 869 << " string " << strScaleFactor << "\n"; 870 } 871 } 872 873 std::vector<thresholds::Threshold> sensorThresholds; 874 if (!parseThresholdsFromConfig(*sensorData, sensorThresholds, 875 &labelHead)) 876 { 877 std::cerr << "error populating thresholds for " 878 << sensorNameSubStr << "\n"; 879 } 880 881 auto findSensorUnit = sensorTable.find(sensorNameSubStr); 882 if (findSensorUnit == sensorTable.end()) 883 { 884 std::cerr << sensorNameSubStr 885 << " is not a recognized sensor type\n"; 886 continue; 887 } 888 889 if constexpr (debug) 890 { 891 std::cerr << "Sensor properties: Name \"" 892 << psuProperty.labelTypeName << "\" Scale " 893 << psuProperty.sensorScaleFactor << " Min " 894 << psuProperty.minReading << " Max " 895 << psuProperty.maxReading << " Offset " 896 << psuProperty.sensorOffset << "\n"; 897 } 898 899 std::string sensorName = psuProperty.labelTypeName; 900 if (customizedName) 901 { 902 if (sensorName.empty()) 903 { 904 // Allow selective disabling of an individual sensor, 905 // by customizing its name to an empty string. 906 std::cerr << "Sensor disabled, empty string\n"; 907 continue; 908 } 909 } 910 else 911 { 912 // Sensor name not customized, do prefix/suffix composition, 913 // preserving default behavior by using psuNameFromIndex. 914 sensorName = psuNameFromIndex + " " + psuProperty.labelTypeName; 915 916 // The labelTypeName of a fan can be: 917 // "Fan Speed 1", "Fan Speed 2", "Fan Speed 3" ... 918 if (labelHead == "fan" + nameIndexStr) 919 { 920 sensorName += nameIndexStr; 921 } 922 } 923 924 if constexpr (debug) 925 { 926 std::cerr << "Sensor name \"" << sensorName << "\" path \"" 927 << sensorPathStr << "\" type \"" << sensorType 928 << "\"\n"; 929 } 930 // destruct existing one first if already created 931 932 auto& sensor = sensors[sensorName]; 933 if (!activateOnly) 934 { 935 sensor = nullptr; 936 } 937 938 if (sensor != nullptr) 939 { 940 sensor->activate(sensorPathStr, i2cDev); 941 } 942 else 943 { 944 sensors[sensorName] = std::make_shared<PSUSensor>( 945 sensorPathStr, sensorType, objectServer, dbusConnection, io, 946 sensorName, std::move(sensorThresholds), *interfacePath, 947 readState, findSensorUnit->second, factor, 948 psuProperty.maxReading, psuProperty.minReading, 949 psuProperty.sensorOffset, labelHead, thresholdConfSize, 950 pollRate, i2cDev); 951 sensors[sensorName]->setupRead(); 952 ++numCreated; 953 if constexpr (debug) 954 { 955 std::cerr << "Created " << numCreated 956 << " sensors so far\n"; 957 } 958 } 959 } 960 961 if (devType == DevTypes::HWMON) 962 { 963 // OperationalStatus event 964 combineEvents[*psuName + "OperationalStatus"] = nullptr; 965 combineEvents[*psuName + "OperationalStatus"] = 966 std::make_unique<PSUCombineEvent>( 967 objectServer, dbusConnection, io, *psuName, readState, 968 eventPathList, groupEventPathList, "OperationalStatus", 969 pollRate); 970 } 971 } 972 973 if constexpr (debug) 974 { 975 std::cerr << "Created total of " << numCreated << " sensors\n"; 976 } 977 } 978 979 static void 980 getPresentCpus(std::shared_ptr<sdbusplus::asio::connection>& dbusConnection) 981 { 982 static const int depth = 2; 983 static const int numKeys = 1; 984 GetSubTreeType cpuSubTree; 985 986 try 987 { 988 auto getItems = dbusConnection->new_method_call( 989 mapper::busName, mapper::path, mapper::interface, mapper::subtree); 990 getItems.append(cpuInventoryPath, static_cast<int32_t>(depth), 991 std::array<const char*, numKeys>{ 992 "xyz.openbmc_project.Inventory.Item"}); 993 auto getItemsResp = dbusConnection->call(getItems); 994 getItemsResp.read(cpuSubTree); 995 } 996 catch (sdbusplus::exception_t& e) 997 { 998 std::cerr << "error getting inventory item subtree: " << e.what() 999 << "\n"; 1000 return; 1001 } 1002 1003 for (const auto& [path, objDict] : cpuSubTree) 1004 { 1005 auto obj = sdbusplus::message::object_path(path).filename(); 1006 if (!obj.starts_with("cpu") || objDict.empty()) 1007 { 1008 continue; 1009 } 1010 const std::string& owner = objDict.begin()->first; 1011 1012 std::variant<bool> respValue; 1013 try 1014 { 1015 auto getPresence = dbusConnection->new_method_call( 1016 owner.c_str(), path.c_str(), "org.freedesktop.DBus.Properties", 1017 "Get"); 1018 getPresence.append("xyz.openbmc_project.Inventory.Item", "Present"); 1019 auto resp = dbusConnection->call(getPresence); 1020 resp.read(respValue); 1021 } 1022 catch (sdbusplus::exception_t& e) 1023 { 1024 std::cerr << "Error in getting CPU presence: " << e.what() << "\n"; 1025 continue; 1026 } 1027 1028 auto* present = std::get_if<bool>(&respValue); 1029 if (present != nullptr && *present) 1030 { 1031 int cpuIndex = 0; 1032 try 1033 { 1034 cpuIndex = std::stoi(obj.substr(obj.find_last_of("cpu") + 1)); 1035 } 1036 catch (const std::exception& e) 1037 { 1038 std::cerr << "Error converting CPU index, " << e.what() << '\n'; 1039 continue; 1040 } 1041 cpuPresence[cpuIndex + 1] = *present; 1042 } 1043 } 1044 } 1045 1046 void createSensors( 1047 boost::asio::io_context& io, sdbusplus::asio::object_server& objectServer, 1048 std::shared_ptr<sdbusplus::asio::connection>& dbusConnection, 1049 const std::shared_ptr<boost::container::flat_set<std::string>>& 1050 sensorsChanged, 1051 bool activateOnly) 1052 { 1053 auto getter = std::make_shared<GetSensorConfiguration>( 1054 dbusConnection, [&io, &objectServer, &dbusConnection, sensorsChanged, 1055 activateOnly](const ManagedObjectType& sensorConfigs) { 1056 createSensorsCallback(io, objectServer, dbusConnection, sensorConfigs, 1057 sensorsChanged, activateOnly); 1058 }); 1059 std::vector<std::string> types(sensorTypes.size()); 1060 for (const auto& [type, dt] : sensorTypes) 1061 { 1062 types.push_back(type); 1063 } 1064 getter->getConfiguration(types); 1065 } 1066 1067 void propertyInitialize() 1068 { 1069 sensorTable = {{"power", sensor_paths::unitWatts}, 1070 {"curr", sensor_paths::unitAmperes}, 1071 {"temp", sensor_paths::unitDegreesC}, 1072 {"in", sensor_paths::unitVolts}, 1073 {"voltage", sensor_paths::unitVolts}, 1074 {"fan", sensor_paths::unitRPMs}}; 1075 1076 labelMatch = { 1077 {"pin", PSUProperty("Input Power", 3000, 0, 6, 0)}, 1078 {"pout", PSUProperty("Output Power", 3000, 0, 6, 0)}, 1079 {"power", PSUProperty("Output Power", 3000, 0, 6, 0)}, 1080 {"maxpin", PSUProperty("Max Input Power", 3000, 0, 6, 0)}, 1081 {"vin", PSUProperty("Input Voltage", 300, 0, 3, 0)}, 1082 {"maxvin", PSUProperty("Max Input Voltage", 300, 0, 3, 0)}, 1083 {"in_voltage", PSUProperty("Output Voltage", 255, 0, 3, 0)}, 1084 {"vout", PSUProperty("Output Voltage", 255, 0, 3, 0)}, 1085 {"vmon", PSUProperty("Auxiliary Input Voltage", 255, 0, 3, 0)}, 1086 {"in", PSUProperty("Output Voltage", 255, 0, 3, 0)}, 1087 {"iin", PSUProperty("Input Current", 20, 0, 3, 0)}, 1088 {"iout", PSUProperty("Output Current", 255, 0, 3, 0)}, 1089 {"curr", PSUProperty("Output Current", 255, 0, 3, 0)}, 1090 {"maxiout", PSUProperty("Max Output Current", 255, 0, 3, 0)}, 1091 {"temp", PSUProperty("Temperature", 127, -128, 3, 0)}, 1092 {"maxtemp", PSUProperty("Max Temperature", 127, -128, 3, 0)}, 1093 {"fan", PSUProperty("Fan Speed ", 30000, 0, 0, 0)}}; 1094 1095 limitEventMatch = {{"PredictiveFailure", {"max_alarm", "min_alarm"}}, 1096 {"Failure", {"crit_alarm", "lcrit_alarm"}}}; 1097 1098 eventMatch = {{"PredictiveFailure", {"power1_alarm"}}, 1099 {"Failure", {"in2_alarm"}}, 1100 {"ACLost", {"in1_beep"}}, 1101 {"ConfigureError", {"in1_fault"}}}; 1102 1103 devParamMap = { 1104 {DevTypes::HWMON, {1, R"(\w\d+_input$)", "([A-Za-z]+)[0-9]*_"}}, 1105 {DevTypes::IIO, 1106 {2, R"(\w+_(raw|input)$)", "^(in|out)_([A-Za-z]+)[0-9]*_"}}}; 1107 } 1108 1109 static void powerStateChanged( 1110 PowerState type, bool newState, 1111 boost::container::flat_map<std::string, std::shared_ptr<PSUSensor>>& 1112 sensors, 1113 boost::asio::io_context& io, sdbusplus::asio::object_server& objectServer, 1114 std::shared_ptr<sdbusplus::asio::connection>& dbusConnection) 1115 { 1116 if (newState) 1117 { 1118 createSensors(io, objectServer, dbusConnection, nullptr, true); 1119 } 1120 else 1121 { 1122 for (auto& [path, sensor] : sensors) 1123 { 1124 if (sensor != nullptr && sensor->readState == type) 1125 { 1126 sensor->deactivate(); 1127 } 1128 } 1129 } 1130 } 1131 1132 int main() 1133 { 1134 boost::asio::io_context io; 1135 auto systemBus = std::make_shared<sdbusplus::asio::connection>(io); 1136 1137 sdbusplus::asio::object_server objectServer(systemBus, true); 1138 objectServer.add_manager("/xyz/openbmc_project/sensors"); 1139 objectServer.add_manager("/xyz/openbmc_project/control"); 1140 systemBus->request_name("xyz.openbmc_project.PSUSensor"); 1141 auto sensorsChanged = 1142 std::make_shared<boost::container::flat_set<std::string>>(); 1143 1144 propertyInitialize(); 1145 1146 auto powerCallBack = [&io, &objectServer, &systemBus](PowerState type, 1147 bool state) { 1148 powerStateChanged(type, state, sensors, io, objectServer, systemBus); 1149 }; 1150 1151 setupPowerMatchCallback(systemBus, powerCallBack); 1152 1153 boost::asio::post(io, [&]() { 1154 createSensors(io, objectServer, systemBus, nullptr, false); 1155 }); 1156 boost::asio::steady_timer filterTimer(io); 1157 std::function<void(sdbusplus::message_t&)> eventHandler = 1158 [&](sdbusplus::message_t& message) { 1159 if (message.is_method_error()) 1160 { 1161 std::cerr << "callback method error\n"; 1162 return; 1163 } 1164 sensorsChanged->insert(message.get_path()); 1165 filterTimer.expires_after(std::chrono::seconds(3)); 1166 filterTimer.async_wait([&](const boost::system::error_code& ec) { 1167 if (ec == boost::asio::error::operation_aborted) 1168 { 1169 return; 1170 } 1171 if (ec) 1172 { 1173 std::cerr << "timer error\n"; 1174 } 1175 createSensors(io, objectServer, systemBus, sensorsChanged, false); 1176 }); 1177 }; 1178 1179 boost::asio::steady_timer cpuFilterTimer(io); 1180 std::function<void(sdbusplus::message_t&)> cpuPresenceHandler = 1181 [&](sdbusplus::message_t& message) { 1182 std::string path = message.get_path(); 1183 boost::to_lower(path); 1184 1185 sdbusplus::message::object_path cpuPath(path); 1186 std::string cpuName = cpuPath.filename(); 1187 if (!cpuName.starts_with("cpu")) 1188 { 1189 return; 1190 } 1191 size_t index = 0; 1192 try 1193 { 1194 index = std::stoi(path.substr(path.size() - 1)); 1195 } 1196 catch (const std::invalid_argument&) 1197 { 1198 std::cerr << "Found invalid path " << path << "\n"; 1199 return; 1200 } 1201 1202 std::string objectName; 1203 boost::container::flat_map<std::string, std::variant<bool>> values; 1204 message.read(objectName, values); 1205 auto findPresence = values.find("Present"); 1206 try 1207 { 1208 cpuPresence[index] = std::get<bool>(findPresence->second); 1209 } 1210 catch (const std::bad_variant_access& err) 1211 { 1212 return; 1213 } 1214 1215 if (!cpuPresence[index]) 1216 { 1217 return; 1218 } 1219 cpuFilterTimer.expires_after(std::chrono::seconds(1)); 1220 cpuFilterTimer.async_wait([&](const boost::system::error_code& ec) { 1221 if (ec == boost::asio::error::operation_aborted) 1222 { 1223 return; 1224 } 1225 if (ec) 1226 { 1227 std::cerr << "timer error\n"; 1228 return; 1229 } 1230 createSensors(io, objectServer, systemBus, nullptr, false); 1231 }); 1232 }; 1233 1234 std::vector<std::unique_ptr<sdbusplus::bus::match_t>> matches = 1235 setupPropertiesChangedMatches(*systemBus, sensorTypes, eventHandler); 1236 1237 matches.emplace_back(std::make_unique<sdbusplus::bus::match_t>( 1238 static_cast<sdbusplus::bus_t&>(*systemBus), 1239 "type='signal',member='PropertiesChanged',path_namespace='" + 1240 std::string(cpuInventoryPath) + 1241 "',arg0namespace='xyz.openbmc_project.Inventory.Item'", 1242 cpuPresenceHandler)); 1243 1244 getPresentCpus(systemBus); 1245 1246 setupManufacturingModeMatch(*systemBus); 1247 io.run(); 1248 } 1249