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