/* // Copyright (c) 2018 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 "commandutils.hpp" #include "types.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #pragma once struct CmpStrVersion { bool operator()(std::string a, std::string b) const { return strverscmp(a.c_str(), b.c_str()) < 0; } }; using SensorSubTree = boost::container::flat_map< std::string, boost::container::flat_map>, CmpStrVersion>; using SensorNumMap = boost::bimap; static constexpr uint16_t maxSensorsPerLUN = 255; static constexpr uint16_t maxIPMISensors = (maxSensorsPerLUN * 3); static constexpr uint16_t lun1Sensor0 = 0x100; static constexpr uint16_t lun3Sensor0 = 0x300; static constexpr uint16_t invalidSensorNumber = 0xFFFF; static constexpr uint8_t reservedSensorNumber = 0xFF; namespace details { // Enable/disable the logging of stats instrumentation static constexpr bool enableInstrumentation = false; class IPMIStatsEntry { private: int numReadings = 0; int numMissings = 0; int numStreakRead = 0; int numStreakMiss = 0; double minValue = 0.0; double maxValue = 0.0; std::string sensorName; public: const std::string& getName(void) const { return sensorName; } void updateName(std::string_view name) { sensorName = name; } // Returns true if this is the first successful reading // This is so the caller can log the coefficients used bool updateReading(double reading, int raw) { if constexpr (!enableInstrumentation) { return false; } bool first = ((numReadings == 0) && (numMissings == 0)); // Sensors can use "nan" to indicate unavailable reading if (!(std::isfinite(reading))) { // Only show this if beginning a new streak if (numStreakMiss == 0) { std::cerr << "IPMI sensor " << sensorName << ": Missing reading, byte=" << raw << ", Reading counts good=" << numReadings << " miss=" << numMissings << ", Prior good streak=" << numStreakRead << "\n"; } numStreakRead = 0; ++numMissings; ++numStreakMiss; return first; } // Only show this if beginning a new streak and not the first time if ((numStreakRead == 0) && (numReadings != 0)) { std::cerr << "IPMI sensor " << sensorName << ": Recovered reading, value=" << reading << " byte=" << raw << ", Reading counts good=" << numReadings << " miss=" << numMissings << ", Prior miss streak=" << numStreakMiss << "\n"; } // Initialize min/max if the first successful reading if (numReadings == 0) { std::cerr << "IPMI sensor " << sensorName << ": First reading, value=" << reading << " byte=" << raw << "\n"; minValue = reading; maxValue = reading; } numStreakMiss = 0; ++numReadings; ++numStreakRead; // Only provide subsequent output if new min/max established if (reading < minValue) { std::cerr << "IPMI sensor " << sensorName << ": Lowest reading, value=" << reading << " byte=" << raw << "\n"; minValue = reading; } if (reading > maxValue) { std::cerr << "IPMI sensor " << sensorName << ": Highest reading, value=" << reading << " byte=" << raw << "\n"; maxValue = reading; } return first; } }; class IPMIStatsTable { private: std::vector entries; private: void padEntries(size_t index) { char hexbuf[16]; // Pad vector until entries[index] becomes a valid index while (entries.size() <= index) { // As name not known yet, use human-readable hex as name IPMIStatsEntry newEntry; sprintf(hexbuf, "0x%02zX", entries.size()); newEntry.updateName(hexbuf); entries.push_back(std::move(newEntry)); } } public: void wipeTable(void) { entries.clear(); } const std::string& getName(size_t index) { padEntries(index); return entries[index].getName(); } void updateName(size_t index, std::string_view name) { padEntries(index); entries[index].updateName(name); } bool updateReading(size_t index, double reading, int raw) { padEntries(index); return entries[index].updateReading(reading, raw); } }; // This object is global singleton, used from a variety of places inline IPMIStatsTable sdrStatsTable; inline static uint16_t getSensorSubtree(std::shared_ptr& subtree) { static std::shared_ptr sensorTreePtr; static uint16_t sensorUpdatedIndex = 0; sd_bus* bus = NULL; int ret = sd_bus_default_system(&bus); if (ret < 0) { phosphor::logging::log( "Failed to connect to system bus", phosphor::logging::entry("ERRNO=0x%X", -ret)); sd_bus_unref(bus); return sensorUpdatedIndex; } sdbusplus::bus_t dbus(bus); static sdbusplus::bus::match_t sensorAdded( dbus, "type='signal',member='InterfacesAdded',arg0path='/xyz/openbmc_project/" "sensors/'", [](sdbusplus::message_t& m) { sensorTreePtr.reset(); }); static sdbusplus::bus::match_t sensorRemoved( dbus, "type='signal',member='InterfacesRemoved',arg0path='/xyz/" "openbmc_project/sensors/'", [](sdbusplus::message_t& m) { sensorTreePtr.reset(); }); if (sensorTreePtr) { subtree = sensorTreePtr; return sensorUpdatedIndex; } sensorTreePtr = std::make_shared(); auto mapperCall = dbus.new_method_call("xyz.openbmc_project.ObjectMapper", "/xyz/openbmc_project/object_mapper", "xyz.openbmc_project.ObjectMapper", "GetSubTree"); static constexpr const auto depth = 2; static constexpr std::array interfaces = { "xyz.openbmc_project.Sensor.Value", "xyz.openbmc_project.Sensor.Threshold.Warning", "xyz.openbmc_project.Sensor.Threshold.Critical"}; mapperCall.append("/xyz/openbmc_project/sensors", depth, interfaces); try { auto mapperReply = dbus.call(mapperCall); mapperReply.read(*sensorTreePtr); } catch (const sdbusplus::exception_t& e) { phosphor::logging::log(e.what()); return sensorUpdatedIndex; } subtree = sensorTreePtr; sensorUpdatedIndex++; // The SDR is being regenerated, wipe the old stats sdrStatsTable.wipeTable(); return sensorUpdatedIndex; } inline static bool getSensorNumMap(std::shared_ptr& sensorNumMap) { static std::shared_ptr sensorNumMapPtr; bool sensorNumMapUpated = false; static uint16_t prevSensorUpdatedIndex = 0; std::shared_ptr sensorTree; uint16_t curSensorUpdatedIndex = details::getSensorSubtree(sensorTree); if (!sensorTree) { return sensorNumMapUpated; } if ((curSensorUpdatedIndex == prevSensorUpdatedIndex) && sensorNumMapPtr) { sensorNumMap = sensorNumMapPtr; return sensorNumMapUpated; } prevSensorUpdatedIndex = curSensorUpdatedIndex; sensorNumMapPtr = std::make_shared(); uint16_t sensorNum = 0; uint16_t sensorIndex = 0; for (const auto& sensor : *sensorTree) { sensorNumMapPtr->insert( SensorNumMap::value_type(sensorNum, sensor.first)); sensorIndex++; if (sensorIndex == maxSensorsPerLUN) { sensorIndex = lun1Sensor0; } else if (sensorIndex == (lun1Sensor0 | maxSensorsPerLUN)) { // Skip assigning LUN 0x2 any sensors sensorIndex = lun3Sensor0; } else if (sensorIndex == (lun3Sensor0 | maxSensorsPerLUN)) { // this is an error, too many IPMI sensors throw std::out_of_range("Maximum number of IPMI sensors exceeded."); } sensorNum = sensorIndex; } sensorNumMap = sensorNumMapPtr; sensorNumMapUpated = true; return sensorNumMapUpated; } } // namespace details inline static bool getSensorSubtree(SensorSubTree& subtree) { std::shared_ptr sensorTree; details::getSensorSubtree(sensorTree); if (!sensorTree) { return false; } subtree = *sensorTree; return true; } struct CmpStr { bool operator()(const char* a, const char* b) const { return std::strcmp(a, b) < 0; } }; enum class SensorTypeCodes : uint8_t { reserved = 0x0, temperature = 0x1, voltage = 0x2, current = 0x3, fan = 0x4, other = 0xB, }; const static boost::container::flat_map sensorTypes{{{"temperature", SensorTypeCodes::temperature}, {"voltage", SensorTypeCodes::voltage}, {"current", SensorTypeCodes::current}, {"fan_tach", SensorTypeCodes::fan}, {"fan_pwm", SensorTypeCodes::fan}, {"power", SensorTypeCodes::other}}}; inline static std::string getSensorTypeStringFromPath(const std::string& path) { // get sensor type string from path, path is defined as // /xyz/openbmc_project/sensors//label size_t typeEnd = path.rfind("/"); if (typeEnd == std::string::npos) { return path; } size_t typeStart = path.rfind("/", typeEnd - 1); if (typeStart == std::string::npos) { return path; } // Start at the character after the '/' typeStart++; return path.substr(typeStart, typeEnd - typeStart); } inline static uint8_t getSensorTypeFromPath(const std::string& path) { uint8_t sensorType = 0; std::string type = getSensorTypeStringFromPath(path); auto findSensor = sensorTypes.find(type.c_str()); if (findSensor != sensorTypes.end()) { sensorType = static_cast(findSensor->second); } // else default 0x0 RESERVED return sensorType; } inline static uint16_t getSensorNumberFromPath(const std::string& path) { std::shared_ptr sensorNumMapPtr; details::getSensorNumMap(sensorNumMapPtr); if (!sensorNumMapPtr) { return invalidSensorNumber; } try { return sensorNumMapPtr->right.at(path); } catch (const std::out_of_range& e) { phosphor::logging::log(e.what()); return invalidSensorNumber; } } inline static uint8_t getSensorEventTypeFromPath(const std::string& path) { // TODO: Add support for additional reading types as needed return 0x1; // reading type = threshold } inline static std::string getPathFromSensorNumber(uint16_t sensorNum) { std::shared_ptr sensorNumMapPtr; details::getSensorNumMap(sensorNumMapPtr); if (!sensorNumMapPtr) { return std::string(); } try { return sensorNumMapPtr->left.at(sensorNum); } catch (const std::out_of_range& e) { phosphor::logging::log(e.what()); return std::string(); } } namespace ipmi { static inline std::map> getObjectInterfaces(const char* path) { std::map> interfacesResponse; std::vector interfaces; std::shared_ptr dbus = getSdBus(); sdbusplus::message_t getObjectMessage = dbus->new_method_call("xyz.openbmc_project.ObjectMapper", "/xyz/openbmc_project/object_mapper", "xyz.openbmc_project.ObjectMapper", "GetObject"); getObjectMessage.append(path, interfaces); try { sdbusplus::message_t response = dbus->call(getObjectMessage); response.read(interfacesResponse); } catch (const std::exception& e) { phosphor::logging::log( "Failed to GetObject", phosphor::logging::entry("PATH=%s", path), phosphor::logging::entry("WHAT=%s", e.what())); } return interfacesResponse; } static inline std::map getEntityManagerProperties(const char* path, const char* interface) { std::map properties; std::shared_ptr dbus = getSdBus(); sdbusplus::message_t getProperties = dbus->new_method_call("xyz.openbmc_project.EntityManager", path, "org.freedesktop.DBus.Properties", "GetAll"); getProperties.append(interface); try { sdbusplus::message_t response = dbus->call(getProperties); response.read(properties); } catch (const std::exception& e) { phosphor::logging::log( "Failed to GetAll", phosphor::logging::entry("PATH=%s", path), phosphor::logging::entry("INTF=%s", interface), phosphor::logging::entry("WHAT=%s", e.what())); } return properties; } static inline const std::string* getSensorConfigurationInterface( const std::map>& sensorInterfacesResponse) { auto entityManagerService = sensorInterfacesResponse.find("xyz.openbmc_project.EntityManager"); if (entityManagerService == sensorInterfacesResponse.end()) { return nullptr; } // Find the fan configuration first (fans can have multiple configuration // interfaces). for (const auto& entry : entityManagerService->second) { if (entry == "xyz.openbmc_project.Configuration.AspeedFan" || entry == "xyz.openbmc_project.Configuration.I2CFan" || entry == "xyz.openbmc_project.Configuration.NuvotonFan") { return &entry; } } for (const auto& entry : entityManagerService->second) { if (boost::algorithm::starts_with(entry, "xyz.openbmc_project.Configuration.")) { return &entry; } } return nullptr; } // Follow Association properties for Sensor back to the Board dbus object to // check for an EntityId and EntityInstance property. static inline void updateIpmiFromAssociation(const std::string& path, const SensorMap& sensorMap, uint8_t& entityId, uint8_t& entityInstance) { namespace fs = std::filesystem; auto sensorAssociationObject = sensorMap.find("xyz.openbmc_project.Association.Definitions"); if (sensorAssociationObject == sensorMap.end()) { if constexpr (debug) { std::fprintf(stderr, "path=%s, no association interface found\n", path.c_str()); } return; } auto associationObject = sensorAssociationObject->second.find("Associations"); if (associationObject == sensorAssociationObject->second.end()) { if constexpr (debug) { std::fprintf(stderr, "path=%s, no association records found\n", path.c_str()); } return; } std::vector associationValues = std::get>(associationObject->second); // loop through the Associations looking for the right one: for (const auto& entry : associationValues) { // forward, reverse, endpoint const std::string& forward = std::get<0>(entry); const std::string& reverse = std::get<1>(entry); const std::string& endpoint = std::get<2>(entry); // We only currently concern ourselves with chassis+all_sensors. if (!(forward == "chassis" && reverse == "all_sensors")) { continue; } // the endpoint is the board entry provided by // Entity-Manager. so let's grab its properties if it has // the right interface. // just try grabbing the properties first. std::map ipmiProperties = getEntityManagerProperties( endpoint.c_str(), "xyz.openbmc_project.Inventory.Decorator.Ipmi"); auto entityIdProp = ipmiProperties.find("EntityId"); auto entityInstanceProp = ipmiProperties.find("EntityInstance"); if (entityIdProp != ipmiProperties.end()) { entityId = static_cast(std::get(entityIdProp->second)); } if (entityInstanceProp != ipmiProperties.end()) { entityInstance = static_cast( std::get(entityInstanceProp->second)); } // Now check the entity-manager entry for this sensor to see // if it has its own value and use that instead. // // In theory, checking this first saves us from checking // both, except in most use-cases identified, there won't be // a per sensor override, so we need to always check both. std::string sensorNameFromPath = fs::path(path).filename(); std::string sensorConfigPath = endpoint + "/" + sensorNameFromPath; // Download the interfaces for the sensor from // Entity-Manager to find the name of the configuration // interface. std::map> sensorInterfacesResponse = getObjectInterfaces(sensorConfigPath.c_str()); const std::string* configurationInterface = getSensorConfigurationInterface(sensorInterfacesResponse); // We didnt' find a configuration interface for this sensor, but we // followed the Association property to get here, so we're done // searching. if (!configurationInterface) { break; } // We found a configuration interface. std::map configurationProperties = getEntityManagerProperties(sensorConfigPath.c_str(), configurationInterface->c_str()); entityIdProp = configurationProperties.find("EntityId"); entityInstanceProp = configurationProperties.find("EntityInstance"); if (entityIdProp != configurationProperties.end()) { entityId = static_cast(std::get(entityIdProp->second)); } if (entityInstanceProp != configurationProperties.end()) { entityInstance = static_cast( std::get(entityInstanceProp->second)); } // stop searching Association records. break; } // end for Association vectors. if constexpr (debug) { std::fprintf(stderr, "path=%s, entityId=%d, entityInstance=%d\n", path.c_str(), entityId, entityInstance); } } } // namespace ipmi