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