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