| /* |
| // 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 <PSUEvent.hpp> |
| #include <PSUSensor.hpp> |
| #include <Utils.hpp> |
| #include <boost/algorithm/string/predicate.hpp> |
| #include <boost/algorithm/string/replace.hpp> |
| #include <boost/container/flat_set.hpp> |
| #include <filesystem> |
| #include <fstream> |
| #include <iostream> |
| #include <regex> |
| #include <sdbusplus/asio/connection.hpp> |
| #include <sdbusplus/asio/object_server.hpp> |
| |
| static constexpr bool DEBUG = false; |
| |
| static constexpr std::array<const char*, 3> sensorTypes = { |
| "xyz.openbmc_project.Configuration.pmbus", |
| "xyz.openbmc_project.Configuration.MAX34451", |
| "xyz.openbmc_project.Configuration.ISL68137"}; |
| |
| static std::vector<std::string> pmbusNames = {"pmbus", "pxe1610", "ina219", |
| "ina230", "max34451", "isl68137"}; |
| |
| namespace fs = std::filesystem; |
| |
| static boost::container::flat_map<std::string, std::unique_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 boost::container::flat_map<std::string, std::string> pwmTable; |
| static boost::container::flat_map<std::string, std::vector<std::string>> |
| eventMatch; |
| static boost::container::flat_map<std::string, std::vector<std::string>> |
| limitEventMatch; |
| |
| // 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 boost::container::flat_map<std::string, std::vector<std::string>>& |
| eventMatch, |
| boost::container::flat_map<std::string, std::vector<std::string>>& |
| 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) |
| { |
| auto eventPath = directory + "/" + eventAttr; |
| |
| std::ifstream eventFile(eventPath); |
| if (!eventFile.good()) |
| { |
| continue; |
| } |
| |
| eventPathList[eventName].push_back(eventPath); |
| } |
| } |
| } |
| |
| // 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 boost::container::flat_map<std::string, std::vector<std::string>>& |
| limitEventMatch, |
| boost::container::flat_map<std::string, std::vector<std::string>>& |
| eventPathList) |
| { |
| 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 = |
| boost::replace_all_copy(sensorPathStr, "input", 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, |
| sdbusplus::asio::object_server& objectServer, |
| const std::string& psuName) |
| { |
| for (const auto& pwmName : pwmTable) |
| { |
| if (pwmName.first != labelHead) |
| { |
| continue; |
| } |
| |
| const std::string& sensorPathStr = sensorPath.string(); |
| const std::string& pwmPathStr = |
| boost::replace_all_copy(sensorPathStr, "input", "target"); |
| std::ifstream pwmFile(pwmPathStr); |
| if (!pwmFile.good()) |
| { |
| continue; |
| } |
| |
| auto findPWMSensor = pwmSensors.find(psuName + labelHead); |
| if (findPWMSensor != pwmSensors.end()) |
| { |
| continue; |
| } |
| |
| pwmSensors[psuName + labelHead] = std::make_unique<PwmSensor>( |
| "Pwm_" + psuName + "_" + pwmName.second, pwmPathStr, objectServer, |
| interfacePath + "_" + pwmName.second); |
| } |
| } |
| |
| void createSensors(boost::asio::io_service& io, |
| sdbusplus::asio::object_server& objectServer, |
| std::shared_ptr<sdbusplus::asio::connection>& dbusConnection) |
| { |
| |
| ManagedObjectType sensorConfigs; |
| int numCreated = 0; |
| bool useCache = false; |
| |
| // TODO may need only modify the ones that need to be changed. |
| sensors.clear(); |
| combineEvents.clear(); |
| for (const char* type : sensorTypes) |
| { |
| if (!getSensorConfiguration(type, dbusConnection, sensorConfigs, |
| useCache)) |
| { |
| std::cerr << "error get sensor config from entity manager\n"; |
| return; |
| } |
| useCache = true; |
| } |
| |
| std::vector<fs::path> pmbusPaths; |
| if (!findFiles(fs::path("/sys/class/hwmon"), "name", pmbusPaths)) |
| { |
| std::cerr << "No PSU sensors in system\n"; |
| return; |
| } |
| |
| boost::container::flat_set<std::string> directories; |
| for (const auto& pmbusPath : pmbusPaths) |
| { |
| boost::container::flat_map<std::string, std::vector<std::string>> |
| eventPathList; |
| |
| 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 (std::find(pmbusNames.begin(), pmbusNames.end(), pmbusName) == |
| pmbusNames.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; |
| } |
| |
| const std::string* psuName; |
| 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 |
| } |
| |
| fs::path device = directory / "device"; |
| std::string deviceName = fs::canonical(device).stem(); |
| auto findHyphen = deviceName.find("-"); |
| if (findHyphen == std::string::npos) |
| { |
| std::cerr << "found bad device" << deviceName << "\n"; |
| continue; |
| } |
| std::string busStr = deviceName.substr(0, findHyphen); |
| std::string addrStr = deviceName.substr(findHyphen + 1); |
| |
| size_t bus = 0; |
| size_t addr = 0; |
| |
| try |
| { |
| bus = std::stoi(busStr); |
| addr = std::stoi(addrStr, 0, 16); |
| } |
| catch (std::invalid_argument&) |
| { |
| std::cerr << "Error parsing bus " << busStr << " addr " << addrStr |
| << "\n"; |
| continue; |
| } |
| |
| const std::pair<std::string, boost::container::flat_map< |
| std::string, BasicVariantType>>* |
| baseConfig = nullptr; |
| const SensorData* sensorData = nullptr; |
| const std::string* interfacePath = nullptr; |
| const char* sensorType = nullptr; |
| |
| for (const std::pair<sdbusplus::message::object_path, SensorData>& |
| sensor : sensorConfigs) |
| { |
| sensorData = &(sensor.second); |
| for (const char* type : sensorTypes) |
| { |
| auto sensorBase = sensorData->find(type); |
| if (sensorBase != sensorData->end()) |
| { |
| baseConfig = &(*sensorBase); |
| sensorType = type; |
| break; |
| } |
| } |
| if (baseConfig == nullptr) |
| { |
| std::cerr << "error finding base configuration for " |
| << deviceName << "\n"; |
| continue; |
| } |
| |
| auto configBus = baseConfig->second.find("Bus"); |
| auto configAddress = baseConfig->second.find("Address"); |
| |
| if (configBus == baseConfig->second.end() || |
| configAddress == baseConfig->second.end()) |
| { |
| std::cerr << "error finding necessary entry in configuration\n"; |
| continue; |
| } |
| |
| const uint64_t* confBus; |
| const uint64_t* confAddr; |
| if (!(confBus = std::get_if<uint64_t>(&(configBus->second))) || |
| !(confAddr = std::get_if<uint64_t>(&(configAddress->second)))) |
| { |
| std::cerr |
| << "Cannot get bus or address, invalid configuration\n"; |
| continue; |
| } |
| |
| if ((*confBus != bus) || (*confAddr != addr)) |
| { |
| std::cerr << "Configuration mismatch of bus or addr\n"; |
| continue; |
| } |
| |
| interfacePath = &(sensor.first.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 findPSUName = baseConfig->second.find("Name"); |
| if (findPSUName == baseConfig->second.end()) |
| { |
| std::cerr << "could not determine configuration name for " |
| << deviceName << "\n"; |
| continue; |
| } |
| |
| if (!(psuName = std::get_if<std::string>(&(findPSUName->second)))) |
| { |
| std::cerr << "Cannot find psu name, invalid configuration\n"; |
| continue; |
| } |
| checkEvent(directory.string(), eventMatch, eventPathList); |
| |
| /* 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(std::get<std::string>(findPSUName->second)); |
| findPSUName = baseConfig->second.find("Name" + std::to_string(i++)); |
| } while (findPSUName != baseConfig->second.end()); |
| |
| std::vector<fs::path> sensorPaths; |
| if (!findFiles(directory, R"(\w\d+_input$)", sensorPaths, 0)) |
| { |
| std::cerr << "No PSU non-label sensor in PSU\n"; |
| continue; |
| } |
| |
| /* Find array of labels to be exposed if it is defined in config */ |
| std::vector<std::string> findLabels; |
| auto findLabelObj = baseConfig->second.find("Labels"); |
| if (findLabelObj != baseConfig->second.end()) |
| { |
| findLabels = |
| std::get<std::vector<std::string>>(findLabelObj->second); |
| } |
| |
| std::regex sensorNameRegEx("([A-Za-z]+)[0-9]*_"); |
| std::smatch matches; |
| |
| for (const auto& sensorPath : sensorPaths) |
| { |
| |
| std::string labelHead; |
| std::string sensorPathStr = sensorPath.string(); |
| std::string sensorNameStr = sensorPath.filename(); |
| std::string sensorNameSubStr{""}; |
| if (std::regex_search(sensorNameStr, matches, sensorNameRegEx)) |
| { |
| sensorNameSubStr = matches[1]; |
| } |
| else |
| { |
| std::cerr << "Couldn't extract the alpha prefix from " |
| << sensorNameStr; |
| continue; |
| } |
| |
| auto labelPath = |
| boost::replace_all_copy(sensorPathStr, "input", "label"); |
| std::ifstream labelFile(labelPath); |
| if (!labelFile.good()) |
| { |
| std::cerr << "Failure reading " << sensorPath << "\n"; |
| 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; |
| } |
| |
| labelHead = label.substr(0, label.find(" ")); |
| } |
| |
| if constexpr (DEBUG) |
| { |
| std::cerr << "Sensor label head " << labelHead << "\n"; |
| } |
| |
| checkPWMSensor(sensorPath, labelHead, *interfacePath, objectServer, |
| psuNames[0]); |
| |
| if (!findLabels.empty()) |
| { |
| /* Check if this labelHead is enabled in config file */ |
| if (std::find(findLabels.begin(), findLabels.end(), |
| labelHead) == findLabels.end()) |
| { |
| std::cerr << "couldn't find " << labelHead |
| << " in the Labels list\n"; |
| continue; |
| } |
| } |
| |
| /* Find out sensor name index for this label */ |
| std::regex rgx("[A-Za-z]+([0-9]+)"); |
| int nameIndex{0}; |
| if (std::regex_search(labelHead, matches, rgx)) |
| { |
| nameIndex = std::stoi(matches[1]); |
| |
| // 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; |
| } |
| |
| auto findProperty = labelMatch.find(labelHead); |
| if (findProperty == labelMatch.end()) |
| { |
| std::cerr << "Could not find " << labelHead << "\n"; |
| continue; |
| } |
| |
| checkEventLimits(sensorPathStr, limitEventMatch, eventPathList); |
| |
| unsigned int factor = |
| std::pow(10, findProperty->second.sensorScaleFactor); |
| |
| /* Change first char of substring to uppercase */ |
| char firstChar = sensorNameSubStr[0] - 0x20; |
| std::string strScaleFactor = |
| firstChar + sensorNameSubStr.substr(1) + "ScaleFactor"; |
| |
| auto findScaleFactor = baseConfig->second.find(strScaleFactor); |
| if (findScaleFactor != baseConfig->second.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)) |
| { |
| std::cerr << "error populating thresholds for " |
| << sensorNameSubStr << "\n"; |
| } |
| |
| auto findSensorType = sensorTable.find(sensorNameSubStr); |
| if (findSensorType == sensorTable.end()) |
| { |
| std::cerr << sensorNameSubStr |
| << " is not a recognize sensor file\n"; |
| continue; |
| } |
| |
| std::string sensorName = |
| psuNames[nameIndex] + " " + findProperty->second.labelTypeName; |
| |
| ++numCreated; |
| if constexpr (DEBUG) |
| { |
| std::cerr << "Created " << numCreated |
| << " sensors so far: " << sensorName << "\n"; |
| } |
| |
| sensors[sensorName] = std::make_unique<PSUSensor>( |
| sensorPathStr, sensorType, objectServer, dbusConnection, io, |
| sensorName, std::move(sensorThresholds), *interfacePath, |
| findSensorType->second, factor, findProperty->second.maxReading, |
| findProperty->second.minReading); |
| } |
| |
| // OperationalStatus event |
| combineEvents[*psuName + "OperationalStatus"] = nullptr; |
| combineEvents[*psuName + "OperationalStatus"] = |
| std::make_unique<PSUCombineEvent>( |
| objectServer, io, *psuName, eventPathList, "OperationalStatus"); |
| } |
| |
| if constexpr (DEBUG) |
| { |
| std::cerr << "Created total of " << numCreated << " sensors\n"; |
| } |
| return; |
| } |
| |
| void propertyInitialize(void) |
| { |
| sensorTable = {{"power", "power/"}, |
| {"curr", "current/"}, |
| {"temp", "temperature/"}, |
| {"in", "voltage/"}, |
| {"fan", "fan_tach/"}}; |
| |
| labelMatch = {{"pin", PSUProperty("Input Power", 3000, 0, 6)}, |
| {"pout1", PSUProperty("Output Power", 3000, 0, 6)}, |
| {"pout2", PSUProperty("Output Power", 3000, 0, 6)}, |
| {"pout3", PSUProperty("Output Power", 3000, 0, 6)}, |
| {"power1", PSUProperty("Output Power", 3000, 0, 6)}, |
| {"vin", PSUProperty("Input Voltage", 300, 0, 3)}, |
| {"vout1", PSUProperty("Output Voltage", 255, 0, 3)}, |
| {"vout2", PSUProperty("Output Voltage", 255, 0, 3)}, |
| {"vout3", PSUProperty("Output Voltage", 255, 0, 3)}, |
| {"vout4", PSUProperty("Output Voltage", 255, 0, 3)}, |
| {"vout5", PSUProperty("Output Voltage", 255, 0, 3)}, |
| {"vout6", PSUProperty("Output Voltage", 255, 0, 3)}, |
| {"vout7", PSUProperty("Output Voltage", 255, 0, 3)}, |
| {"vout8", PSUProperty("Output Voltage", 255, 0, 3)}, |
| {"vout9", PSUProperty("Output Voltage", 255, 0, 3)}, |
| {"vout10", PSUProperty("Output Voltage", 255, 0, 3)}, |
| {"vout11", PSUProperty("Output Voltage", 255, 0, 3)}, |
| {"vout12", PSUProperty("Output Voltage", 255, 0, 3)}, |
| {"vout13", PSUProperty("Output Voltage", 255, 0, 3)}, |
| {"vout14", PSUProperty("Output Voltage", 255, 0, 3)}, |
| {"vout15", PSUProperty("Output Voltage", 255, 0, 3)}, |
| {"vout16", PSUProperty("Output Voltage", 255, 0, 3)}, |
| {"in1", PSUProperty("Output Voltage", 255, 0, 3)}, |
| {"iin", PSUProperty("Input Current", 20, 0, 3)}, |
| {"iout1", PSUProperty("Output Current", 255, 0, 3)}, |
| {"iout2", PSUProperty("Output Current", 255, 0, 3)}, |
| {"iout3", PSUProperty("Output Current", 255, 0, 3)}, |
| {"curr1", PSUProperty("Output Current", 255, 0, 3)}, |
| {"temp1", PSUProperty("Temperature", 127, -128, 3)}, |
| {"temp2", PSUProperty("Temperature", 127, -128, 3)}, |
| {"temp3", PSUProperty("Temperature", 127, -128, 3)}, |
| {"temp4", PSUProperty("Temperature", 127, -128, 3)}, |
| {"temp5", PSUProperty("Temperature", 127, -128, 3)}, |
| {"fan1", PSUProperty("Fan Speed 1", 30000, 0, 0)}, |
| {"fan2", PSUProperty("Fan Speed 2", 30000, 0, 0)}}; |
| |
| pwmTable = {{"fan1", "Fan_1"}, {"fan2", "Fan_2"}}; |
| |
| limitEventMatch = {{"PredictiveFailure", {"max_alarm", "min_alarm"}}, |
| {"Failure", {"crit_alarm", "lcrit_alarm"}}}; |
| |
| eventMatch = { |
| {"PredictiveFailure", {"power1_alarm"}}, |
| {"Failure", {"in2_alarm"}}, |
| {"ACLost", {"in1_beep"}}, |
| {"FanFault", {"fan1_alarm", "fan2_alarm", "fan1_fault", "fan2_fault"}}, |
| {"ConfigureError", {"in1_fault"}}}; |
| } |
| |
| int main() |
| { |
| boost::asio::io_service io; |
| auto systemBus = std::make_shared<sdbusplus::asio::connection>(io); |
| |
| systemBus->request_name("xyz.openbmc_project.PSUSensor"); |
| sdbusplus::asio::object_server objectServer(systemBus); |
| std::vector<std::unique_ptr<sdbusplus::bus::match::match>> matches; |
| |
| propertyInitialize(); |
| |
| io.post([&]() { createSensors(io, objectServer, systemBus); }); |
| boost::asio::deadline_timer filterTimer(io); |
| std::function<void(sdbusplus::message::message&)> eventHandler = |
| [&](sdbusplus::message::message& message) { |
| if (message.is_method_error()) |
| { |
| std::cerr << "callback method error\n"; |
| return; |
| } |
| filterTimer.expires_from_now(boost::posix_time::seconds(1)); |
| filterTimer.async_wait([&](const boost::system::error_code& ec) { |
| if (ec == boost::asio::error::operation_aborted) |
| { |
| return; |
| } |
| else if (ec) |
| { |
| std::cerr << "timer error\n"; |
| } |
| createSensors(io, objectServer, systemBus); |
| }); |
| }; |
| |
| for (const char* type : sensorTypes) |
| { |
| auto match = std::make_unique<sdbusplus::bus::match::match>( |
| static_cast<sdbusplus::bus::bus&>(*systemBus), |
| "type='signal',member='PropertiesChanged',path_namespace='" + |
| std::string(inventoryPath) + "',arg0namespace='" + type + "'", |
| eventHandler); |
| matches.emplace_back(std::move(match)); |
| } |
| io.run(); |
| } |