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