| /* |
| // Copyright (c) 2019 Intel Corporation |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| */ |
| |
| #include "DeviceMgmt.hpp" |
| #include "PSUEvent.hpp" |
| #include "PSUSensor.hpp" |
| #include "PwmSensor.hpp" |
| #include "SensorPaths.hpp" |
| #include "Thresholds.hpp" |
| #include "Utils.hpp" |
| #include "VariantVisitors.hpp" |
| |
| #include <boost/algorithm/string/case_conv.hpp> |
| #include <boost/algorithm/string/replace.hpp> |
| #include <boost/asio/error.hpp> |
| #include <boost/asio/io_context.hpp> |
| #include <boost/asio/post.hpp> |
| #include <boost/asio/steady_timer.hpp> |
| #include <boost/container/flat_map.hpp> |
| #include <boost/container/flat_set.hpp> |
| #include <sdbusplus/asio/connection.hpp> |
| #include <sdbusplus/asio/object_server.hpp> |
| #include <sdbusplus/bus.hpp> |
| #include <sdbusplus/bus/match.hpp> |
| #include <sdbusplus/exception.hpp> |
| #include <sdbusplus/message.hpp> |
| #include <sdbusplus/message/native_types.hpp> |
| |
| #include <algorithm> |
| #include <array> |
| #include <cctype> |
| #include <chrono> |
| #include <cmath> |
| #include <cstddef> |
| #include <cstdint> |
| #include <exception> |
| #include <filesystem> |
| #include <fstream> |
| #include <functional> |
| #include <iostream> |
| #include <memory> |
| #include <regex> |
| #include <stdexcept> |
| #include <string> |
| #include <string_view> |
| #include <utility> |
| #include <variant> |
| #include <vector> |
| |
| static constexpr bool debug = false; |
| static std::regex i2cDevRegex(R"((\/i2c\-\d+\/\d+-[a-fA-F0-9]{4,4})(\/|$))"); |
| |
| static const I2CDeviceTypeMap sensorTypes{ |
| {"ADC128D818", I2CDeviceType{"adc128d818", true}}, |
| {"ADM1266", I2CDeviceType{"adm1266", true}}, |
| {"ADM1272", I2CDeviceType{"adm1272", true}}, |
| {"ADM1275", I2CDeviceType{"adm1275", true}}, |
| {"ADM1278", I2CDeviceType{"adm1278", true}}, |
| {"ADM1293", I2CDeviceType{"adm1293", true}}, |
| {"ADS1015", I2CDeviceType{"ads1015", true}}, |
| {"ADS7830", I2CDeviceType{"ads7830", true}}, |
| {"AHE50DC_FAN", I2CDeviceType{"ahe50dc_fan", true}}, |
| {"BMR490", I2CDeviceType{"bmr490", true}}, |
| {"cffps", I2CDeviceType{"cffps", true}}, |
| {"cffps1", I2CDeviceType{"cffps", true}}, |
| {"cffps2", I2CDeviceType{"cffps", true}}, |
| {"cffps3", I2CDeviceType{"cffps", true}}, |
| {"DPS800", I2CDeviceType{"dps800", true}}, |
| {"INA219", I2CDeviceType{"ina219", true}}, |
| {"INA230", I2CDeviceType{"ina230", true}}, |
| {"INA238", I2CDeviceType{"ina238", true}}, |
| {"IPSPS1", I2CDeviceType{"ipsps1", true}}, |
| {"IR38060", I2CDeviceType{"ir38060", true}}, |
| {"IR38164", I2CDeviceType{"ir38164", true}}, |
| {"IR38263", I2CDeviceType{"ir38263", true}}, |
| {"ISL28022", I2CDeviceType{"isl28022", true}}, |
| {"ISL68137", I2CDeviceType{"isl68137", true}}, |
| {"ISL68220", I2CDeviceType{"isl68220", true}}, |
| {"ISL68223", I2CDeviceType{"isl68223", true}}, |
| {"ISL69225", I2CDeviceType{"isl69225", true}}, |
| {"ISL69243", I2CDeviceType{"isl69243", true}}, |
| {"ISL69260", I2CDeviceType{"isl69260", true}}, |
| {"LM25066", I2CDeviceType{"lm25066", true}}, |
| {"LTC2945", I2CDeviceType{"ltc2945", true}}, |
| {"LTC4286", I2CDeviceType{"ltc4286", true}}, |
| {"LTC4287", I2CDeviceType{"ltc4287", true}}, |
| {"MAX5970", I2CDeviceType{"max5970", true}}, |
| {"MAX11607", I2CDeviceType{"max11607", false}}, |
| {"MAX11615", I2CDeviceType{"max11615", false}}, |
| {"MAX11617", I2CDeviceType{"max11617", false}}, |
| {"MAX16601", I2CDeviceType{"max16601", true}}, |
| {"MAX20710", I2CDeviceType{"max20710", true}}, |
| {"MAX20730", I2CDeviceType{"max20730", true}}, |
| {"MAX20734", I2CDeviceType{"max20734", true}}, |
| {"MAX20796", I2CDeviceType{"max20796", true}}, |
| {"MAX34451", I2CDeviceType{"max34451", true}}, |
| {"MP2856", I2CDeviceType{"mp2856", true}}, |
| {"MP2857", I2CDeviceType{"mp2857", true}}, |
| {"MP2971", I2CDeviceType{"mp2971", true}}, |
| {"MP2973", I2CDeviceType{"mp2973", true}}, |
| {"MP2975", I2CDeviceType{"mp2975", true}}, |
| {"MP5023", I2CDeviceType{"mp5023", true}}, |
| {"MP5990", I2CDeviceType{"mp5990", true}}, |
| {"MPQ8785", I2CDeviceType{"mpq8785", true}}, |
| {"NCP4200", I2CDeviceType{"ncp4200", true}}, |
| {"PLI1209BC", I2CDeviceType{"pli1209bc", true}}, |
| {"pmbus", I2CDeviceType{"pmbus", true}}, |
| {"PXE1610", I2CDeviceType{"pxe1610", true}}, |
| {"RAA228000", I2CDeviceType{"raa228000", true}}, |
| {"RAA228004", I2CDeviceType{"raa228004", true}}, |
| {"RAA228228", I2CDeviceType{"raa228228", true}}, |
| {"RAA228620", I2CDeviceType{"raa228620", true}}, |
| {"RAA229001", I2CDeviceType{"raa229001", true}}, |
| {"RAA229004", I2CDeviceType{"raa229004", true}}, |
| {"RAA229126", I2CDeviceType{"raa229126", true}}, |
| {"RTQ6056", I2CDeviceType{"rtq6056", false}}, |
| {"SBRMI", I2CDeviceType{"sbrmi", true}}, |
| {"smpro_hwmon", I2CDeviceType{"smpro", false}}, |
| {"TDA38640", I2CDeviceType{"tda38640", true}}, |
| {"TPS53679", I2CDeviceType{"tps53679", true}}, |
| {"TPS546D24", I2CDeviceType{"tps546d24", true}}, |
| {"XDP710", I2CDeviceType{"xdp710", true}}, |
| {"XDPE11280", I2CDeviceType{"xdpe11280", true}}, |
| {"XDPE12284", I2CDeviceType{"xdpe12284", true}}, |
| {"XDPE152C4", I2CDeviceType{"xdpe152c4", true}}, |
| }; |
| |
| enum class DevTypes |
| { |
| Unknown = 0, |
| HWMON, |
| IIO |
| }; |
| |
| struct DevParams |
| { |
| unsigned int matchIndex = 0; |
| std::string matchRegEx; |
| std::string nameRegEx; |
| }; |
| |
| namespace fs = std::filesystem; |
| |
| static boost::container::flat_map<std::string, std::shared_ptr<PSUSensor>> |
| sensors; |
| static boost::container::flat_map<std::string, std::unique_ptr<PSUCombineEvent>> |
| combineEvents; |
| static boost::container::flat_map<std::string, std::unique_ptr<PwmSensor>> |
| pwmSensors; |
| static boost::container::flat_map<std::string, std::string> sensorTable; |
| static boost::container::flat_map<std::string, PSUProperty> labelMatch; |
| static EventPathList eventMatch; |
| static EventPathList limitEventMatch; |
| |
| static boost::container::flat_map<size_t, bool> cpuPresence; |
| static boost::container::flat_map<DevTypes, DevParams> devParamMap; |
| |
| // Function CheckEvent will check each attribute from eventMatch table in the |
| // sysfs. If the attributes exists in sysfs, then store the complete path |
| // of the attribute into eventPathList. |
| void checkEvent(const std::string& directory, const EventPathList& eventMatch, |
| EventPathList& eventPathList) |
| { |
| for (const auto& match : eventMatch) |
| { |
| const std::vector<std::string>& eventAttrs = match.second; |
| const std::string& eventName = match.first; |
| for (const auto& eventAttr : eventAttrs) |
| { |
| std::string eventPath = directory; |
| eventPath += "/"; |
| eventPath += eventAttr; |
| |
| std::ifstream eventFile(eventPath); |
| if (!eventFile.good()) |
| { |
| continue; |
| } |
| |
| eventPathList[eventName].push_back(eventPath); |
| } |
| } |
| } |
| |
| // Check Group Events which contains more than one targets in each combine |
| // events. |
| void checkGroupEvent(const std::string& directory, |
| GroupEventPathList& groupEventPathList) |
| { |
| EventPathList pathList; |
| std::vector<fs::path> eventPaths; |
| if (!findFiles(fs::path(directory), R"(fan\d+_(alarm|fault))", eventPaths)) |
| { |
| return; |
| } |
| |
| for (const auto& eventPath : eventPaths) |
| { |
| std::string attrName = eventPath.filename(); |
| pathList[attrName.substr(0, attrName.find('_'))].push_back(eventPath); |
| } |
| groupEventPathList["FanFault"] = pathList; |
| } |
| |
| // Function checkEventLimits will check all the psu related xxx_input attributes |
| // in sysfs to see if xxx_crit_alarm xxx_lcrit_alarm xxx_max_alarm |
| // xxx_min_alarm exist, then store the existing paths of the alarm attributes |
| // to eventPathList. |
| void checkEventLimits(const std::string& sensorPathStr, |
| const EventPathList& limitEventMatch, |
| EventPathList& eventPathList) |
| { |
| auto attributePartPos = sensorPathStr.find_last_of('_'); |
| if (attributePartPos == std::string::npos) |
| { |
| // There is no '_' in the string, skip it |
| return; |
| } |
| auto attributePart = |
| std::string_view(sensorPathStr).substr(attributePartPos + 1); |
| if (attributePart != "input") |
| { |
| // If the sensor is not xxx_input, skip it |
| return; |
| } |
| |
| auto prefixPart = sensorPathStr.substr(0, attributePartPos + 1); |
| for (const auto& limitMatch : limitEventMatch) |
| { |
| const std::vector<std::string>& limitEventAttrs = limitMatch.second; |
| const std::string& eventName = limitMatch.first; |
| for (const auto& limitEventAttr : limitEventAttrs) |
| { |
| auto limitEventPath = prefixPart + limitEventAttr; |
| std::ifstream eventFile(limitEventPath); |
| if (!eventFile.good()) |
| { |
| continue; |
| } |
| eventPathList[eventName].push_back(limitEventPath); |
| } |
| } |
| } |
| |
| static void checkPWMSensor( |
| const fs::path& sensorPath, std::string& labelHead, |
| const std::string& interfacePath, |
| std::shared_ptr<sdbusplus::asio::connection>& dbusConnection, |
| sdbusplus::asio::object_server& objectServer, const std::string& psuName) |
| { |
| if (!labelHead.starts_with("fan")) |
| { |
| return; |
| } |
| std::string labelHeadIndex = labelHead.substr(3); |
| |
| const std::string& sensorPathStr = sensorPath.string(); |
| const std::string& pwmPathStr = |
| boost::replace_all_copy(sensorPathStr, "input", "target"); |
| std::ifstream pwmFile(pwmPathStr); |
| if (!pwmFile.good()) |
| { |
| return; |
| } |
| |
| auto findPWMSensor = pwmSensors.find(psuName + labelHead); |
| if (findPWMSensor != pwmSensors.end()) |
| { |
| return; |
| } |
| |
| std::string name = "Pwm_"; |
| name += psuName; |
| name += "_Fan_"; |
| name += labelHeadIndex; |
| |
| std::string objPath = interfacePath; |
| objPath += "_Fan_"; |
| objPath += labelHeadIndex; |
| |
| pwmSensors[psuName + labelHead] = std::make_unique<PwmSensor>( |
| name, pwmPathStr, dbusConnection, objectServer, objPath, "PSU"); |
| } |
| |
| static void createSensorsCallback( |
| boost::asio::io_context& io, sdbusplus::asio::object_server& objectServer, |
| std::shared_ptr<sdbusplus::asio::connection>& dbusConnection, |
| const ManagedObjectType& sensorConfigs, |
| const std::shared_ptr<boost::container::flat_set<std::string>>& |
| sensorsChanged, |
| bool activateOnly) |
| { |
| int numCreated = 0; |
| bool firstScan = sensorsChanged == nullptr; |
| |
| auto devices = instantiateDevices(sensorConfigs, sensors, sensorTypes); |
| |
| std::vector<fs::path> pmbusPaths; |
| findFiles(fs::path("/sys/bus/iio/devices"), "name", pmbusPaths); |
| findFiles(fs::path("/sys/class/hwmon"), "name", pmbusPaths); |
| if (pmbusPaths.empty()) |
| { |
| std::cerr << "No PSU sensors in system\n"; |
| return; |
| } |
| |
| boost::container::flat_set<std::string> directories; |
| for (const auto& pmbusPath : pmbusPaths) |
| { |
| EventPathList eventPathList; |
| GroupEventPathList groupEventPathList; |
| |
| std::ifstream nameFile(pmbusPath); |
| if (!nameFile.good()) |
| { |
| std::cerr << "Failure finding pmbus path " << pmbusPath << "\n"; |
| continue; |
| } |
| |
| std::string pmbusName; |
| std::getline(nameFile, pmbusName); |
| nameFile.close(); |
| |
| if (sensorTypes.find(pmbusName) == sensorTypes.end()) |
| { |
| // To avoid this error message, add your driver name to |
| // the pmbusNames vector at the top of this file. |
| std::cerr << "Driver name " << pmbusName |
| << " not found in sensor whitelist\n"; |
| continue; |
| } |
| |
| auto directory = pmbusPath.parent_path(); |
| |
| auto ret = directories.insert(directory.string()); |
| if (!ret.second) |
| { |
| std::cerr << "Duplicate path " << directory.string() << "\n"; |
| continue; // check if path has already been searched |
| } |
| |
| DevTypes devType = DevTypes::HWMON; |
| std::string deviceName; |
| if (directory.parent_path() == "/sys/class/hwmon") |
| { |
| std::string devicePath = fs::canonical(directory / "device"); |
| std::smatch match; |
| // Find /i2c-<bus>/<bus>-<address> match in device path |
| std::regex_search(devicePath, match, i2cDevRegex); |
| if (match.empty()) |
| { |
| std::cerr << "Found bad device path " << devicePath << "\n"; |
| continue; |
| } |
| // Extract <bus>-<address> |
| std::string matchStr = match[1]; |
| deviceName = matchStr.substr(matchStr.find_last_of('/') + 1); |
| } |
| else |
| { |
| deviceName = fs::canonical(directory).parent_path().stem(); |
| devType = DevTypes::IIO; |
| } |
| |
| size_t bus = 0; |
| size_t addr = 0; |
| if (!getDeviceBusAddr(deviceName, bus, addr)) |
| { |
| continue; |
| } |
| |
| const SensorBaseConfigMap* baseConfig = nullptr; |
| const SensorData* sensorData = nullptr; |
| const std::string* interfacePath = nullptr; |
| std::string sensorType; |
| size_t thresholdConfSize = 0; |
| |
| for (const auto& [path, cfgData] : sensorConfigs) |
| { |
| sensorData = &cfgData; |
| for (const auto& [type, dt] : sensorTypes) |
| { |
| auto sensorBase = sensorData->find(configInterfaceName(type)); |
| if (sensorBase != sensorData->end()) |
| { |
| baseConfig = &sensorBase->second; |
| sensorType = type; |
| break; |
| } |
| } |
| if (baseConfig == nullptr) |
| { |
| std::cerr << "error finding base configuration for " |
| << deviceName << "\n"; |
| continue; |
| } |
| |
| auto configBus = baseConfig->find("Bus"); |
| auto configAddress = baseConfig->find("Address"); |
| |
| if (configBus == baseConfig->end() || |
| configAddress == baseConfig->end()) |
| { |
| std::cerr << "error finding necessary entry in configuration\n"; |
| continue; |
| } |
| |
| const uint64_t* confBus = |
| std::get_if<uint64_t>(&(configBus->second)); |
| const uint64_t* confAddr = |
| std::get_if<uint64_t>(&(configAddress->second)); |
| if (confBus == nullptr || confAddr == nullptr) |
| { |
| std::cerr |
| << "Cannot get bus or address, invalid configuration\n"; |
| continue; |
| } |
| |
| if ((*confBus != bus) || (*confAddr != addr)) |
| { |
| if constexpr (debug) |
| { |
| std::cerr << "Configuration skipping " << *confBus << "-" |
| << *confAddr << " because not " << bus << "-" |
| << addr << "\n"; |
| } |
| continue; |
| } |
| |
| std::vector<thresholds::Threshold> confThresholds; |
| if (!parseThresholdsFromConfig(*sensorData, confThresholds)) |
| { |
| std::cerr << "error populating total thresholds\n"; |
| } |
| thresholdConfSize = confThresholds.size(); |
| |
| interfacePath = &path.str; |
| break; |
| } |
| if (interfacePath == nullptr) |
| { |
| // To avoid this error message, add your export map entry, |
| // from Entity Manager, to sensorTypes at the top of this file. |
| std::cerr << "failed to find match for " << deviceName << "\n"; |
| continue; |
| } |
| |
| auto findI2CDev = devices.find(*interfacePath); |
| |
| std::shared_ptr<I2CDevice> i2cDev; |
| if (findI2CDev != devices.end()) |
| { |
| if (activateOnly && !findI2CDev->second.second) |
| { |
| continue; |
| } |
| i2cDev = findI2CDev->second.first; |
| } |
| |
| auto findPSUName = baseConfig->find("Name"); |
| if (findPSUName == baseConfig->end()) |
| { |
| std::cerr << "could not determine configuration name for " |
| << deviceName << "\n"; |
| continue; |
| } |
| const std::string* psuName = |
| std::get_if<std::string>(&(findPSUName->second)); |
| if (psuName == nullptr) |
| { |
| std::cerr << "Cannot find psu name, invalid configuration\n"; |
| continue; |
| } |
| |
| auto findCPU = baseConfig->find("CPURequired"); |
| if (findCPU != baseConfig->end()) |
| { |
| size_t index = std::visit(VariantToIntVisitor(), findCPU->second); |
| auto presenceFind = cpuPresence.find(index); |
| if (presenceFind == cpuPresence.end() || !presenceFind->second) |
| { |
| continue; |
| } |
| } |
| |
| // on rescans, only update sensors we were signaled by |
| if (!firstScan) |
| { |
| std::string psuNameStr = "/" + escapeName(*psuName); |
| auto it = |
| std::find_if(sensorsChanged->begin(), sensorsChanged->end(), |
| [psuNameStr](std::string& s) { |
| return s.ends_with(psuNameStr); |
| }); |
| |
| if (it == sensorsChanged->end()) |
| { |
| continue; |
| } |
| sensorsChanged->erase(it); |
| } |
| checkEvent(directory.string(), eventMatch, eventPathList); |
| checkGroupEvent(directory.string(), groupEventPathList); |
| |
| PowerState readState = getPowerState(*baseConfig); |
| |
| /* Check if there are more sensors in the same interface */ |
| int i = 1; |
| std::vector<std::string> psuNames; |
| do |
| { |
| // Individual string fields: Name, Name1, Name2, Name3, ... |
| psuNames.push_back( |
| escapeName(std::get<std::string>(findPSUName->second))); |
| findPSUName = baseConfig->find("Name" + std::to_string(i++)); |
| } while (findPSUName != baseConfig->end()); |
| |
| std::vector<fs::path> sensorPaths; |
| if (!findFiles(directory, devParamMap[devType].matchRegEx, sensorPaths, |
| 0)) |
| { |
| std::cerr << "No PSU non-label sensor in PSU\n"; |
| continue; |
| } |
| |
| /* read max value in sysfs for in, curr, power, temp, ... */ |
| if (!findFiles(directory, R"(\w\d+_max$)", sensorPaths, 0)) |
| { |
| if constexpr (debug) |
| { |
| std::cerr << "No max name in PSU \n"; |
| } |
| } |
| |
| float pollRate = getPollRate(*baseConfig, PSUSensor::defaultSensorPoll); |
| |
| /* Find array of labels to be exposed if it is defined in config */ |
| std::vector<std::string> findLabels; |
| auto findLabelObj = baseConfig->find("Labels"); |
| if (findLabelObj != baseConfig->end()) |
| { |
| findLabels = |
| std::get<std::vector<std::string>>(findLabelObj->second); |
| } |
| |
| std::regex sensorNameRegEx(devParamMap[devType].nameRegEx); |
| std::smatch matches; |
| |
| for (const auto& sensorPath : sensorPaths) |
| { |
| bool maxLabel = false; |
| std::string labelHead; |
| std::string sensorPathStr = sensorPath.string(); |
| std::string sensorNameStr = sensorPath.filename(); |
| std::string sensorNameSubStr; |
| if (std::regex_search(sensorNameStr, matches, sensorNameRegEx)) |
| { |
| // hwmon *_input filename without number: |
| // in, curr, power, temp, ... |
| // iio in_*_raw filename without number: |
| // voltage, temp, pressure, ... |
| sensorNameSubStr = matches[devParamMap[devType].matchIndex]; |
| } |
| else |
| { |
| std::cerr << "Could not extract the alpha prefix from " |
| << sensorNameStr; |
| continue; |
| } |
| |
| std::string labelPath; |
| |
| if (devType == DevTypes::HWMON) |
| { |
| /* find and differentiate _max and _input to replace "label" */ |
| size_t pos = sensorPathStr.find('_'); |
| if (pos != std::string::npos) |
| { |
| std::string sensorPathStrMax = sensorPathStr.substr(pos); |
| if (sensorPathStrMax == "_max") |
| { |
| labelPath = boost::replace_all_copy(sensorPathStr, |
| "max", "label"); |
| maxLabel = true; |
| } |
| else |
| { |
| labelPath = boost::replace_all_copy(sensorPathStr, |
| "input", "label"); |
| maxLabel = false; |
| } |
| } |
| else |
| { |
| continue; |
| } |
| |
| std::ifstream labelFile(labelPath); |
| if (!labelFile.good()) |
| { |
| if constexpr (debug) |
| { |
| std::cerr << "Input file " << sensorPath |
| << " has no corresponding label file\n"; |
| } |
| // hwmon *_input filename with number: |
| // temp1, temp2, temp3, ... |
| labelHead = |
| sensorNameStr.substr(0, sensorNameStr.find('_')); |
| } |
| else |
| { |
| std::string label; |
| std::getline(labelFile, label); |
| labelFile.close(); |
| auto findSensor = sensors.find(label); |
| if (findSensor != sensors.end()) |
| { |
| continue; |
| } |
| |
| // hwmon corresponding *_label file contents: |
| // vin1, vout1, ... |
| labelHead = label.substr(0, label.find(' ')); |
| } |
| |
| /* append "max" for labelMatch */ |
| if (maxLabel) |
| { |
| labelHead.insert(0, "max"); |
| } |
| |
| checkPWMSensor(sensorPath, labelHead, *interfacePath, |
| dbusConnection, objectServer, psuNames[0]); |
| } |
| else if (devType == DevTypes::IIO) |
| { |
| auto findIIOHyphen = sensorNameStr.find_last_of('_'); |
| labelHead = sensorNameStr.substr(0, findIIOHyphen); |
| } |
| |
| if constexpr (debug) |
| { |
| std::cerr << "Sensor type=\"" << sensorNameSubStr |
| << "\" label=\"" << labelHead << "\"\n"; |
| } |
| |
| if (!findLabels.empty()) |
| { |
| /* Check if this labelHead is enabled in config file */ |
| if (std::find(findLabels.begin(), findLabels.end(), |
| labelHead) == findLabels.end()) |
| { |
| if constexpr (debug) |
| { |
| std::cerr << "could not find " << labelHead |
| << " in the Labels list\n"; |
| } |
| continue; |
| } |
| } |
| |
| auto findProperty = labelMatch.find(sensorNameSubStr); |
| if (findProperty == labelMatch.end()) |
| { |
| if constexpr (debug) |
| { |
| std::cerr << "Could not find matching default property for " |
| << sensorNameSubStr << "\n"; |
| } |
| continue; |
| } |
| |
| // Protect the hardcoded labelMatch list from changes, |
| // by making a copy and modifying that instead. |
| // Avoid bleedthrough of one device's customizations to |
| // the next device, as each should be independently customizable. |
| PSUProperty psuProperty = findProperty->second; |
| |
| // Use label head as prefix for reading from config file, |
| // example if temp1: temp1_Name, temp1_Scale, temp1_Min, ... |
| std::string keyName = labelHead + "_Name"; |
| std::string keyScale = labelHead + "_Scale"; |
| std::string keyMin = labelHead + "_Min"; |
| std::string keyMax = labelHead + "_Max"; |
| std::string keyOffset = labelHead + "_Offset"; |
| std::string keyPowerState = labelHead + "_PowerState"; |
| |
| bool customizedName = false; |
| auto findCustomName = baseConfig->find(keyName); |
| if (findCustomName != baseConfig->end()) |
| { |
| try |
| { |
| psuProperty.labelTypeName = std::visit( |
| VariantToStringVisitor(), findCustomName->second); |
| } |
| catch (const std::invalid_argument&) |
| { |
| std::cerr << "Unable to parse " << keyName << "\n"; |
| continue; |
| } |
| |
| // All strings are valid, including empty string |
| customizedName = true; |
| } |
| |
| bool customizedScale = false; |
| auto findCustomScale = baseConfig->find(keyScale); |
| if (findCustomScale != baseConfig->end()) |
| { |
| try |
| { |
| psuProperty.sensorScaleFactor = std::visit( |
| VariantToUnsignedIntVisitor(), findCustomScale->second); |
| } |
| catch (const std::invalid_argument&) |
| { |
| std::cerr << "Unable to parse " << keyScale << "\n"; |
| continue; |
| } |
| |
| // Avoid later division by zero |
| if (psuProperty.sensorScaleFactor > 0) |
| { |
| customizedScale = true; |
| } |
| else |
| { |
| std::cerr << "Unable to accept " << keyScale << "\n"; |
| continue; |
| } |
| } |
| |
| auto findCustomMin = baseConfig->find(keyMin); |
| if (findCustomMin != baseConfig->end()) |
| { |
| try |
| { |
| psuProperty.minReading = std::visit( |
| VariantToDoubleVisitor(), findCustomMin->second); |
| } |
| catch (const std::invalid_argument&) |
| { |
| std::cerr << "Unable to parse " << keyMin << "\n"; |
| continue; |
| } |
| } |
| |
| auto findCustomMax = baseConfig->find(keyMax); |
| if (findCustomMax != baseConfig->end()) |
| { |
| try |
| { |
| psuProperty.maxReading = std::visit( |
| VariantToDoubleVisitor(), findCustomMax->second); |
| } |
| catch (const std::invalid_argument&) |
| { |
| std::cerr << "Unable to parse " << keyMax << "\n"; |
| continue; |
| } |
| } |
| |
| auto findCustomOffset = baseConfig->find(keyOffset); |
| if (findCustomOffset != baseConfig->end()) |
| { |
| try |
| { |
| psuProperty.sensorOffset = std::visit( |
| VariantToDoubleVisitor(), findCustomOffset->second); |
| } |
| catch (const std::invalid_argument&) |
| { |
| std::cerr << "Unable to parse " << keyOffset << "\n"; |
| continue; |
| } |
| } |
| |
| // if we find label head power state set ,override the powerstate. |
| auto findPowerState = baseConfig->find(keyPowerState); |
| if (findPowerState != baseConfig->end()) |
| { |
| std::string powerState = std::visit(VariantToStringVisitor(), |
| findPowerState->second); |
| setReadState(powerState, readState); |
| } |
| if (!(psuProperty.minReading < psuProperty.maxReading)) |
| { |
| std::cerr << "Min must be less than Max\n"; |
| continue; |
| } |
| |
| // If the sensor name is being customized by config file, |
| // then prefix/suffix composition becomes not necessary, |
| // and in fact not wanted, because it gets in the way. |
| std::string psuNameFromIndex; |
| std::string nameIndexStr = "1"; |
| if (!customizedName) |
| { |
| /* Find out sensor name index for this label */ |
| std::regex rgx("[A-Za-z]+([0-9]+)"); |
| size_t nameIndex{0}; |
| if (std::regex_search(labelHead, matches, rgx)) |
| { |
| nameIndexStr = matches[1]; |
| nameIndex = std::stoi(nameIndexStr); |
| |
| // Decrement to preserve alignment, because hwmon |
| // human-readable filenames and labels use 1-based |
| // numbering, but the "Name", "Name1", "Name2", etc. naming |
| // convention (the psuNames vector) uses 0-based numbering. |
| if (nameIndex > 0) |
| { |
| --nameIndex; |
| } |
| } |
| else |
| { |
| nameIndex = 0; |
| } |
| |
| if (psuNames.size() <= nameIndex) |
| { |
| std::cerr << "Could not pair " << labelHead |
| << " with a Name field\n"; |
| continue; |
| } |
| |
| psuNameFromIndex = psuNames[nameIndex]; |
| |
| if constexpr (debug) |
| { |
| std::cerr << "Sensor label head " << labelHead |
| << " paired with " << psuNameFromIndex |
| << " at index " << nameIndex << "\n"; |
| } |
| } |
| |
| if (devType == DevTypes::HWMON) |
| { |
| checkEventLimits(sensorPathStr, limitEventMatch, eventPathList); |
| } |
| |
| // Similarly, if sensor scaling factor is being customized, |
| // then the below power-of-10 constraint becomes unnecessary, |
| // as config should be able to specify an arbitrary divisor. |
| unsigned int factor = psuProperty.sensorScaleFactor; |
| if (!customizedScale) |
| { |
| // Preserve existing usage of hardcoded labelMatch table below |
| factor = std::pow(10.0, factor); |
| |
| /* Change first char of substring to uppercase */ |
| char firstChar = |
| static_cast<char>(std::toupper(sensorNameSubStr[0])); |
| std::string strScaleFactor = |
| firstChar + sensorNameSubStr.substr(1) + "ScaleFactor"; |
| |
| // Preserve existing configs by accepting earlier syntax, |
| // example CurrScaleFactor, PowerScaleFactor, ... |
| auto findScaleFactor = baseConfig->find(strScaleFactor); |
| if (findScaleFactor != baseConfig->end()) |
| { |
| factor = std::visit(VariantToIntVisitor(), |
| findScaleFactor->second); |
| } |
| |
| if constexpr (debug) |
| { |
| std::cerr << "Sensor scaling factor " << factor |
| << " string " << strScaleFactor << "\n"; |
| } |
| } |
| |
| std::vector<thresholds::Threshold> sensorThresholds; |
| if (!parseThresholdsFromConfig(*sensorData, sensorThresholds, |
| &labelHead)) |
| { |
| std::cerr << "error populating thresholds for " |
| << sensorNameSubStr << "\n"; |
| } |
| |
| auto findSensorUnit = sensorTable.find(sensorNameSubStr); |
| if (findSensorUnit == sensorTable.end()) |
| { |
| std::cerr << sensorNameSubStr |
| << " is not a recognized sensor type\n"; |
| continue; |
| } |
| |
| if constexpr (debug) |
| { |
| std::cerr << "Sensor properties: Name \"" |
| << psuProperty.labelTypeName << "\" Scale " |
| << psuProperty.sensorScaleFactor << " Min " |
| << psuProperty.minReading << " Max " |
| << psuProperty.maxReading << " Offset " |
| << psuProperty.sensorOffset << "\n"; |
| } |
| |
| std::string sensorName = psuProperty.labelTypeName; |
| if (customizedName) |
| { |
| if (sensorName.empty()) |
| { |
| // Allow selective disabling of an individual sensor, |
| // by customizing its name to an empty string. |
| std::cerr << "Sensor disabled, empty string\n"; |
| continue; |
| } |
| } |
| else |
| { |
| // Sensor name not customized, do prefix/suffix composition, |
| // preserving default behavior by using psuNameFromIndex. |
| sensorName = psuNameFromIndex + " " + psuProperty.labelTypeName; |
| |
| // The labelTypeName of a fan can be: |
| // "Fan Speed 1", "Fan Speed 2", "Fan Speed 3" ... |
| if (labelHead == "fan" + nameIndexStr) |
| { |
| sensorName += nameIndexStr; |
| } |
| } |
| |
| if constexpr (debug) |
| { |
| std::cerr << "Sensor name \"" << sensorName << "\" path \"" |
| << sensorPathStr << "\" type \"" << sensorType |
| << "\"\n"; |
| } |
| // destruct existing one first if already created |
| |
| auto& sensor = sensors[sensorName]; |
| if (!activateOnly) |
| { |
| sensor = nullptr; |
| } |
| |
| if (sensor != nullptr) |
| { |
| sensor->activate(sensorPathStr, i2cDev); |
| } |
| else |
| { |
| sensors[sensorName] = std::make_shared<PSUSensor>( |
| sensorPathStr, sensorType, objectServer, dbusConnection, io, |
| sensorName, std::move(sensorThresholds), *interfacePath, |
| readState, findSensorUnit->second, factor, |
| psuProperty.maxReading, psuProperty.minReading, |
| psuProperty.sensorOffset, labelHead, thresholdConfSize, |
| pollRate, i2cDev); |
| sensors[sensorName]->setupRead(); |
| ++numCreated; |
| if constexpr (debug) |
| { |
| std::cerr |
| << "Created " << numCreated << " sensors so far\n"; |
| } |
| } |
| } |
| |
| if (devType == DevTypes::HWMON) |
| { |
| // OperationalStatus event |
| combineEvents[*psuName + "OperationalStatus"] = nullptr; |
| combineEvents[*psuName + "OperationalStatus"] = |
| std::make_unique<PSUCombineEvent>( |
| objectServer, dbusConnection, io, *psuName, readState, |
| eventPathList, groupEventPathList, "OperationalStatus", |
| pollRate); |
| } |
| } |
| |
| if constexpr (debug) |
| { |
| std::cerr << "Created total of " << numCreated << " sensors\n"; |
| } |
| } |
| |
| static void |
| getPresentCpus(std::shared_ptr<sdbusplus::asio::connection>& dbusConnection) |
| { |
| static const int depth = 2; |
| static const int numKeys = 1; |
| GetSubTreeType cpuSubTree; |
| |
| try |
| { |
| auto getItems = dbusConnection->new_method_call( |
| mapper::busName, mapper::path, mapper::interface, mapper::subtree); |
| getItems.append(cpuInventoryPath, static_cast<int32_t>(depth), |
| std::array<const char*, numKeys>{ |
| "xyz.openbmc_project.Inventory.Item"}); |
| auto getItemsResp = dbusConnection->call(getItems); |
| getItemsResp.read(cpuSubTree); |
| } |
| catch (sdbusplus::exception_t& e) |
| { |
| std::cerr << "error getting inventory item subtree: " << e.what() |
| << "\n"; |
| return; |
| } |
| |
| for (const auto& [path, objDict] : cpuSubTree) |
| { |
| auto obj = sdbusplus::message::object_path(path).filename(); |
| boost::to_lower(obj); |
| |
| if (!obj.starts_with("cpu") || objDict.empty()) |
| { |
| continue; |
| } |
| const std::string& owner = objDict.begin()->first; |
| |
| std::variant<bool> respValue; |
| try |
| { |
| auto getPresence = dbusConnection->new_method_call( |
| owner.c_str(), path.c_str(), "org.freedesktop.DBus.Properties", |
| "Get"); |
| getPresence.append("xyz.openbmc_project.Inventory.Item", "Present"); |
| auto resp = dbusConnection->call(getPresence); |
| resp.read(respValue); |
| } |
| catch (sdbusplus::exception_t& e) |
| { |
| std::cerr << "Error in getting CPU presence: " << e.what() << "\n"; |
| continue; |
| } |
| |
| auto* present = std::get_if<bool>(&respValue); |
| if (present != nullptr && *present) |
| { |
| int cpuIndex = 0; |
| try |
| { |
| cpuIndex = std::stoi(obj.substr(obj.size() - 1)); |
| } |
| catch (const std::exception& e) |
| { |
| std::cerr << "Error converting CPU index, " << e.what() << '\n'; |
| continue; |
| } |
| cpuPresence[cpuIndex] = *present; |
| } |
| } |
| } |
| |
| void createSensors( |
| boost::asio::io_context& io, sdbusplus::asio::object_server& objectServer, |
| std::shared_ptr<sdbusplus::asio::connection>& dbusConnection, |
| const std::shared_ptr<boost::container::flat_set<std::string>>& |
| sensorsChanged, |
| bool activateOnly) |
| { |
| auto getter = std::make_shared<GetSensorConfiguration>( |
| dbusConnection, [&io, &objectServer, &dbusConnection, sensorsChanged, |
| activateOnly](const ManagedObjectType& sensorConfigs) { |
| createSensorsCallback(io, objectServer, dbusConnection, |
| sensorConfigs, sensorsChanged, activateOnly); |
| }); |
| std::vector<std::string> types(sensorTypes.size()); |
| for (const auto& [type, dt] : sensorTypes) |
| { |
| types.push_back(type); |
| } |
| getter->getConfiguration(types); |
| } |
| |
| void propertyInitialize() |
| { |
| sensorTable = {{"power", sensor_paths::unitWatts}, |
| {"curr", sensor_paths::unitAmperes}, |
| {"temp", sensor_paths::unitDegreesC}, |
| {"in", sensor_paths::unitVolts}, |
| {"voltage", sensor_paths::unitVolts}, |
| {"fan", sensor_paths::unitRPMs}}; |
| |
| labelMatch = { |
| {"pin", PSUProperty("Input Power", 3000, 0, 6, 0)}, |
| {"pout", PSUProperty("Output Power", 3000, 0, 6, 0)}, |
| {"power", PSUProperty("Output Power", 3000, 0, 6, 0)}, |
| {"maxpin", PSUProperty("Max Input Power", 3000, 0, 6, 0)}, |
| {"vin", PSUProperty("Input Voltage", 300, 0, 3, 0)}, |
| {"maxvin", PSUProperty("Max Input Voltage", 300, 0, 3, 0)}, |
| {"in_voltage", PSUProperty("Output Voltage", 255, 0, 3, 0)}, |
| {"voltage", PSUProperty("Output Voltage", 255, 0, 3, 0)}, |
| {"vout", PSUProperty("Output Voltage", 255, 0, 3, 0)}, |
| {"vmon", PSUProperty("Auxiliary Input Voltage", 255, 0, 3, 0)}, |
| {"in", PSUProperty("Output Voltage", 255, 0, 3, 0)}, |
| {"iin", PSUProperty("Input Current", 20, 0, 3, 0)}, |
| {"iout", PSUProperty("Output Current", 255, 0, 3, 0)}, |
| {"curr", PSUProperty("Output Current", 255, 0, 3, 0)}, |
| {"maxiout", PSUProperty("Max Output Current", 255, 0, 3, 0)}, |
| {"temp", PSUProperty("Temperature", 127, -128, 3, 0)}, |
| {"maxtemp", PSUProperty("Max Temperature", 127, -128, 3, 0)}, |
| {"fan", PSUProperty("Fan Speed ", 30000, 0, 0, 0)}}; |
| |
| limitEventMatch = {{"PredictiveFailure", {"max_alarm", "min_alarm"}}, |
| {"Failure", {"crit_alarm", "lcrit_alarm"}}}; |
| |
| eventMatch = {{"PredictiveFailure", {"power1_alarm"}}, |
| {"Failure", {"in2_alarm"}}, |
| {"ACLost", {"in1_beep"}}, |
| {"ConfigureError", {"in1_fault"}}}; |
| |
| devParamMap = { |
| {DevTypes::HWMON, {1, R"(\w\d+_input$)", "([A-Za-z]+)[0-9]*_"}}, |
| {DevTypes::IIO, |
| {2, R"(\w+_(raw|input)$)", "^(in|out)_([A-Za-z]+)[0-9]*_"}}}; |
| } |
| |
| static void powerStateChanged( |
| PowerState type, bool newState, |
| boost::container::flat_map<std::string, std::shared_ptr<PSUSensor>>& |
| sensors, |
| boost::asio::io_context& io, sdbusplus::asio::object_server& objectServer, |
| std::shared_ptr<sdbusplus::asio::connection>& dbusConnection) |
| { |
| if (newState) |
| { |
| createSensors(io, objectServer, dbusConnection, nullptr, true); |
| } |
| else |
| { |
| for (auto& [path, sensor] : sensors) |
| { |
| if (sensor != nullptr && sensor->readState == type) |
| { |
| sensor->deactivate(); |
| } |
| } |
| } |
| } |
| |
| int main() |
| { |
| boost::asio::io_context io; |
| auto systemBus = std::make_shared<sdbusplus::asio::connection>(io); |
| |
| sdbusplus::asio::object_server objectServer(systemBus, true); |
| objectServer.add_manager("/xyz/openbmc_project/sensors"); |
| objectServer.add_manager("/xyz/openbmc_project/control"); |
| systemBus->request_name("xyz.openbmc_project.PSUSensor"); |
| auto sensorsChanged = |
| std::make_shared<boost::container::flat_set<std::string>>(); |
| |
| propertyInitialize(); |
| |
| auto powerCallBack = [&io, &objectServer, |
| &systemBus](PowerState type, bool state) { |
| powerStateChanged(type, state, sensors, io, objectServer, systemBus); |
| }; |
| |
| setupPowerMatchCallback(systemBus, powerCallBack); |
| |
| boost::asio::post(io, [&]() { |
| createSensors(io, objectServer, systemBus, nullptr, false); |
| }); |
| boost::asio::steady_timer filterTimer(io); |
| std::function<void(sdbusplus::message_t&)> eventHandler = |
| [&](sdbusplus::message_t& message) { |
| if (message.is_method_error()) |
| { |
| std::cerr << "callback method error\n"; |
| return; |
| } |
| sensorsChanged->insert(message.get_path()); |
| filterTimer.expires_after(std::chrono::seconds(3)); |
| filterTimer.async_wait([&](const boost::system::error_code& ec) { |
| if (ec == boost::asio::error::operation_aborted) |
| { |
| return; |
| } |
| if (ec) |
| { |
| std::cerr << "timer error\n"; |
| } |
| createSensors(io, objectServer, systemBus, sensorsChanged, |
| false); |
| }); |
| }; |
| |
| boost::asio::steady_timer cpuFilterTimer(io); |
| std::function<void(sdbusplus::message_t&)> cpuPresenceHandler = |
| [&](sdbusplus::message_t& message) { |
| std::string path = message.get_path(); |
| boost::to_lower(path); |
| |
| sdbusplus::message::object_path cpuPath(path); |
| std::string cpuName = cpuPath.filename(); |
| if (!cpuName.starts_with("cpu")) |
| { |
| return; |
| } |
| size_t index = 0; |
| try |
| { |
| index = std::stoi(path.substr(path.size() - 1)); |
| } |
| catch (const std::invalid_argument&) |
| { |
| std::cerr << "Found invalid path " << path << "\n"; |
| return; |
| } |
| |
| std::string objectName; |
| boost::container::flat_map<std::string, std::variant<bool>> values; |
| message.read(objectName, values); |
| auto findPresence = values.find("Present"); |
| if (findPresence == values.end()) |
| { |
| return; |
| } |
| try |
| { |
| cpuPresence[index] = std::get<bool>(findPresence->second); |
| } |
| catch (const std::bad_variant_access& err) |
| { |
| return; |
| } |
| |
| if (!cpuPresence[index]) |
| { |
| return; |
| } |
| cpuFilterTimer.expires_after(std::chrono::seconds(1)); |
| cpuFilterTimer.async_wait([&](const boost::system::error_code& ec) { |
| if (ec == boost::asio::error::operation_aborted) |
| { |
| return; |
| } |
| if (ec) |
| { |
| std::cerr << "timer error\n"; |
| return; |
| } |
| createSensors(io, objectServer, systemBus, nullptr, false); |
| }); |
| }; |
| |
| std::vector<std::unique_ptr<sdbusplus::bus::match_t>> matches = |
| setupPropertiesChangedMatches(*systemBus, sensorTypes, eventHandler); |
| |
| matches.emplace_back(std::make_unique<sdbusplus::bus::match_t>( |
| static_cast<sdbusplus::bus_t&>(*systemBus), |
| "type='signal',member='PropertiesChanged',path_namespace='" + |
| std::string(cpuInventoryPath) + |
| "',arg0namespace='xyz.openbmc_project.Inventory.Item'", |
| cpuPresenceHandler)); |
| |
| getPresentCpus(systemBus); |
| |
| setupManufacturingModeMatch(*systemBus); |
| io.run(); |
| } |