dbus-sdr: Initial Dynamic Sensor Stack
Migrate intel-ipmi-oem dynamic sensor stack into
phosphor-host-ipmid for more general use.
The files are copied from
https://gerrit.openbmc-project.xyz/c/openbmc/intel-ipmi-oem/+/39743
https://gerrit.openbmc-project.xyz/plugins/gitiles/openbmc/intel-ipmi-oem/+/b910987a7d832e38e9342f0946aeb555a48f9cb0
Created `libdynamiccmds` to enable dynamic sensor stack.
Minor changes in the migration include:
1, Removing the use of `commandutils.hpp` in all files since it is only used
for
```
static constexpr bool debug = false;
```
It is added to `sdrutils.hpp` instead.
2, Update lastWriteAddr to size_t to match the vector.size() type
during comparison.
3, Renamed the sensorcommand unit test to sensorcommands_unitest.cpp
4, Removed unused variables.
- sensorcommands
```
constexpr uint8_t thresholdMask = 0xFF;
```
- sensorcommands_unitest
```
double yError = std::abs(y - yRoundtrip);
```
5, Removed Intel Specific Changes
- Redfish logs
- node manager/ME
- BIOS to SEL event
6, Removed externing a global variable for sensorTree.
- Replaced it with a method that returns a singleton
- auto& sensorTree = getSensorTree(); for access
7, Replaced intel_oem namespace with dynamic_sensors
8, Removed types.hpp and use `ipmid/types.hpp` directly
- Updated the types to match ipmid/types
- Added Association and std::vector<Association>> under Value.
9, Add cpp files for sdrutils and sensorutils.
10, link libipmid as it is required for getManagedObjects needed
by sensorcommands.cpp
Signed-off-by: Willy Tu <wltu@google.com>
Change-Id: If944620c895ecf4c9f4c3efe72479f4de276f4fb
Signed-off-by: Vijay Khemka <vijaykhemkalinux@gmail.com>
diff --git a/Makefile.am b/Makefile.am
index 82fdd5d..fbeb379 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -164,6 +164,22 @@
-version-info 0:0:0 -shared
libsysintfcmds_la_CXXFLAGS = $(COMMON_CXX)
+if FEATURE_DYNAMIC_SENSORS
+providers_LTLIBRARIES += libdynamiccmds.la
+libdynamiccmds_la_LIBADD = \
+ libipmid/libipmid.la
+libdynamiccmds_la_SOURCES = \
+ dbus-sdr/sensorcommands.cpp \
+ dbus-sdr/storagecommands.cpp \
+ dbus-sdr/sdrutils.cpp \
+ dbus-sdr/sensorutils.cpp
+libdynamiccmds_la_LDFLAGS = \
+ $(PHOSPHOR_LOGGING_LIBS) \
+ $(libmapper_LIBS) \
+ -version-info 0:0:0 -shared
+libdynamiccmds_la_CXXFLAGS = $(COMMON_CXX)
+endif
+
if FEATURE_IPMI_WHITELIST
libwhitelistdir = ${libdir}/ipmid-providers
libwhitelist_LTLIBRARIES = libwhitelist.la
diff --git a/configure.ac b/configure.ac
index 29f58e2..824b778 100644
--- a/configure.ac
+++ b/configure.ac
@@ -266,6 +266,17 @@
)
AM_CONDITIONAL([FEATURE_IPMI_WHITELIST], [test x$ipmi_whitelist = xtrue])
+# Dynamic sensors stack is enabled by default; offer a way to disable it
+AC_ARG_ENABLE([dynamic_sensors],
+ [ --enable-dynamic_sensors Enable/disable Dynamic Sensors stack],
+ [case "${enableval}" in
+ yes) dynamic_sensors=true ;;
+ no) dynamic_sensors=false ;;
+ *) AC_MSG_ERROR([bad value ${enableval} for --enable-dynamic_sensors]) ;;
+ esac],[dynamic_sensors=true]
+ )
+AM_CONDITIONAL([FEATURE_DYNAMIC_SENSORS], [test x$dynamic_sensors = xtrue])
+
# Create configured output
AC_CONFIG_FILES([
Makefile
diff --git a/dbus-sdr/sdrutils.cpp b/dbus-sdr/sdrutils.cpp
new file mode 100644
index 0000000..a6468bb
--- /dev/null
+++ b/dbus-sdr/sdrutils.cpp
@@ -0,0 +1,436 @@
+/*
+// 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 "dbus-sdr/sdrutils.hpp"
+
+namespace details
+{
+bool getSensorSubtree(std::shared_ptr<SensorSubTree>& subtree)
+{
+ static std::shared_ptr<SensorSubTree> sensorTreePtr;
+ std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus();
+ static sdbusplus::bus::match::match sensorAdded(
+ *dbus,
+ "type='signal',member='InterfacesAdded',arg0path='/xyz/openbmc_project/"
+ "sensors/'",
+ [](sdbusplus::message::message& m) { sensorTreePtr.reset(); });
+
+ static sdbusplus::bus::match::match sensorRemoved(
+ *dbus,
+ "type='signal',member='InterfacesRemoved',arg0path='/xyz/"
+ "openbmc_project/sensors/'",
+ [](sdbusplus::message::message& m) { sensorTreePtr.reset(); });
+
+ bool sensorTreeUpdated = false;
+ if (sensorTreePtr)
+ {
+ subtree = sensorTreePtr;
+ return sensorTreeUpdated;
+ }
+
+ sensorTreePtr = std::make_shared<SensorSubTree>();
+
+ auto mapperCall =
+ dbus->new_method_call("xyz.openbmc_project.ObjectMapper",
+ "/xyz/openbmc_project/object_mapper",
+ "xyz.openbmc_project.ObjectMapper", "GetSubTree");
+ static constexpr const int32_t depth = 2;
+ static constexpr std::array<const char*, 3> 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 (sdbusplus::exception_t& e)
+ {
+ phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
+ return sensorTreeUpdated;
+ }
+ subtree = sensorTreePtr;
+ sensorTreeUpdated = true;
+ return sensorTreeUpdated;
+}
+
+bool getSensorNumMap(std::shared_ptr<SensorNumMap>& sensorNumMap)
+{
+ static std::shared_ptr<SensorNumMap> sensorNumMapPtr;
+ bool sensorNumMapUpated = false;
+
+ std::shared_ptr<SensorSubTree> sensorTree;
+ bool sensorTreeUpdated = details::getSensorSubtree(sensorTree);
+ if (!sensorTree)
+ {
+ return sensorNumMapUpated;
+ }
+
+ if (!sensorTreeUpdated && sensorNumMapPtr)
+ {
+ sensorNumMap = sensorNumMapPtr;
+ return sensorNumMapUpated;
+ }
+
+ sensorNumMapPtr = std::make_shared<SensorNumMap>();
+
+ 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
+
+bool getSensorSubtree(SensorSubTree& subtree)
+{
+ std::shared_ptr<SensorSubTree> sensorTree;
+ details::getSensorSubtree(sensorTree);
+ if (!sensorTree)
+ {
+ return false;
+ }
+
+ subtree = *sensorTree;
+ return true;
+}
+
+std::string getSensorTypeStringFromPath(const std::string& path)
+{
+ // get sensor type string from path, path is defined as
+ // /xyz/openbmc_project/sensors/<type>/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);
+}
+
+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<uint8_t>(findSensor->second);
+ } // else default 0x0 RESERVED
+
+ return sensorType;
+}
+
+uint16_t getSensorNumberFromPath(const std::string& path)
+{
+ std::shared_ptr<SensorNumMap> sensorNumMapPtr;
+ details::getSensorNumMap(sensorNumMapPtr);
+ if (!sensorNumMapPtr)
+ {
+ return invalidSensorNumber;
+ }
+
+ try
+ {
+ return sensorNumMapPtr->right.at(path);
+ }
+ catch (std::out_of_range& e)
+ {
+ phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
+ return invalidSensorNumber;
+ }
+}
+
+uint8_t getSensorEventTypeFromPath(const std::string& path)
+{
+ // TODO: Add support for additional reading types as needed
+ return 0x1; // reading type = threshold
+}
+
+std::string getPathFromSensorNumber(uint16_t sensorNum)
+{
+ std::shared_ptr<SensorNumMap> sensorNumMapPtr;
+ details::getSensorNumMap(sensorNumMapPtr);
+ if (!sensorNumMapPtr)
+ {
+ return std::string();
+ }
+
+ try
+ {
+ return sensorNumMapPtr->left.at(sensorNum);
+ }
+ catch (std::out_of_range& e)
+ {
+ phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
+ return std::string();
+ }
+}
+
+namespace ipmi
+{
+
+std::map<std::string, std::vector<std::string>>
+ getObjectInterfaces(const char* path)
+{
+ std::map<std::string, std::vector<std::string>> interfacesResponse;
+ std::vector<std::string> interfaces;
+ std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus();
+
+ sdbusplus::message::message 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::message response = dbus->call(getObjectMessage);
+ response.read(interfacesResponse);
+ }
+ catch (const std::exception& e)
+ {
+ phosphor::logging::log<phosphor::logging::level::ERR>(
+ "Failed to GetObject", phosphor::logging::entry("PATH=%s", path),
+ phosphor::logging::entry("WHAT=%s", e.what()));
+ }
+
+ return interfacesResponse;
+}
+
+std::map<std::string, Value> getEntityManagerProperties(const char* path,
+ const char* interface)
+{
+ std::map<std::string, Value> properties;
+ std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus();
+
+ sdbusplus::message::message getProperties =
+ dbus->new_method_call("xyz.openbmc_project.EntityManager", path,
+ "org.freedesktop.DBus.Properties", "GetAll");
+ getProperties.append(interface);
+
+ try
+ {
+ sdbusplus::message::message response = dbus->call(getProperties);
+ response.read(properties);
+ }
+ catch (const std::exception& e)
+ {
+ phosphor::logging::log<phosphor::logging::level::ERR>(
+ "Failed to GetAll", phosphor::logging::entry("PATH=%s", path),
+ phosphor::logging::entry("INTF=%s", interface),
+ phosphor::logging::entry("WHAT=%s", e.what()));
+ }
+
+ return properties;
+}
+
+const std::string* getSensorConfigurationInterface(
+ const std::map<std::string, std::vector<std::string>>&
+ 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.
+void updateIpmiFromAssociation(const std::string& path,
+ const DbusInterfaceMap& 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<Association> associationValues =
+ std::get<std::vector<Association>>(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<std::string, Value> 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<uint8_t>(std::get<uint64_t>(entityIdProp->second));
+ }
+ if (entityInstanceProp != ipmiProperties.end())
+ {
+ entityInstance = static_cast<uint8_t>(
+ std::get<uint64_t>(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<std::string, std::vector<std::string>>
+ 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<std::string, Value> configurationProperties =
+ getEntityManagerProperties(sensorConfigPath.c_str(),
+ configurationInterface->c_str());
+
+ entityIdProp = configurationProperties.find("EntityId");
+ entityInstanceProp = configurationProperties.find("EntityInstance");
+ if (entityIdProp != configurationProperties.end())
+ {
+ entityId =
+ static_cast<uint8_t>(std::get<uint64_t>(entityIdProp->second));
+ }
+ if (entityInstanceProp != configurationProperties.end())
+ {
+ entityInstance = static_cast<uint8_t>(
+ std::get<uint64_t>(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
diff --git a/dbus-sdr/sensorcommands.cpp b/dbus-sdr/sensorcommands.cpp
new file mode 100644
index 0000000..b6099ec
--- /dev/null
+++ b/dbus-sdr/sensorcommands.cpp
@@ -0,0 +1,1597 @@
+/*
+// Copyright (c) 2017 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 "dbus-sdr/sensorcommands.hpp"
+
+#include "dbus-sdr/sdrutils.hpp"
+#include "dbus-sdr/sensorutils.hpp"
+#include "dbus-sdr/storagecommands.hpp"
+
+#include <algorithm>
+#include <array>
+#include <boost/algorithm/string.hpp>
+#include <boost/container/flat_map.hpp>
+#include <chrono>
+#include <cmath>
+#include <cstring>
+#include <iostream>
+#include <ipmid/api.hpp>
+#include <ipmid/types.hpp>
+#include <ipmid/utils.hpp>
+#include <map>
+#include <memory>
+#include <optional>
+#include <phosphor-logging/log.hpp>
+#include <sdbusplus/bus.hpp>
+#include <stdexcept>
+#include <string>
+#include <utility>
+#include <variant>
+
+namespace ipmi
+{
+using SDRObjectType =
+ boost::container::flat_map<uint16_t, std::vector<uint8_t>>;
+
+static constexpr int sensorMapUpdatePeriod = 10;
+
+constexpr size_t maxSDRTotalSize =
+ 76; // Largest SDR Record Size (type 01) + SDR Overheader Size
+constexpr static const uint32_t noTimestamp = 0xFFFFFFFF;
+
+static uint16_t sdrReservationID;
+static uint32_t sdrLastAdd = noTimestamp;
+static uint32_t sdrLastRemove = noTimestamp;
+static constexpr size_t lastRecordIndex = 0xFFFF;
+static constexpr int GENERAL_ERROR = -1;
+
+SDRObjectType sensorDataRecords;
+
+static boost::container::flat_map<std::string, ObjectValueTree> SensorCache;
+
+// Specify the comparison required to sort and find char* map objects
+struct CmpStr
+{
+ bool operator()(const char* a, const char* b) const
+ {
+ return std::strcmp(a, b) < 0;
+ }
+};
+const static boost::container::flat_map<const char*, SensorUnits, CmpStr>
+ sensorUnits{{{"temperature", SensorUnits::degreesC},
+ {"voltage", SensorUnits::volts},
+ {"current", SensorUnits::amps},
+ {"fan_tach", SensorUnits::rpm},
+ {"power", SensorUnits::watts}}};
+
+void registerSensorFunctions() __attribute__((constructor));
+
+static sdbusplus::bus::match::match sensorAdded(
+ *getSdBus(),
+ "type='signal',member='InterfacesAdded',arg0path='/xyz/openbmc_project/"
+ "sensors/'",
+ [](sdbusplus::message::message& m) {
+ getSensorTree().clear();
+ sdrLastAdd = std::chrono::duration_cast<std::chrono::seconds>(
+ std::chrono::system_clock::now().time_since_epoch())
+ .count();
+ });
+
+static sdbusplus::bus::match::match sensorRemoved(
+ *getSdBus(),
+ "type='signal',member='InterfacesRemoved',arg0path='/xyz/openbmc_project/"
+ "sensors/'",
+ [](sdbusplus::message::message& m) {
+ getSensorTree().clear();
+ sdrLastRemove = std::chrono::duration_cast<std::chrono::seconds>(
+ std::chrono::system_clock::now().time_since_epoch())
+ .count();
+ });
+
+// this keeps track of deassertions for sensor event status command. A
+// deasertion can only happen if an assertion was seen first.
+static boost::container::flat_map<
+ std::string, boost::container::flat_map<std::string, std::optional<bool>>>
+ thresholdDeassertMap;
+
+static sdbusplus::bus::match::match thresholdChanged(
+ *getSdBus(),
+ "type='signal',member='PropertiesChanged',interface='org.freedesktop.DBus."
+ "Properties',arg0namespace='xyz.openbmc_project.Sensor.Threshold'",
+ [](sdbusplus::message::message& m) {
+ boost::container::flat_map<std::string, std::variant<bool, double>>
+ values;
+ m.read(std::string(), values);
+
+ auto findAssert =
+ std::find_if(values.begin(), values.end(), [](const auto& pair) {
+ return pair.first.find("Alarm") != std::string::npos;
+ });
+ if (findAssert != values.end())
+ {
+ auto ptr = std::get_if<bool>(&(findAssert->second));
+ if (ptr == nullptr)
+ {
+ phosphor::logging::log<phosphor::logging::level::ERR>(
+ "thresholdChanged: Assert non bool");
+ return;
+ }
+ if (*ptr)
+ {
+ phosphor::logging::log<phosphor::logging::level::INFO>(
+ "thresholdChanged: Assert",
+ phosphor::logging::entry("SENSOR=%s", m.get_path()));
+ thresholdDeassertMap[m.get_path()][findAssert->first] = *ptr;
+ }
+ else
+ {
+ auto& value =
+ thresholdDeassertMap[m.get_path()][findAssert->first];
+ if (value)
+ {
+ phosphor::logging::log<phosphor::logging::level::INFO>(
+ "thresholdChanged: deassert",
+ phosphor::logging::entry("SENSOR=%s", m.get_path()));
+ value = *ptr;
+ }
+ }
+ }
+ });
+
+static void getSensorMaxMin(const DbusInterfaceMap& sensorMap, double& max,
+ double& min)
+{
+ max = 127;
+ min = -128;
+
+ auto sensorObject = sensorMap.find("xyz.openbmc_project.Sensor.Value");
+ auto critical =
+ sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Critical");
+ auto warning =
+ sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Warning");
+
+ if (sensorObject != sensorMap.end())
+ {
+ auto maxMap = sensorObject->second.find("MaxValue");
+ auto minMap = sensorObject->second.find("MinValue");
+
+ if (maxMap != sensorObject->second.end())
+ {
+ max = std::visit(VariantToDoubleVisitor(), maxMap->second);
+ }
+ if (minMap != sensorObject->second.end())
+ {
+ min = std::visit(VariantToDoubleVisitor(), minMap->second);
+ }
+ }
+ if (critical != sensorMap.end())
+ {
+ auto lower = critical->second.find("CriticalLow");
+ auto upper = critical->second.find("CriticalHigh");
+ if (lower != critical->second.end())
+ {
+ double value = std::visit(VariantToDoubleVisitor(), lower->second);
+ min = std::min(value, min);
+ }
+ if (upper != critical->second.end())
+ {
+ double value = std::visit(VariantToDoubleVisitor(), upper->second);
+ max = std::max(value, max);
+ }
+ }
+ if (warning != sensorMap.end())
+ {
+
+ auto lower = warning->second.find("WarningLow");
+ auto upper = warning->second.find("WarningHigh");
+ if (lower != warning->second.end())
+ {
+ double value = std::visit(VariantToDoubleVisitor(), lower->second);
+ min = std::min(value, min);
+ }
+ if (upper != warning->second.end())
+ {
+ double value = std::visit(VariantToDoubleVisitor(), upper->second);
+ max = std::max(value, max);
+ }
+ }
+}
+
+static bool getSensorMap(ipmi::Context::ptr ctx, std::string sensorConnection,
+ std::string sensorPath, DbusInterfaceMap& sensorMap)
+{
+ static boost::container::flat_map<
+ std::string, std::chrono::time_point<std::chrono::steady_clock>>
+ updateTimeMap;
+
+ auto updateFind = updateTimeMap.find(sensorConnection);
+ auto lastUpdate = std::chrono::time_point<std::chrono::steady_clock>();
+ if (updateFind != updateTimeMap.end())
+ {
+ lastUpdate = updateFind->second;
+ }
+
+ auto now = std::chrono::steady_clock::now();
+
+ if (std::chrono::duration_cast<std::chrono::seconds>(now - lastUpdate)
+ .count() > sensorMapUpdatePeriod)
+ {
+ updateTimeMap[sensorConnection] = now;
+
+ ObjectValueTree managedObjects;
+ boost::system::error_code ec = getManagedObjects(
+ ctx, sensorConnection.c_str(), "/", managedObjects);
+ if (ec)
+ {
+ phosphor::logging::log<phosphor::logging::level::ERR>(
+ "GetMangagedObjects for getSensorMap failed",
+ phosphor::logging::entry("ERROR=%s", ec.message().c_str()));
+
+ return false;
+ }
+
+ SensorCache[sensorConnection] = managedObjects;
+ }
+ auto connection = SensorCache.find(sensorConnection);
+ if (connection == SensorCache.end())
+ {
+ return false;
+ }
+ auto path = connection->second.find(sensorPath);
+ if (path == connection->second.end())
+ {
+ return false;
+ }
+ sensorMap = path->second;
+
+ return true;
+}
+
+ipmi::RspType<> ipmiSenPlatformEvent(uint8_t generatorID, uint8_t evmRev,
+ uint8_t sensorType, uint8_t sensorNum,
+ uint8_t eventType, uint8_t eventData1,
+ std::optional<uint8_t> eventData2,
+ std::optional<uint8_t> eventData3)
+{
+ return ipmi::responseSuccess();
+}
+
+ipmi::RspType<uint8_t, uint8_t, uint8_t, std::optional<uint8_t>>
+ ipmiSenGetSensorReading(ipmi::Context::ptr ctx, uint8_t sensnum)
+{
+ std::string connection;
+ std::string path;
+
+ auto status = getSensorConnection(ctx, sensnum, connection, path);
+ if (status)
+ {
+ return ipmi::response(status);
+ }
+
+ DbusInterfaceMap sensorMap;
+ if (!getSensorMap(ctx, connection, path, sensorMap))
+ {
+ return ipmi::responseResponseError();
+ }
+ auto sensorObject = sensorMap.find("xyz.openbmc_project.Sensor.Value");
+
+ if (sensorObject == sensorMap.end() ||
+ sensorObject->second.find("Value") == sensorObject->second.end())
+ {
+ return ipmi::responseResponseError();
+ }
+ auto& valueVariant = sensorObject->second["Value"];
+ double reading = std::visit(VariantToDoubleVisitor(), valueVariant);
+
+ double max = 0;
+ double min = 0;
+ getSensorMaxMin(sensorMap, max, min);
+
+ int16_t mValue = 0;
+ int16_t bValue = 0;
+ int8_t rExp = 0;
+ int8_t bExp = 0;
+ bool bSigned = false;
+
+ if (!getSensorAttributes(max, min, mValue, rExp, bValue, bExp, bSigned))
+ {
+ return ipmi::responseResponseError();
+ }
+
+ uint8_t value =
+ scaleIPMIValueFromDouble(reading, mValue, rExp, bValue, bExp, bSigned);
+ uint8_t operation =
+ static_cast<uint8_t>(IPMISensorReadingByte2::sensorScanningEnable);
+ operation |=
+ static_cast<uint8_t>(IPMISensorReadingByte2::eventMessagesEnable);
+ bool notReading = std::isnan(reading);
+
+ if (!notReading)
+ {
+ auto availableObject =
+ sensorMap.find("xyz.openbmc_project.State.Decorator.Availability");
+ if (availableObject != sensorMap.end())
+ {
+ auto findAvailable = availableObject->second.find("Available");
+ if (findAvailable != availableObject->second.end())
+ {
+ bool* available = std::get_if<bool>(&(findAvailable->second));
+ if (available && !(*available))
+ {
+ notReading = true;
+ }
+ }
+ }
+ }
+
+ if (notReading)
+ {
+ operation |= static_cast<uint8_t>(
+ IPMISensorReadingByte2::readingStateUnavailable);
+ }
+
+ uint8_t thresholds = 0;
+
+ auto warningObject =
+ sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Warning");
+ if (warningObject != sensorMap.end())
+ {
+ auto alarmHigh = warningObject->second.find("WarningAlarmHigh");
+ auto alarmLow = warningObject->second.find("WarningAlarmLow");
+ if (alarmHigh != warningObject->second.end())
+ {
+ if (std::get<bool>(alarmHigh->second))
+ {
+ thresholds |= static_cast<uint8_t>(
+ IPMISensorReadingByte3::upperNonCritical);
+ }
+ }
+ if (alarmLow != warningObject->second.end())
+ {
+ if (std::get<bool>(alarmLow->second))
+ {
+ thresholds |= static_cast<uint8_t>(
+ IPMISensorReadingByte3::lowerNonCritical);
+ }
+ }
+ }
+
+ auto criticalObject =
+ sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Critical");
+ if (criticalObject != sensorMap.end())
+ {
+ auto alarmHigh = criticalObject->second.find("CriticalAlarmHigh");
+ auto alarmLow = criticalObject->second.find("CriticalAlarmLow");
+ if (alarmHigh != criticalObject->second.end())
+ {
+ if (std::get<bool>(alarmHigh->second))
+ {
+ thresholds |=
+ static_cast<uint8_t>(IPMISensorReadingByte3::upperCritical);
+ }
+ }
+ if (alarmLow != criticalObject->second.end())
+ {
+ if (std::get<bool>(alarmLow->second))
+ {
+ thresholds |=
+ static_cast<uint8_t>(IPMISensorReadingByte3::lowerCritical);
+ }
+ }
+ }
+
+ // no discrete as of today so optional byte is never returned
+ return ipmi::responseSuccess(value, operation, thresholds, std::nullopt);
+}
+
+/** @brief implements the Set Sensor threshold command
+ * @param sensorNumber - sensor number
+ * @param lowerNonCriticalThreshMask
+ * @param lowerCriticalThreshMask
+ * @param lowerNonRecovThreshMask
+ * @param upperNonCriticalThreshMask
+ * @param upperCriticalThreshMask
+ * @param upperNonRecovThreshMask
+ * @param reserved
+ * @param lowerNonCritical - lower non-critical threshold
+ * @param lowerCritical - Lower critical threshold
+ * @param lowerNonRecoverable - Lower non recovarable threshold
+ * @param upperNonCritical - Upper non-critical threshold
+ * @param upperCritical - Upper critical
+ * @param upperNonRecoverable - Upper Non-recoverable
+ *
+ * @returns IPMI completion code
+ */
+ipmi::RspType<> ipmiSenSetSensorThresholds(
+ ipmi::Context::ptr ctx, uint8_t sensorNum, bool lowerNonCriticalThreshMask,
+ bool lowerCriticalThreshMask, bool lowerNonRecovThreshMask,
+ bool upperNonCriticalThreshMask, bool upperCriticalThreshMask,
+ bool upperNonRecovThreshMask, uint2_t reserved, uint8_t lowerNonCritical,
+ uint8_t lowerCritical, uint8_t lowerNonRecoverable,
+ uint8_t upperNonCritical, uint8_t upperCritical,
+ uint8_t upperNonRecoverable)
+{
+ if (reserved)
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ // lower nc and upper nc not suppported on any sensor
+ if (lowerNonRecovThreshMask || upperNonRecovThreshMask)
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ // if none of the threshold mask are set, nothing to do
+ if (!(lowerNonCriticalThreshMask | lowerCriticalThreshMask |
+ lowerNonRecovThreshMask | upperNonCriticalThreshMask |
+ upperCriticalThreshMask | upperNonRecovThreshMask))
+ {
+ return ipmi::responseSuccess();
+ }
+
+ std::string connection;
+ std::string path;
+
+ ipmi::Cc status = getSensorConnection(ctx, sensorNum, connection, path);
+ if (status)
+ {
+ return ipmi::response(status);
+ }
+ DbusInterfaceMap sensorMap;
+ if (!getSensorMap(ctx, connection, path, sensorMap))
+ {
+ return ipmi::responseResponseError();
+ }
+
+ double max = 0;
+ double min = 0;
+ getSensorMaxMin(sensorMap, max, min);
+
+ int16_t mValue = 0;
+ int16_t bValue = 0;
+ int8_t rExp = 0;
+ int8_t bExp = 0;
+ bool bSigned = false;
+
+ if (!getSensorAttributes(max, min, mValue, rExp, bValue, bExp, bSigned))
+ {
+ return ipmi::responseResponseError();
+ }
+
+ // store a vector of property name, value to set, and interface
+ std::vector<std::tuple<std::string, uint8_t, std::string>> thresholdsToSet;
+
+ // define the indexes of the tuple
+ constexpr uint8_t propertyName = 0;
+ constexpr uint8_t thresholdValue = 1;
+ constexpr uint8_t interface = 2;
+ // verifiy all needed fields are present
+ if (lowerCriticalThreshMask || upperCriticalThreshMask)
+ {
+ auto findThreshold =
+ sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Critical");
+ if (findThreshold == sensorMap.end())
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+ if (lowerCriticalThreshMask)
+ {
+ auto findLower = findThreshold->second.find("CriticalLow");
+ if (findLower == findThreshold->second.end())
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+ thresholdsToSet.emplace_back("CriticalLow", lowerCritical,
+ findThreshold->first);
+ }
+ if (upperCriticalThreshMask)
+ {
+ auto findUpper = findThreshold->second.find("CriticalHigh");
+ if (findUpper == findThreshold->second.end())
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+ thresholdsToSet.emplace_back("CriticalHigh", upperCritical,
+ findThreshold->first);
+ }
+ }
+ if (lowerNonCriticalThreshMask || upperNonCriticalThreshMask)
+ {
+ auto findThreshold =
+ sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Warning");
+ if (findThreshold == sensorMap.end())
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+ if (lowerNonCriticalThreshMask)
+ {
+ auto findLower = findThreshold->second.find("WarningLow");
+ if (findLower == findThreshold->second.end())
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+ thresholdsToSet.emplace_back("WarningLow", lowerNonCritical,
+ findThreshold->first);
+ }
+ if (upperNonCriticalThreshMask)
+ {
+ auto findUpper = findThreshold->second.find("WarningHigh");
+ if (findUpper == findThreshold->second.end())
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+ thresholdsToSet.emplace_back("WarningHigh", upperNonCritical,
+ findThreshold->first);
+ }
+ }
+ for (const auto& property : thresholdsToSet)
+ {
+ // from section 36.3 in the IPMI Spec, assume all linear
+ double valueToSet = ((mValue * std::get<thresholdValue>(property)) +
+ (bValue * std::pow(10.0, bExp))) *
+ std::pow(10.0, rExp);
+ setDbusProperty(
+ *getSdBus(), connection, path, std::get<interface>(property),
+ std::get<propertyName>(property), ipmi::Value(valueToSet));
+ }
+ return ipmi::responseSuccess();
+}
+
+IPMIThresholds getIPMIThresholds(const DbusInterfaceMap& sensorMap)
+{
+ IPMIThresholds resp;
+ auto warningInterface =
+ sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Warning");
+ auto criticalInterface =
+ sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Critical");
+
+ if ((warningInterface != sensorMap.end()) ||
+ (criticalInterface != sensorMap.end()))
+ {
+ auto sensorPair = sensorMap.find("xyz.openbmc_project.Sensor.Value");
+
+ if (sensorPair == sensorMap.end())
+ {
+ // should not have been able to find a sensor not implementing
+ // the sensor object
+ throw std::runtime_error("Invalid sensor map");
+ }
+
+ double max = 0;
+ double min = 0;
+ getSensorMaxMin(sensorMap, max, min);
+
+ int16_t mValue = 0;
+ int16_t bValue = 0;
+ int8_t rExp = 0;
+ int8_t bExp = 0;
+ bool bSigned = false;
+
+ if (!getSensorAttributes(max, min, mValue, rExp, bValue, bExp, bSigned))
+ {
+ throw std::runtime_error("Invalid sensor atrributes");
+ }
+ if (warningInterface != sensorMap.end())
+ {
+ auto& warningMap = warningInterface->second;
+
+ auto warningHigh = warningMap.find("WarningHigh");
+ auto warningLow = warningMap.find("WarningLow");
+
+ if (warningHigh != warningMap.end())
+ {
+
+ double value =
+ std::visit(VariantToDoubleVisitor(), warningHigh->second);
+ resp.warningHigh = scaleIPMIValueFromDouble(
+ value, mValue, rExp, bValue, bExp, bSigned);
+ }
+ if (warningLow != warningMap.end())
+ {
+ double value =
+ std::visit(VariantToDoubleVisitor(), warningLow->second);
+ resp.warningLow = scaleIPMIValueFromDouble(
+ value, mValue, rExp, bValue, bExp, bSigned);
+ }
+ }
+ if (criticalInterface != sensorMap.end())
+ {
+ auto& criticalMap = criticalInterface->second;
+
+ auto criticalHigh = criticalMap.find("CriticalHigh");
+ auto criticalLow = criticalMap.find("CriticalLow");
+
+ if (criticalHigh != criticalMap.end())
+ {
+ double value =
+ std::visit(VariantToDoubleVisitor(), criticalHigh->second);
+ resp.criticalHigh = scaleIPMIValueFromDouble(
+ value, mValue, rExp, bValue, bExp, bSigned);
+ }
+ if (criticalLow != criticalMap.end())
+ {
+ double value =
+ std::visit(VariantToDoubleVisitor(), criticalLow->second);
+ resp.criticalLow = scaleIPMIValueFromDouble(
+ value, mValue, rExp, bValue, bExp, bSigned);
+ }
+ }
+ }
+ return resp;
+}
+
+ipmi::RspType<uint8_t, // readable
+ uint8_t, // lowerNCrit
+ uint8_t, // lowerCrit
+ uint8_t, // lowerNrecoverable
+ uint8_t, // upperNC
+ uint8_t, // upperCrit
+ uint8_t> // upperNRecoverable
+ ipmiSenGetSensorThresholds(ipmi::Context::ptr ctx, uint8_t sensorNumber)
+{
+ std::string connection;
+ std::string path;
+
+ auto status = getSensorConnection(ctx, sensorNumber, connection, path);
+ if (status)
+ {
+ return ipmi::response(status);
+ }
+
+ DbusInterfaceMap sensorMap;
+ if (!getSensorMap(ctx, connection, path, sensorMap))
+ {
+ return ipmi::responseResponseError();
+ }
+
+ IPMIThresholds thresholdData;
+ try
+ {
+ thresholdData = getIPMIThresholds(sensorMap);
+ }
+ catch (std::exception&)
+ {
+ return ipmi::responseResponseError();
+ }
+
+ uint8_t readable = 0;
+ uint8_t lowerNC = 0;
+ uint8_t lowerCritical = 0;
+ uint8_t lowerNonRecoverable = 0;
+ uint8_t upperNC = 0;
+ uint8_t upperCritical = 0;
+ uint8_t upperNonRecoverable = 0;
+
+ if (thresholdData.warningHigh)
+ {
+ readable |=
+ 1 << static_cast<uint8_t>(IPMIThresholdRespBits::upperNonCritical);
+ upperNC = *thresholdData.warningHigh;
+ }
+ if (thresholdData.warningLow)
+ {
+ readable |=
+ 1 << static_cast<uint8_t>(IPMIThresholdRespBits::lowerNonCritical);
+ lowerNC = *thresholdData.warningLow;
+ }
+
+ if (thresholdData.criticalHigh)
+ {
+ readable |=
+ 1 << static_cast<uint8_t>(IPMIThresholdRespBits::upperCritical);
+ upperCritical = *thresholdData.criticalHigh;
+ }
+ if (thresholdData.criticalLow)
+ {
+ readable |=
+ 1 << static_cast<uint8_t>(IPMIThresholdRespBits::lowerCritical);
+ lowerCritical = *thresholdData.criticalLow;
+ }
+
+ return ipmi::responseSuccess(readable, lowerNC, lowerCritical,
+ lowerNonRecoverable, upperNC, upperCritical,
+ upperNonRecoverable);
+}
+
+/** @brief implements the get Sensor event enable command
+ * @param sensorNumber - sensor number
+ *
+ * @returns IPMI completion code plus response data
+ * - enabled - Sensor Event messages
+ * - assertionEnabledLsb - Assertion event messages
+ * - assertionEnabledMsb - Assertion event messages
+ * - deassertionEnabledLsb - Deassertion event messages
+ * - deassertionEnabledMsb - Deassertion event messages
+ */
+
+ipmi::RspType<uint8_t, // enabled
+ uint8_t, // assertionEnabledLsb
+ uint8_t, // assertionEnabledMsb
+ uint8_t, // deassertionEnabledLsb
+ uint8_t> // deassertionEnabledMsb
+ ipmiSenGetSensorEventEnable(ipmi::Context::ptr ctx, uint8_t sensorNum)
+{
+ std::string connection;
+ std::string path;
+
+ uint8_t enabled = 0;
+ uint8_t assertionEnabledLsb = 0;
+ uint8_t assertionEnabledMsb = 0;
+ uint8_t deassertionEnabledLsb = 0;
+ uint8_t deassertionEnabledMsb = 0;
+
+ auto status = getSensorConnection(ctx, sensorNum, connection, path);
+ if (status)
+ {
+ return ipmi::response(status);
+ }
+
+ DbusInterfaceMap sensorMap;
+ if (!getSensorMap(ctx, connection, path, sensorMap))
+ {
+ return ipmi::responseResponseError();
+ }
+
+ auto warningInterface =
+ sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Warning");
+ auto criticalInterface =
+ sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Critical");
+ if ((warningInterface != sensorMap.end()) ||
+ (criticalInterface != sensorMap.end()))
+ {
+ enabled = static_cast<uint8_t>(
+ IPMISensorEventEnableByte2::sensorScanningEnable);
+ if (warningInterface != sensorMap.end())
+ {
+ auto& warningMap = warningInterface->second;
+
+ auto warningHigh = warningMap.find("WarningHigh");
+ auto warningLow = warningMap.find("WarningLow");
+ if (warningHigh != warningMap.end())
+ {
+ assertionEnabledLsb |= static_cast<uint8_t>(
+ IPMISensorEventEnableThresholds::upperNonCriticalGoingHigh);
+ deassertionEnabledLsb |= static_cast<uint8_t>(
+ IPMISensorEventEnableThresholds::upperNonCriticalGoingLow);
+ }
+ if (warningLow != warningMap.end())
+ {
+ assertionEnabledLsb |= static_cast<uint8_t>(
+ IPMISensorEventEnableThresholds::lowerNonCriticalGoingLow);
+ deassertionEnabledLsb |= static_cast<uint8_t>(
+ IPMISensorEventEnableThresholds::lowerNonCriticalGoingHigh);
+ }
+ }
+ if (criticalInterface != sensorMap.end())
+ {
+ auto& criticalMap = criticalInterface->second;
+
+ auto criticalHigh = criticalMap.find("CriticalHigh");
+ auto criticalLow = criticalMap.find("CriticalLow");
+
+ if (criticalHigh != criticalMap.end())
+ {
+ assertionEnabledMsb |= static_cast<uint8_t>(
+ IPMISensorEventEnableThresholds::upperCriticalGoingHigh);
+ deassertionEnabledMsb |= static_cast<uint8_t>(
+ IPMISensorEventEnableThresholds::upperCriticalGoingLow);
+ }
+ if (criticalLow != criticalMap.end())
+ {
+ assertionEnabledLsb |= static_cast<uint8_t>(
+ IPMISensorEventEnableThresholds::lowerCriticalGoingLow);
+ deassertionEnabledLsb |= static_cast<uint8_t>(
+ IPMISensorEventEnableThresholds::lowerCriticalGoingHigh);
+ }
+ }
+ }
+
+ return ipmi::responseSuccess(enabled, assertionEnabledLsb,
+ assertionEnabledMsb, deassertionEnabledLsb,
+ deassertionEnabledMsb);
+}
+
+/** @brief implements the get Sensor event status command
+ * @param sensorNumber - sensor number, FFh = reserved
+ *
+ * @returns IPMI completion code plus response data
+ * - sensorEventStatus - Sensor Event messages state
+ * - assertions - Assertion event messages
+ * - deassertions - Deassertion event messages
+ */
+ipmi::RspType<uint8_t, // sensorEventStatus
+ std::bitset<16>, // assertions
+ std::bitset<16> // deassertion
+ >
+ ipmiSenGetSensorEventStatus(ipmi::Context::ptr ctx, uint8_t sensorNum)
+{
+ if (sensorNum == reservedSensorNumber)
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ std::string connection;
+ std::string path;
+ auto status = getSensorConnection(ctx, sensorNum, connection, path);
+ if (status)
+ {
+ phosphor::logging::log<phosphor::logging::level::ERR>(
+ "ipmiSenGetSensorEventStatus: Sensor connection Error",
+ phosphor::logging::entry("SENSOR=%d", sensorNum));
+ return ipmi::response(status);
+ }
+
+ DbusInterfaceMap sensorMap;
+ if (!getSensorMap(ctx, connection, path, sensorMap))
+ {
+ phosphor::logging::log<phosphor::logging::level::ERR>(
+ "ipmiSenGetSensorEventStatus: Sensor Mapping Error",
+ phosphor::logging::entry("SENSOR=%s", path.c_str()));
+ return ipmi::responseResponseError();
+ }
+ auto warningInterface =
+ sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Warning");
+ auto criticalInterface =
+ sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Critical");
+
+ uint8_t sensorEventStatus =
+ static_cast<uint8_t>(IPMISensorEventEnableByte2::sensorScanningEnable);
+
+ std::optional<bool> criticalDeassertHigh =
+ thresholdDeassertMap[path]["CriticalAlarmHigh"];
+ std::optional<bool> criticalDeassertLow =
+ thresholdDeassertMap[path]["CriticalAlarmLow"];
+ std::optional<bool> warningDeassertHigh =
+ thresholdDeassertMap[path]["WarningAlarmHigh"];
+ std::optional<bool> warningDeassertLow =
+ thresholdDeassertMap[path]["WarningAlarmLow"];
+
+ std::bitset<16> assertions = 0;
+ std::bitset<16> deassertions = 0;
+
+ if (criticalDeassertHigh && !*criticalDeassertHigh)
+ {
+ deassertions.set(static_cast<size_t>(
+ IPMIGetSensorEventEnableThresholds::upperCriticalGoingHigh));
+ }
+ if (criticalDeassertLow && !*criticalDeassertLow)
+ {
+ deassertions.set(static_cast<size_t>(
+ IPMIGetSensorEventEnableThresholds::upperCriticalGoingLow));
+ }
+ if (warningDeassertHigh && !*warningDeassertHigh)
+ {
+ deassertions.set(static_cast<size_t>(
+ IPMIGetSensorEventEnableThresholds::upperNonCriticalGoingHigh));
+ }
+ if (warningDeassertLow && !*warningDeassertLow)
+ {
+ deassertions.set(static_cast<size_t>(
+ IPMIGetSensorEventEnableThresholds::lowerNonCriticalGoingHigh));
+ }
+ if ((warningInterface != sensorMap.end()) ||
+ (criticalInterface != sensorMap.end()))
+ {
+ sensorEventStatus = static_cast<size_t>(
+ IPMISensorEventEnableByte2::eventMessagesEnable);
+ if (warningInterface != sensorMap.end())
+ {
+ auto& warningMap = warningInterface->second;
+
+ auto warningHigh = warningMap.find("WarningAlarmHigh");
+ auto warningLow = warningMap.find("WarningAlarmLow");
+ auto warningHighAlarm = false;
+ auto warningLowAlarm = false;
+
+ if (warningHigh != warningMap.end())
+ {
+ warningHighAlarm = std::get<bool>(warningHigh->second);
+ }
+ if (warningLow != warningMap.end())
+ {
+ warningLowAlarm = std::get<bool>(warningLow->second);
+ }
+ if (warningHighAlarm)
+ {
+ assertions.set(
+ static_cast<size_t>(IPMIGetSensorEventEnableThresholds::
+ upperNonCriticalGoingHigh));
+ }
+ if (warningLowAlarm)
+ {
+ assertions.set(
+ static_cast<size_t>(IPMIGetSensorEventEnableThresholds::
+ lowerNonCriticalGoingLow));
+ }
+ }
+ if (criticalInterface != sensorMap.end())
+ {
+ auto& criticalMap = criticalInterface->second;
+
+ auto criticalHigh = criticalMap.find("CriticalAlarmHigh");
+ auto criticalLow = criticalMap.find("CriticalAlarmLow");
+ auto criticalHighAlarm = false;
+ auto criticalLowAlarm = false;
+
+ if (criticalHigh != criticalMap.end())
+ {
+ criticalHighAlarm = std::get<bool>(criticalHigh->second);
+ }
+ if (criticalLow != criticalMap.end())
+ {
+ criticalLowAlarm = std::get<bool>(criticalLow->second);
+ }
+ if (criticalHighAlarm)
+ {
+ assertions.set(
+ static_cast<size_t>(IPMIGetSensorEventEnableThresholds::
+ upperCriticalGoingHigh));
+ }
+ if (criticalLowAlarm)
+ {
+ assertions.set(static_cast<size_t>(
+ IPMIGetSensorEventEnableThresholds::lowerCriticalGoingLow));
+ }
+ }
+ }
+
+ return ipmi::responseSuccess(sensorEventStatus, assertions, deassertions);
+}
+
+static int getSensorDataRecords(ipmi::Context::ptr ctx)
+{
+ auto& sensorTree = getSensorTree();
+ size_t recordID = 0;
+ size_t fruCount = 0;
+
+ ipmi::Cc ret = ipmi::storage::getFruSdrCount(ctx, fruCount);
+ if (ret != ipmi::ccSuccess)
+ {
+ return GENERAL_ERROR;
+ }
+
+ size_t lastRecord =
+ sensorTree.size() + fruCount + ipmi::storage::type12Count - 1;
+ if (lastRecord > lastRecordIndex)
+ {
+ return GENERAL_ERROR;
+ }
+
+ std::string connection;
+ std::string path;
+ for (const auto& sensor : sensorTree)
+ {
+
+ connection = sensor.second.begin()->first;
+ path = sensor.first;
+
+ DbusInterfaceMap sensorMap;
+ if (!getSensorMap(ctx, connection, path, sensorMap))
+ {
+ return GENERAL_ERROR;
+ }
+ uint16_t sensorNum = getSensorNumberFromPath(path);
+ if (sensorNum == invalidSensorNumber)
+ {
+ return GENERAL_ERROR;
+ }
+ uint8_t sensornumber = static_cast<uint8_t>(sensorNum);
+ uint8_t lun = static_cast<uint8_t>(sensorNum >> 8);
+
+ get_sdr::SensorDataFullRecord record = {0};
+
+ get_sdr::header::set_record_id(
+ recordID,
+ reinterpret_cast<get_sdr::SensorDataRecordHeader*>(&record));
+ record.header.sdr_version = ipmiSdrVersion;
+ record.header.record_type = get_sdr::SENSOR_DATA_FULL_RECORD;
+ record.header.record_length = sizeof(get_sdr::SensorDataFullRecord) -
+ sizeof(get_sdr::SensorDataRecordHeader);
+ record.key.owner_id = 0x20;
+ record.key.owner_lun = lun;
+ record.key.sensor_number = sensornumber;
+
+ record.body.sensor_capabilities = 0x68; // auto rearm - todo hysteresis
+ record.body.sensor_type = getSensorTypeFromPath(path);
+ std::string type = getSensorTypeStringFromPath(path);
+ auto typeCstr = type.c_str();
+ auto findUnits = sensorUnits.find(typeCstr);
+ if (findUnits != sensorUnits.end())
+ {
+ record.body.sensor_units_2_base =
+ static_cast<uint8_t>(findUnits->second);
+ } // else default 0x0 unspecified
+
+ record.body.event_reading_type = getSensorEventTypeFromPath(path);
+
+ auto sensorObject = sensorMap.find("xyz.openbmc_project.Sensor.Value");
+ if (sensorObject == sensorMap.end())
+ {
+ return GENERAL_ERROR;
+ }
+
+ uint8_t entityId = 0;
+ uint8_t entityInstance = 0x01;
+
+ // follow the association chain to get the parent board's entityid and
+ // entityInstance
+ updateIpmiFromAssociation(path, sensorMap, entityId, entityInstance);
+
+ record.body.entity_id = entityId;
+ record.body.entity_instance = entityInstance;
+
+ auto maxObject = sensorObject->second.find("MaxValue");
+ auto minObject = sensorObject->second.find("MinValue");
+
+ // If min and/or max are left unpopulated,
+ // then default to what a signed byte would be, namely (-128,127) range.
+ auto max = static_cast<double>(std::numeric_limits<int8_t>::max());
+ auto min = static_cast<double>(std::numeric_limits<int8_t>::lowest());
+ if (maxObject != sensorObject->second.end())
+ {
+ max = std::visit(VariantToDoubleVisitor(), maxObject->second);
+ }
+
+ if (minObject != sensorObject->second.end())
+ {
+ min = std::visit(VariantToDoubleVisitor(), minObject->second);
+ }
+
+ int16_t mValue = 0;
+ int8_t rExp = 0;
+ int16_t bValue = 0;
+ int8_t bExp = 0;
+ bool bSigned = false;
+
+ if (!getSensorAttributes(max, min, mValue, rExp, bValue, bExp, bSigned))
+ {
+ return GENERAL_ERROR;
+ }
+
+ // The record.body is a struct SensorDataFullRecordBody
+ // from sensorhandler.hpp in phosphor-ipmi-host.
+ // The meaning of these bits appears to come from
+ // table 43.1 of the IPMI spec.
+ // The above 5 sensor attributes are stuffed in as follows:
+ // Byte 21 = AA000000 = analog interpretation, 10 signed, 00 unsigned
+ // Byte 22-24 are for other purposes
+ // Byte 25 = MMMMMMMM = LSB of M
+ // Byte 26 = MMTTTTTT = MSB of M (signed), and Tolerance
+ // Byte 27 = BBBBBBBB = LSB of B
+ // Byte 28 = BBAAAAAA = MSB of B (signed), and LSB of Accuracy
+ // Byte 29 = AAAAEE00 = MSB of Accuracy, exponent of Accuracy
+ // Byte 30 = RRRRBBBB = rExp (signed), bExp (signed)
+
+ // apply M, B, and exponents, M and B are 10 bit values, exponents are 4
+ record.body.m_lsb = mValue & 0xFF;
+
+ uint8_t mBitSign = (mValue < 0) ? 1 : 0;
+ uint8_t mBitNine = (mValue & 0x0100) >> 8;
+
+ // move the smallest bit of the MSB into place (bit 9)
+ // the MSbs are bits 7:8 in m_msb_and_tolerance
+ record.body.m_msb_and_tolerance = (mBitSign << 7) | (mBitNine << 6);
+
+ record.body.b_lsb = bValue & 0xFF;
+
+ uint8_t bBitSign = (bValue < 0) ? 1 : 0;
+ uint8_t bBitNine = (bValue & 0x0100) >> 8;
+
+ // move the smallest bit of the MSB into place (bit 9)
+ // the MSbs are bits 7:8 in b_msb_and_accuracy_lsb
+ record.body.b_msb_and_accuracy_lsb = (bBitSign << 7) | (bBitNine << 6);
+
+ uint8_t rExpSign = (rExp < 0) ? 1 : 0;
+ uint8_t rExpBits = rExp & 0x07;
+
+ uint8_t bExpSign = (bExp < 0) ? 1 : 0;
+ uint8_t bExpBits = bExp & 0x07;
+
+ // move rExp and bExp into place
+ record.body.r_b_exponents =
+ (rExpSign << 7) | (rExpBits << 4) | (bExpSign << 3) | bExpBits;
+
+ // Set the analog reading byte interpretation accordingly
+ record.body.sensor_units_1 = (bSigned ? 1 : 0) << 7;
+
+ // TODO(): Perhaps care about Tolerance, Accuracy, and so on
+ // These seem redundant, but derivable from the above 5 attributes
+ // Original comment said "todo fill out rest of units"
+
+ // populate sensor name from path
+ std::string name;
+ size_t nameStart = path.rfind("/");
+ if (nameStart != std::string::npos)
+ {
+ name = path.substr(nameStart + 1, std::string::npos - nameStart);
+ }
+
+ std::replace(name.begin(), name.end(), '_', ' ');
+ if (name.size() > FULL_RECORD_ID_STR_MAX_LENGTH)
+ {
+ // try to not truncate by replacing common words
+ constexpr std::array<std::pair<const char*, const char*>, 2>
+ replaceWords = {std::make_pair("Output", "Out"),
+ std::make_pair("Input", "In")};
+ for (const auto& [find, replace] : replaceWords)
+ {
+ boost::replace_all(name, find, replace);
+ }
+
+ name.resize(FULL_RECORD_ID_STR_MAX_LENGTH);
+ }
+ record.body.id_string_info = name.size();
+ std::strncpy(record.body.id_string, name.c_str(),
+ sizeof(record.body.id_string));
+
+ IPMIThresholds thresholdData;
+ try
+ {
+ thresholdData = getIPMIThresholds(sensorMap);
+ }
+ catch (std::exception&)
+ {
+ return GENERAL_ERROR;
+ }
+
+ if (thresholdData.criticalHigh)
+ {
+ record.body.upper_critical_threshold = *thresholdData.criticalHigh;
+ record.body.supported_deassertions[1] |= static_cast<uint8_t>(
+ IPMISensorEventEnableThresholds::criticalThreshold);
+ record.body.supported_deassertions[1] |= static_cast<uint8_t>(
+ IPMISensorEventEnableThresholds::upperCriticalGoingHigh);
+ record.body.supported_assertions[1] |= static_cast<uint8_t>(
+ IPMISensorEventEnableThresholds::upperCriticalGoingHigh);
+ record.body.discrete_reading_setting_mask[0] |=
+ static_cast<uint8_t>(IPMISensorReadingByte3::upperCritical);
+ }
+ if (thresholdData.warningHigh)
+ {
+ record.body.upper_noncritical_threshold =
+ *thresholdData.warningHigh;
+ record.body.supported_deassertions[1] |= static_cast<uint8_t>(
+ IPMISensorEventEnableThresholds::nonCriticalThreshold);
+ record.body.supported_deassertions[0] |= static_cast<uint8_t>(
+ IPMISensorEventEnableThresholds::upperNonCriticalGoingHigh);
+ record.body.supported_assertions[0] |= static_cast<uint8_t>(
+ IPMISensorEventEnableThresholds::upperNonCriticalGoingHigh);
+ record.body.discrete_reading_setting_mask[0] |=
+ static_cast<uint8_t>(IPMISensorReadingByte3::upperNonCritical);
+ }
+ if (thresholdData.criticalLow)
+ {
+ record.body.lower_critical_threshold = *thresholdData.criticalLow;
+ record.body.supported_assertions[1] |= static_cast<uint8_t>(
+ IPMISensorEventEnableThresholds::criticalThreshold);
+ record.body.supported_deassertions[0] |= static_cast<uint8_t>(
+ IPMISensorEventEnableThresholds::lowerCriticalGoingLow);
+ record.body.supported_assertions[0] |= static_cast<uint8_t>(
+ IPMISensorEventEnableThresholds::lowerCriticalGoingLow);
+ record.body.discrete_reading_setting_mask[0] |=
+ static_cast<uint8_t>(IPMISensorReadingByte3::lowerCritical);
+ }
+ if (thresholdData.warningLow)
+ {
+ record.body.lower_noncritical_threshold = *thresholdData.warningLow;
+ record.body.supported_assertions[1] |= static_cast<uint8_t>(
+ IPMISensorEventEnableThresholds::nonCriticalThreshold);
+ record.body.supported_deassertions[0] |= static_cast<uint8_t>(
+ IPMISensorEventEnableThresholds::lowerNonCriticalGoingLow);
+ record.body.supported_assertions[0] |= static_cast<uint8_t>(
+ IPMISensorEventEnableThresholds::lowerNonCriticalGoingLow);
+ record.body.discrete_reading_setting_mask[0] |=
+ static_cast<uint8_t>(IPMISensorReadingByte3::lowerNonCritical);
+ }
+
+ // everything that is readable is setable
+ record.body.discrete_reading_setting_mask[1] =
+ record.body.discrete_reading_setting_mask[0];
+
+ // insert the record into the map
+ std::vector<uint8_t> sdr;
+ sdr.insert(sdr.end(), (uint8_t*)&record,
+ ((uint8_t*)&record) + sizeof(record));
+ sensorDataRecords.insert_or_assign(recordID, sdr);
+ recordID++;
+ }
+
+ size_t nonSensorRecCount = fruCount + ipmi::storage::type12Count;
+ do
+ {
+ size_t fruIndex = recordID - sensorTree.size();
+
+ if (fruIndex >= fruCount)
+ {
+ // handle type 12 hardcoded records
+ size_t type12Index = fruIndex - fruCount;
+ if (type12Index >= ipmi::storage::type12Count)
+ {
+ return GENERAL_ERROR;
+ }
+ std::vector<uint8_t> record =
+ ipmi::storage::getType12SDRs(type12Index, recordID);
+ sensorDataRecords.insert_or_assign(recordID, record);
+ }
+ else
+ {
+ // handle fru records
+ get_sdr::SensorDataFruRecord data;
+ ret = ipmi::storage::getFruSdrs(ctx, fruIndex, data);
+ if (ret != IPMI_CC_OK)
+ {
+ return GENERAL_ERROR;
+ }
+ get_sdr::header::set_record_id(
+ recordID,
+ reinterpret_cast<get_sdr::SensorDataRecordHeader*>(&data));
+
+ std::vector<uint8_t> record;
+ record.insert(record.end(), (uint8_t*)&data,
+ ((uint8_t*)&data) + sizeof(data));
+ sensorDataRecords.insert_or_assign(recordID, record);
+ }
+ recordID++;
+ } while (--nonSensorRecCount);
+ return 0;
+}
+
+/** @brief implements the get SDR Info command
+ * @param count - Operation
+ *
+ * @returns IPMI completion code plus response data
+ * - sdrCount - sensor/SDR count
+ * - lunsAndDynamicPopulation - static/Dynamic sensor population flag
+ */
+static ipmi::RspType<uint8_t, // respcount
+ uint8_t, // dynamic population flags
+ uint32_t // last time a sensor was added
+ >
+ ipmiSensorGetDeviceSdrInfo(ipmi::Context::ptr ctx,
+ std::optional<uint8_t> count)
+{
+ auto& sensorTree = getSensorTree();
+ uint8_t sdrCount = 0;
+ // Sensors are dynamically allocated, and there is at least one LUN
+ uint8_t lunsAndDynamicPopulation = 0x80;
+ constexpr uint8_t getSdrCount = 0x01;
+ constexpr uint8_t getSensorCount = 0x00;
+
+ if (!getSensorSubtree(sensorTree) || sensorTree.empty())
+ {
+ return ipmi::responseResponseError();
+ }
+
+ if (sensorDataRecords.empty() && getSensorDataRecords(ctx))
+ {
+ return ipmi::responseResponseError();
+ }
+
+ uint16_t numSensors = sensorTree.size();
+ if (count.value_or(0) == getSdrCount)
+ {
+ // Count the number of Type 1 SDR entries assigned to the LUN
+ for (auto sdr : sensorDataRecords)
+ {
+ get_sdr::SensorDataRecordHeader* hdr =
+ reinterpret_cast<get_sdr::SensorDataRecordHeader*>(
+ sdr.second.data());
+ if (hdr && hdr->record_type == get_sdr::SENSOR_DATA_FULL_RECORD)
+ {
+ get_sdr::SensorDataFullRecord* record =
+ reinterpret_cast<get_sdr::SensorDataFullRecord*>(
+ sdr.second.data());
+ if (ctx->lun == record->key.owner_lun)
+ {
+ sdrCount++;
+ }
+ }
+ }
+ }
+ else if (count.value_or(0) == getSensorCount)
+ {
+ // Return the number of sensors attached to the LUN
+ if ((ctx->lun == 0) && (numSensors > 0))
+ {
+ sdrCount =
+ (numSensors > maxSensorsPerLUN) ? maxSensorsPerLUN : numSensors;
+ }
+ else if ((ctx->lun == 1) && (numSensors > maxSensorsPerLUN))
+ {
+ sdrCount = (numSensors > (2 * maxSensorsPerLUN))
+ ? maxSensorsPerLUN
+ : (numSensors - maxSensorsPerLUN) & maxSensorsPerLUN;
+ }
+ else if (ctx->lun == 3)
+ {
+ if (numSensors <= maxIPMISensors)
+ {
+ sdrCount =
+ (numSensors - (2 * maxSensorsPerLUN)) & maxSensorsPerLUN;
+ }
+ else
+ {
+ // error
+ throw std::out_of_range(
+ "Maximum number of IPMI sensors exceeded.");
+ }
+ }
+ }
+ else
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ // Get Sensor count. This returns the number of sensors
+ if (numSensors > 0)
+ {
+ lunsAndDynamicPopulation |= 1;
+ }
+ if (numSensors > maxSensorsPerLUN)
+ {
+ lunsAndDynamicPopulation |= 2;
+ }
+ if (numSensors >= (maxSensorsPerLUN * 2))
+ {
+ lunsAndDynamicPopulation |= 8;
+ }
+ if (numSensors > maxIPMISensors)
+ {
+ // error
+ throw std::out_of_range("Maximum number of IPMI sensors exceeded.");
+ }
+
+ return ipmi::responseSuccess(sdrCount, lunsAndDynamicPopulation,
+ sdrLastAdd);
+}
+
+/* end sensor commands */
+
+/* storage commands */
+
+ipmi::RspType<uint8_t, // sdr version
+ uint16_t, // record count
+ uint16_t, // free space
+ uint32_t, // most recent addition
+ uint32_t, // most recent erase
+ uint8_t // operationSupport
+ >
+ ipmiStorageGetSDRRepositoryInfo(ipmi::Context::ptr ctx)
+{
+ auto& sensorTree = getSensorTree();
+ constexpr const uint16_t unspecifiedFreeSpace = 0xFFFF;
+ if (sensorTree.empty() && !getSensorSubtree(sensorTree))
+ {
+ return ipmi::responseResponseError();
+ }
+
+ size_t fruCount = 0;
+ ipmi::Cc ret = ipmi::storage::getFruSdrCount(ctx, fruCount);
+ if (ret != ipmi::ccSuccess)
+ {
+ return ipmi::response(ret);
+ }
+
+ uint16_t recordCount =
+ sensorTree.size() + fruCount + ipmi::storage::type12Count;
+
+ uint8_t operationSupport = static_cast<uint8_t>(
+ SdrRepositoryInfoOps::overflow); // write not supported
+
+ operationSupport |=
+ static_cast<uint8_t>(SdrRepositoryInfoOps::allocCommandSupported);
+ operationSupport |= static_cast<uint8_t>(
+ SdrRepositoryInfoOps::reserveSDRRepositoryCommandSupported);
+ return ipmi::responseSuccess(ipmiSdrVersion, recordCount,
+ unspecifiedFreeSpace, sdrLastAdd,
+ sdrLastRemove, operationSupport);
+}
+
+/** @brief implements the get SDR allocation info command
+ *
+ * @returns IPMI completion code plus response data
+ * - allocUnits - Number of possible allocation units
+ * - allocUnitSize - Allocation unit size in bytes.
+ * - allocUnitFree - Number of free allocation units
+ * - allocUnitLargestFree - Largest free block in allocation units
+ * - maxRecordSize - Maximum record size in allocation units.
+ */
+ipmi::RspType<uint16_t, // allocUnits
+ uint16_t, // allocUnitSize
+ uint16_t, // allocUnitFree
+ uint16_t, // allocUnitLargestFree
+ uint8_t // maxRecordSize
+ >
+ ipmiStorageGetSDRAllocationInfo()
+{
+ // 0000h unspecified number of alloc units
+ constexpr uint16_t allocUnits = 0;
+
+ constexpr uint16_t allocUnitFree = 0;
+ constexpr uint16_t allocUnitLargestFree = 0;
+ // only allow one block at a time
+ constexpr uint8_t maxRecordSize = 1;
+
+ return ipmi::responseSuccess(allocUnits, maxSDRTotalSize, allocUnitFree,
+ allocUnitLargestFree, maxRecordSize);
+}
+
+/** @brief implements the reserve SDR command
+ * @returns IPMI completion code plus response data
+ * - sdrReservationID
+ */
+ipmi::RspType<uint16_t> ipmiStorageReserveSDR()
+{
+ sdrReservationID++;
+ if (sdrReservationID == 0)
+ {
+ sdrReservationID++;
+ }
+
+ return ipmi::responseSuccess(sdrReservationID);
+}
+
+ipmi::RspType<uint16_t, // next record ID
+ std::vector<uint8_t> // payload
+ >
+ ipmiStorageGetSDR(ipmi::Context::ptr ctx, uint16_t reservationID,
+ uint16_t recordID, uint8_t offset, uint8_t bytesToRead)
+{
+ // reservation required for partial reads with non zero offset into
+ // record
+ if ((sdrReservationID == 0 || reservationID != sdrReservationID) && offset)
+ {
+ return ipmi::responseInvalidReservationId();
+ }
+
+ if (sensorDataRecords.empty() && getSensorDataRecords(ctx))
+ {
+ return ipmi::responseResponseError();
+ }
+
+ auto& sensorTree = getSensorTree();
+ if (sensorTree.empty() && !getSensorSubtree(sensorTree))
+ {
+ return ipmi::responseResponseError();
+ }
+
+ size_t fruCount = 0;
+ ipmi::Cc ret = ipmi::storage::getFruSdrCount(ctx, fruCount);
+ if (ret != ipmi::ccSuccess)
+ {
+ return ipmi::response(ret);
+ }
+
+ size_t lastRecord =
+ sensorTree.size() + fruCount + ipmi::storage::type12Count - 1;
+ if (recordID == lastRecordIndex)
+ {
+ recordID = lastRecord;
+ }
+ if (recordID > lastRecord)
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ get_sdr::SensorDataRecordHeader* hdr =
+ reinterpret_cast<get_sdr::SensorDataRecordHeader*>(
+ sensorDataRecords[recordID].data());
+
+ if (!hdr)
+ {
+ phosphor::logging::log<phosphor::logging::level::ERR>(
+ "Error: record header is null");
+ std::vector<uint8_t> emptyData;
+ uint16_t nextRecordId = lastRecord > recordID ? recordID + 1 : 0XFFFF;
+ return ipmi::responseSuccess(nextRecordId, emptyData);
+ }
+
+ size_t sdrLength =
+ sizeof(get_sdr::SensorDataRecordHeader) + hdr->record_length;
+ if (sdrLength < (offset + bytesToRead))
+ {
+ bytesToRead = sdrLength - offset;
+ }
+
+ uint8_t* respStart = reinterpret_cast<uint8_t*>(hdr) + offset;
+ if (!respStart)
+ {
+ phosphor::logging::log<phosphor::logging::level::ERR>(
+ "Error: record is null");
+ std::vector<uint8_t> emptyData;
+ uint16_t nextRecordId = lastRecord > recordID ? recordID + 1 : 0XFFFF;
+ return ipmi::responseSuccess(nextRecordId, emptyData);
+ }
+
+ std::vector<uint8_t> recordData(respStart, respStart + bytesToRead);
+ uint16_t nextRecordId = lastRecord > recordID ? recordID + 1 : 0XFFFF;
+ return ipmi::responseSuccess(nextRecordId, recordData);
+}
+/* end storage commands */
+
+void registerSensorFunctions()
+{
+ // <Platform Event>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
+ ipmi::sensor_event::cmdPlatformEvent,
+ ipmi::Privilege::Operator, ipmiSenPlatformEvent);
+
+ // <Get Sensor Reading>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
+ ipmi::sensor_event::cmdGetSensorReading,
+ ipmi::Privilege::User, ipmiSenGetSensorReading);
+
+ // <Get Sensor Threshold>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
+ ipmi::sensor_event::cmdGetSensorThreshold,
+ ipmi::Privilege::User, ipmiSenGetSensorThresholds);
+
+ // <Set Sensor Threshold>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
+ ipmi::sensor_event::cmdSetSensorThreshold,
+ ipmi::Privilege::Operator,
+ ipmiSenSetSensorThresholds);
+
+ // <Get Sensor Event Enable>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
+ ipmi::sensor_event::cmdGetSensorEventEnable,
+ ipmi::Privilege::User, ipmiSenGetSensorEventEnable);
+
+ // <Get Sensor Event Status>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
+ ipmi::sensor_event::cmdGetSensorEventStatus,
+ ipmi::Privilege::User, ipmiSenGetSensorEventStatus);
+
+ // register all storage commands for both Sensor and Storage command
+ // versions
+
+ // <Get SDR Repository Info>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+ ipmi::storage::cmdGetSdrRepositoryInfo,
+ ipmi::Privilege::User,
+ ipmiStorageGetSDRRepositoryInfo);
+
+ // <Get Device SDR Info>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
+ ipmi::sensor_event::cmdGetDeviceSdrInfo,
+ ipmi::Privilege::User, ipmiSensorGetDeviceSdrInfo);
+
+ // <Get SDR Allocation Info>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+ ipmi::storage::cmdGetSdrRepositoryAllocInfo,
+ ipmi::Privilege::User,
+ ipmiStorageGetSDRAllocationInfo);
+
+ // <Reserve SDR Repo>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
+ ipmi::sensor_event::cmdReserveDeviceSdrRepository,
+ ipmi::Privilege::User, ipmiStorageReserveSDR);
+
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+ ipmi::storage::cmdReserveSdrRepository,
+ ipmi::Privilege::User, ipmiStorageReserveSDR);
+
+ // <Get Sdr>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
+ ipmi::sensor_event::cmdGetDeviceSdr,
+ ipmi::Privilege::User, ipmiStorageGetSDR);
+
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+ ipmi::storage::cmdGetSdr, ipmi::Privilege::User,
+ ipmiStorageGetSDR);
+}
+} // namespace ipmi
diff --git a/dbus-sdr/sensorutils.cpp b/dbus-sdr/sensorutils.cpp
new file mode 100644
index 0000000..d31dbed
--- /dev/null
+++ b/dbus-sdr/sensorutils.cpp
@@ -0,0 +1,325 @@
+/*
+// Copyright (c) 2017 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 "dbus-sdr/sensorutils.hpp"
+
+#include <algorithm>
+#include <cmath>
+#include <iostream>
+
+namespace ipmi
+{
+
+// Helper function to avoid repeated complicated expression
+static bool baseInRange(double base)
+{
+ auto min10 = static_cast<double>(minInt10);
+ auto max10 = static_cast<double>(maxInt10);
+
+ return ((base >= min10) && (base <= max10));
+}
+
+// Helper function for internal use by getSensorAttributes()
+// Ensures floating-point "base" is within bounds,
+// and adjusts integer exponent "expShift" accordingly.
+// To minimize data loss when later truncating to integer,
+// the floating-point "base" will be as large as possible,
+// but still within the bounds (minInt10,maxInt10).
+// The bounds of "expShift" are (minInt4,maxInt4).
+// Consider this equation: n = base * (10.0 ** expShift)
+// This function will try to maximize "base",
+// adjusting "expShift" to keep the value "n" unchanged,
+// while keeping base and expShift within bounds.
+// Returns true if successful, modifies values in-place
+static bool scaleFloatExp(double& base, int8_t& expShift)
+{
+ // Comparing with zero should be OK, zero is special in floating-point
+ // If base is exactly zero, no adjustment of the exponent is necessary
+ if (base == 0.0)
+ {
+ return true;
+ }
+
+ // As long as base value is within allowed range, expand precision
+ // This will help to avoid loss when later rounding to integer
+ while (baseInRange(base))
+ {
+ if (expShift <= minInt4)
+ {
+ // Already at the minimum expShift, can not decrement it more
+ break;
+ }
+
+ // Multiply by 10, but shift decimal point to the left, no net change
+ base *= 10.0;
+ --expShift;
+ }
+
+ // As long as base value is *not* within range, shrink precision
+ // This will pull base value closer to zero, thus within range
+ while (!(baseInRange(base)))
+ {
+ if (expShift >= maxInt4)
+ {
+ // Already at the maximum expShift, can not increment it more
+ break;
+ }
+
+ // Divide by 10, but shift decimal point to the right, no net change
+ base /= 10.0;
+ ++expShift;
+ }
+
+ // If the above loop was not able to pull it back within range,
+ // the base value is beyond what expShift can represent, return false.
+ return baseInRange(base);
+}
+
+// Helper function for internal use by getSensorAttributes()
+// Ensures integer "ibase" is no larger than necessary,
+// by normalizing it so that the decimal point shift is in the exponent,
+// whenever possible.
+// This provides more consistent results,
+// as many equivalent solutions are collapsed into one consistent solution.
+// If integer "ibase" is a clean multiple of 10,
+// divide it by 10 (this is lossless), so it is closer to zero.
+// Also modify floating-point "dbase" at the same time,
+// as both integer and floating-point base share the same expShift.
+// Example: (ibase=300, expShift=2) becomes (ibase=3, expShift=4)
+// because the underlying value is the same: 200*(10**2) == 2*(10**4)
+// Always successful, modifies values in-place
+static void normalizeIntExp(int16_t& ibase, int8_t& expShift, double& dbase)
+{
+ for (;;)
+ {
+ // If zero, already normalized, ensure exponent also zero
+ if (ibase == 0)
+ {
+ expShift = 0;
+ break;
+ }
+
+ // If not cleanly divisible by 10, already normalized
+ if ((ibase % 10) != 0)
+ {
+ break;
+ }
+
+ // If exponent already at max, already normalized
+ if (expShift >= maxInt4)
+ {
+ break;
+ }
+
+ // Bring values closer to zero, correspondingly shift exponent,
+ // without changing the underlying number that this all represents,
+ // similar to what is done by scaleFloatExp().
+ // The floating-point base must be kept in sync with the integer base,
+ // as both floating-point and integer share the same exponent.
+ ibase /= 10;
+ dbase /= 10.0;
+ ++expShift;
+ }
+}
+
+// The IPMI equation:
+// y = (Mx + (B * 10^(bExp))) * 10^(rExp)
+// Section 36.3 of this document:
+// https://www.intel.com/content/dam/www/public/us/en/documents/product-briefs/ipmi-second-gen-interface-spec-v2-rev1-1.pdf
+//
+// The goal is to exactly match the math done by the ipmitool command,
+// at the other side of the interface:
+// https://github.com/ipmitool/ipmitool/blob/42a023ff0726c80e8cc7d30315b987fe568a981d/lib/ipmi_sdr.c#L360
+//
+// To use with Wolfram Alpha, make all variables single letters
+// bExp becomes E, rExp becomes R
+// https://www.wolframalpha.com/input/?i=y%3D%28%28M*x%29%2B%28B*%2810%5EE%29%29%29*%2810%5ER%29
+bool getSensorAttributes(const double max, const double min, int16_t& mValue,
+ int8_t& rExp, int16_t& bValue, int8_t& bExp,
+ bool& bSigned)
+{
+ if (!(std::isfinite(min)))
+ {
+ std::cerr << "getSensorAttributes: Min value is unusable\n";
+ return false;
+ }
+ if (!(std::isfinite(max)))
+ {
+ std::cerr << "getSensorAttributes: Max value is unusable\n";
+ return false;
+ }
+
+ // Because NAN has already been tested for, this comparison works
+ if (max <= min)
+ {
+ std::cerr << "getSensorAttributes: Max must be greater than min\n";
+ return false;
+ }
+
+ // Given min and max, we must solve for M, B, bExp, rExp
+ // y comes in from D-Bus (the actual sensor reading)
+ // x is calculated from y by scaleIPMIValueFromDouble() below
+ // If y is min, x should equal = 0 (or -128 if signed)
+ // If y is max, x should equal 255 (or 127 if signed)
+ double fullRange = max - min;
+ double lowestX;
+
+ rExp = 0;
+ bExp = 0;
+
+ // TODO(): The IPMI document is ambiguous, as to whether
+ // the resulting byte should be signed or unsigned,
+ // essentially leaving it up to the caller.
+ // The document just refers to it as "raw reading",
+ // or "byte of reading", without giving further details.
+ // Previous code set it signed if min was less than zero,
+ // so I'm sticking with that, until I learn otherwise.
+ if (min < 0.0)
+ {
+ // TODO(): It would be worth experimenting with the range (-127,127),
+ // instead of the range (-128,127), because this
+ // would give good symmetry around zero, and make results look better.
+ // Divide by 254 instead of 255, and change -128 to -127 elsewhere.
+ bSigned = true;
+ lowestX = -128.0;
+ }
+ else
+ {
+ bSigned = false;
+ lowestX = 0.0;
+ }
+
+ // Step 1: Set y to (max - min), set x to 255, set B to 0, solve for M
+ // This works, regardless of signed or unsigned,
+ // because total range is the same.
+ double dM = fullRange / 255.0;
+
+ // Step 2: Constrain M, and set rExp accordingly
+ if (!(scaleFloatExp(dM, rExp)))
+ {
+ std::cerr << "getSensorAttributes: Multiplier range exceeds scale (M="
+ << dM << ", rExp=" << (int)rExp << ")\n";
+ return false;
+ }
+
+ mValue = static_cast<int16_t>(std::round(dM));
+
+ normalizeIntExp(mValue, rExp, dM);
+
+ // The multiplier can not be zero, for obvious reasons
+ if (mValue == 0)
+ {
+ std::cerr << "getSensorAttributes: Multiplier range below scale\n";
+ return false;
+ }
+
+ // Step 3: set y to min, set x to min, keep M and rExp, solve for B
+ // If negative, x will be -128 (the most negative possible byte), not 0
+
+ // Solve the IPMI equation for B, instead of y
+ // https://www.wolframalpha.com/input/?i=solve+y%3D%28%28M*x%29%2B%28B*%2810%5EE%29%29%29*%2810%5ER%29+for+B
+ // B = 10^(-rExp - bExp) (y - M 10^rExp x)
+ // TODO(): Compare with this alternative solution from SageMathCell
+ // https://sagecell.sagemath.org/?z=eJyrtC1LLNJQr1TX5KqAMCuATF8I0xfIdIIwnYDMIteKAggPxAIKJMEFkiACxfk5Zaka0ZUKtrYKGhq-CloKFZoK2goaTkCWhqGBgpaWAkilpqYmQgBklmasjoKTJgDAECTH&lang=sage&interacts=eJyLjgUAARUAuQ==
+ double dB = std::pow(10.0, ((-rExp) - bExp)) *
+ (min - ((dM * std::pow(10.0, rExp) * lowestX)));
+
+ // Step 4: Constrain B, and set bExp accordingly
+ if (!(scaleFloatExp(dB, bExp)))
+ {
+ std::cerr << "getSensorAttributes: Offset (B=" << dB
+ << ", bExp=" << (int)bExp
+ << ") exceeds multiplier scale (M=" << dM
+ << ", rExp=" << (int)rExp << ")\n";
+ return false;
+ }
+
+ bValue = static_cast<int16_t>(std::round(dB));
+
+ normalizeIntExp(bValue, bExp, dB);
+
+ // Unlike the multiplier, it is perfectly OK for bValue to be zero
+ return true;
+}
+
+uint8_t scaleIPMIValueFromDouble(const double value, const int16_t mValue,
+ const int8_t rExp, const int16_t bValue,
+ const int8_t bExp, const bool bSigned)
+{
+ // Avoid division by zero below
+ if (mValue == 0)
+ {
+ throw std::out_of_range("Scaling multiplier is uninitialized");
+ }
+
+ auto dM = static_cast<double>(mValue);
+ auto dB = static_cast<double>(bValue);
+
+ // Solve the IPMI equation for x, instead of y
+ // https://www.wolframalpha.com/input/?i=solve+y%3D%28%28M*x%29%2B%28B*%2810%5EE%29%29%29*%2810%5ER%29+for+x
+ // x = (10^(-rExp) (y - B 10^(rExp + bExp)))/M and M 10^rExp!=0
+ // TODO(): Compare with this alternative solution from SageMathCell
+ // https://sagecell.sagemath.org/?z=eJyrtC1LLNJQr1TX5KqAMCuATF8I0xfIdIIwnYDMIteKAggPxAIKJMEFkiACxfk5Zaka0ZUKtrYKGhq-CloKFZoK2goaTkCWhqGBgpaWAkilpqYmQgBklmasDlAlAMB8JP0=&lang=sage&interacts=eJyLjgUAARUAuQ==
+ double dX =
+ (std::pow(10.0, -rExp) * (value - (dB * std::pow(10.0, rExp + bExp)))) /
+ dM;
+
+ auto scaledValue = static_cast<int32_t>(std::round(dX));
+
+ int32_t minClamp;
+ int32_t maxClamp;
+
+ // Because of rounding and integer truncation of scaling factors,
+ // sometimes the resulting byte is slightly out of range.
+ // Still allow this, but clamp the values to range.
+ if (bSigned)
+ {
+ minClamp = std::numeric_limits<int8_t>::lowest();
+ maxClamp = std::numeric_limits<int8_t>::max();
+ }
+ else
+ {
+ minClamp = std::numeric_limits<uint8_t>::lowest();
+ maxClamp = std::numeric_limits<uint8_t>::max();
+ }
+
+ auto clampedValue = std::clamp(scaledValue, minClamp, maxClamp);
+
+ // This works for both signed and unsigned,
+ // because it is the same underlying byte storage.
+ return static_cast<uint8_t>(clampedValue);
+}
+
+uint8_t getScaledIPMIValue(const double value, const double max,
+ const double min)
+{
+ int16_t mValue = 0;
+ int8_t rExp = 0;
+ int16_t bValue = 0;
+ int8_t bExp = 0;
+ bool bSigned = false;
+
+ bool result =
+ getSensorAttributes(max, min, mValue, rExp, bValue, bExp, bSigned);
+ if (!result)
+ {
+ throw std::runtime_error("Illegal sensor attributes");
+ }
+
+ return scaleIPMIValueFromDouble(value, mValue, rExp, bValue, bExp, bSigned);
+}
+
+} // namespace ipmi
diff --git a/dbus-sdr/storagecommands.cpp b/dbus-sdr/storagecommands.cpp
new file mode 100644
index 0000000..5926615
--- /dev/null
+++ b/dbus-sdr/storagecommands.cpp
@@ -0,0 +1,1293 @@
+/*
+// Copyright (c) 2017-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 "dbus-sdr/storagecommands.hpp"
+
+#include "dbus-sdr/sdrutils.hpp"
+#include "selutility.hpp"
+
+#include <boost/algorithm/string.hpp>
+#include <boost/container/flat_map.hpp>
+#include <boost/process.hpp>
+#include <filesystem>
+#include <functional>
+#include <iostream>
+#include <ipmid/api.hpp>
+#include <ipmid/message.hpp>
+#include <ipmid/types.hpp>
+#include <phosphor-logging/log.hpp>
+#include <sdbusplus/message/types.hpp>
+#include <sdbusplus/timer.hpp>
+#include <stdexcept>
+#include <string_view>
+
+static constexpr bool DEBUG = false;
+
+namespace dynamic_sensors::ipmi::sel
+{
+static const std::filesystem::path selLogDir = "/var/log";
+static const std::string selLogFilename = "ipmi_sel";
+
+static int getFileTimestamp(const std::filesystem::path& file)
+{
+ struct stat st;
+
+ if (stat(file.c_str(), &st) >= 0)
+ {
+ return st.st_mtime;
+ }
+ return ::ipmi::sel::invalidTimeStamp;
+}
+
+namespace erase_time
+{
+static constexpr const char* selEraseTimestamp = "/var/lib/ipmi/sel_erase_time";
+
+void save()
+{
+ // open the file, creating it if necessary
+ int fd = open(selEraseTimestamp, O_WRONLY | O_CREAT | O_CLOEXEC, 0644);
+ if (fd < 0)
+ {
+ std::cerr << "Failed to open file\n";
+ return;
+ }
+
+ // update the file timestamp to the current time
+ if (futimens(fd, NULL) < 0)
+ {
+ std::cerr << "Failed to update timestamp: "
+ << std::string(strerror(errno));
+ }
+ close(fd);
+}
+
+int get()
+{
+ return getFileTimestamp(selEraseTimestamp);
+}
+} // namespace erase_time
+} // namespace dynamic_sensors::ipmi::sel
+
+namespace ipmi
+{
+
+namespace storage
+{
+
+constexpr static const size_t maxMessageSize = 64;
+constexpr static const size_t maxFruSdrNameSize = 16;
+using ObjectType =
+ boost::container::flat_map<std::string,
+ boost::container::flat_map<std::string, Value>>;
+using ManagedObjectType =
+ boost::container::flat_map<sdbusplus::message::object_path, ObjectType>;
+using ManagedEntry = std::pair<sdbusplus::message::object_path, ObjectType>;
+
+constexpr static const char* fruDeviceServiceName =
+ "xyz.openbmc_project.FruDevice";
+constexpr static const char* entityManagerServiceName =
+ "xyz.openbmc_project.EntityManager";
+constexpr static const size_t writeTimeoutSeconds = 10;
+constexpr static const char* chassisTypeRackMount = "23";
+
+// event direction is bit[7] of eventType where 1b = Deassertion event
+constexpr static const uint8_t deassertionEvent = 0x80;
+
+static std::vector<uint8_t> fruCache;
+static uint8_t cacheBus = 0xFF;
+static uint8_t cacheAddr = 0XFF;
+static uint8_t lastDevId = 0xFF;
+
+static uint8_t writeBus = 0xFF;
+static uint8_t writeAddr = 0XFF;
+
+std::unique_ptr<phosphor::Timer> writeTimer = nullptr;
+static std::vector<sdbusplus::bus::match::match> fruMatches;
+
+ManagedObjectType frus;
+
+// we unfortunately have to build a map of hashes in case there is a
+// collision to verify our dev-id
+boost::container::flat_map<uint8_t, std::pair<uint8_t, uint8_t>> deviceHashes;
+
+void registerStorageFunctions() __attribute__((constructor));
+
+bool writeFru()
+{
+ if (writeBus == 0xFF && writeAddr == 0xFF)
+ {
+ return true;
+ }
+ std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus();
+ sdbusplus::message::message writeFru = dbus->new_method_call(
+ fruDeviceServiceName, "/xyz/openbmc_project/FruDevice",
+ "xyz.openbmc_project.FruDeviceManager", "WriteFru");
+ writeFru.append(writeBus, writeAddr, fruCache);
+ try
+ {
+ sdbusplus::message::message writeFruResp = dbus->call(writeFru);
+ }
+ catch (sdbusplus::exception_t&)
+ {
+ // todo: log sel?
+ phosphor::logging::log<phosphor::logging::level::ERR>(
+ "error writing fru");
+ return false;
+ }
+ writeBus = 0xFF;
+ writeAddr = 0xFF;
+ return true;
+}
+
+void createTimers()
+{
+ writeTimer = std::make_unique<phosphor::Timer>(writeFru);
+}
+
+void recalculateHashes()
+{
+
+ deviceHashes.clear();
+ // hash the object paths to create unique device id's. increment on
+ // collision
+ std::hash<std::string> hasher;
+ for (const auto& fru : frus)
+ {
+ auto fruIface = fru.second.find("xyz.openbmc_project.FruDevice");
+ if (fruIface == fru.second.end())
+ {
+ continue;
+ }
+
+ auto busFind = fruIface->second.find("BUS");
+ auto addrFind = fruIface->second.find("ADDRESS");
+ if (busFind == fruIface->second.end() ||
+ addrFind == fruIface->second.end())
+ {
+ phosphor::logging::log<phosphor::logging::level::INFO>(
+ "fru device missing Bus or Address",
+ phosphor::logging::entry("FRU=%s", fru.first.str.c_str()));
+ continue;
+ }
+
+ uint8_t fruBus = std::get<uint32_t>(busFind->second);
+ uint8_t fruAddr = std::get<uint32_t>(addrFind->second);
+ auto chassisFind = fruIface->second.find("CHASSIS_TYPE");
+ std::string chassisType;
+ if (chassisFind != fruIface->second.end())
+ {
+ chassisType = std::get<std::string>(chassisFind->second);
+ }
+
+ uint8_t fruHash = 0;
+ if (chassisType.compare(chassisTypeRackMount) != 0)
+ {
+ fruHash = hasher(fru.first.str);
+ // can't be 0xFF based on spec, and 0 is reserved for baseboard
+ if (fruHash == 0 || fruHash == 0xFF)
+ {
+ fruHash = 1;
+ }
+ }
+ std::pair<uint8_t, uint8_t> newDev(fruBus, fruAddr);
+
+ bool emplacePassed = false;
+ while (!emplacePassed)
+ {
+ auto resp = deviceHashes.emplace(fruHash, newDev);
+ emplacePassed = resp.second;
+ if (!emplacePassed)
+ {
+ fruHash++;
+ // can't be 0xFF based on spec, and 0 is reserved for
+ // baseboard
+ if (fruHash == 0XFF)
+ {
+ fruHash = 0x1;
+ }
+ }
+ }
+ }
+}
+
+void replaceCacheFru(const std::shared_ptr<sdbusplus::asio::connection>& bus,
+ boost::asio::yield_context& yield,
+ const std::optional<std::string>& path = std::nullopt)
+{
+ boost::system::error_code ec;
+
+ frus = bus->yield_method_call<ManagedObjectType>(
+ yield, ec, fruDeviceServiceName, "/",
+ "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
+ if (ec)
+ {
+ phosphor::logging::log<phosphor::logging::level::ERR>(
+ "GetMangagedObjects for replaceCacheFru failed",
+ phosphor::logging::entry("ERROR=%s", ec.message().c_str()));
+
+ return;
+ }
+ recalculateHashes();
+}
+
+ipmi::Cc getFru(ipmi::Context::ptr ctx, uint8_t devId)
+{
+ if (lastDevId == devId && devId != 0xFF)
+ {
+ return ipmi::ccSuccess;
+ }
+
+ // Set devId to 1 if devId is 0.
+ // 0 is reserved for baseboard and set to 1 in recalculateHashes().
+ if (!devId)
+ devId = 1;
+
+ auto deviceFind = deviceHashes.find(devId);
+ if (deviceFind == deviceHashes.end())
+ {
+ return IPMI_CC_SENSOR_INVALID;
+ }
+
+ fruCache.clear();
+
+ cacheBus = deviceFind->second.first;
+ cacheAddr = deviceFind->second.second;
+
+ boost::system::error_code ec;
+
+ fruCache = ctx->bus->yield_method_call<std::vector<uint8_t>>(
+ ctx->yield, ec, fruDeviceServiceName, "/xyz/openbmc_project/FruDevice",
+ "xyz.openbmc_project.FruDeviceManager", "GetRawFru", cacheBus,
+ cacheAddr);
+ if (ec)
+ {
+ phosphor::logging::log<phosphor::logging::level::ERR>(
+ "Couldn't get raw fru",
+ phosphor::logging::entry("ERROR=%s", ec.message().c_str()));
+
+ cacheBus = 0xFF;
+ cacheAddr = 0xFF;
+ return ipmi::ccResponseError;
+ }
+
+ lastDevId = devId;
+ return ipmi::ccSuccess;
+}
+
+void writeFruIfRunning()
+{
+ if (!writeTimer->isRunning())
+ {
+ return;
+ }
+ writeTimer->stop();
+ writeFru();
+}
+
+void startMatch(void)
+{
+ if (fruMatches.size())
+ {
+ return;
+ }
+
+ fruMatches.reserve(2);
+
+ auto bus = getSdBus();
+ fruMatches.emplace_back(*bus,
+ "type='signal',arg0path='/xyz/openbmc_project/"
+ "FruDevice/',member='InterfacesAdded'",
+ [](sdbusplus::message::message& message) {
+ sdbusplus::message::object_path path;
+ ObjectType object;
+ try
+ {
+ message.read(path, object);
+ }
+ catch (sdbusplus::exception_t&)
+ {
+ return;
+ }
+ auto findType = object.find(
+ "xyz.openbmc_project.FruDevice");
+ if (findType == object.end())
+ {
+ return;
+ }
+ writeFruIfRunning();
+ frus[path] = object;
+ recalculateHashes();
+ lastDevId = 0xFF;
+ });
+
+ fruMatches.emplace_back(*bus,
+ "type='signal',arg0path='/xyz/openbmc_project/"
+ "FruDevice/',member='InterfacesRemoved'",
+ [](sdbusplus::message::message& message) {
+ sdbusplus::message::object_path path;
+ std::set<std::string> interfaces;
+ try
+ {
+ message.read(path, interfaces);
+ }
+ catch (sdbusplus::exception_t&)
+ {
+ return;
+ }
+ auto findType = interfaces.find(
+ "xyz.openbmc_project.FruDevice");
+ if (findType == interfaces.end())
+ {
+ return;
+ }
+ writeFruIfRunning();
+ frus.erase(path);
+ recalculateHashes();
+ lastDevId = 0xFF;
+ });
+
+ // call once to populate
+ boost::asio::spawn(*getIoContext(), [](boost::asio::yield_context yield) {
+ replaceCacheFru(getSdBus(), yield);
+ });
+}
+
+/** @brief implements the read FRU data command
+ * @param fruDeviceId - FRU Device ID
+ * @param fruInventoryOffset - FRU Inventory Offset to write
+ * @param countToRead - Count to read
+ *
+ * @returns ipmi completion code plus response data
+ * - countWritten - Count written
+ */
+ipmi::RspType<uint8_t, // Count
+ std::vector<uint8_t> // Requested data
+ >
+ ipmiStorageReadFruData(ipmi::Context::ptr ctx, uint8_t fruDeviceId,
+ uint16_t fruInventoryOffset, uint8_t countToRead)
+{
+ if (fruDeviceId == 0xFF)
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ ipmi::Cc status = getFru(ctx, fruDeviceId);
+
+ if (status != ipmi::ccSuccess)
+ {
+ return ipmi::response(status);
+ }
+
+ size_t fromFruByteLen = 0;
+ if (countToRead + fruInventoryOffset < fruCache.size())
+ {
+ fromFruByteLen = countToRead;
+ }
+ else if (fruCache.size() > fruInventoryOffset)
+ {
+ fromFruByteLen = fruCache.size() - fruInventoryOffset;
+ }
+ else
+ {
+ return ipmi::responseReqDataLenExceeded();
+ }
+
+ std::vector<uint8_t> requestedData;
+
+ requestedData.insert(
+ requestedData.begin(), fruCache.begin() + fruInventoryOffset,
+ fruCache.begin() + fruInventoryOffset + fromFruByteLen);
+
+ return ipmi::responseSuccess(static_cast<uint8_t>(requestedData.size()),
+ requestedData);
+}
+
+/** @brief implements the write FRU data command
+ * @param fruDeviceId - FRU Device ID
+ * @param fruInventoryOffset - FRU Inventory Offset to write
+ * @param dataToWrite - Data to write
+ *
+ * @returns ipmi completion code plus response data
+ * - countWritten - Count written
+ */
+ipmi::RspType<uint8_t>
+ ipmiStorageWriteFruData(ipmi::Context::ptr ctx, uint8_t fruDeviceId,
+ uint16_t fruInventoryOffset,
+ std::vector<uint8_t>& dataToWrite)
+{
+ if (fruDeviceId == 0xFF)
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ size_t writeLen = dataToWrite.size();
+
+ ipmi::Cc status = getFru(ctx, fruDeviceId);
+ if (status != ipmi::ccSuccess)
+ {
+ return ipmi::response(status);
+ }
+ size_t lastWriteAddr = fruInventoryOffset + writeLen;
+ if (fruCache.size() < lastWriteAddr)
+ {
+ fruCache.resize(fruInventoryOffset + writeLen);
+ }
+
+ std::copy(dataToWrite.begin(), dataToWrite.begin() + writeLen,
+ fruCache.begin() + fruInventoryOffset);
+
+ bool atEnd = false;
+
+ if (fruCache.size() >= sizeof(FRUHeader))
+ {
+ FRUHeader* header = reinterpret_cast<FRUHeader*>(fruCache.data());
+
+ size_t areaLength = 0;
+ size_t lastRecordStart = std::max(
+ {header->internalOffset, header->chassisOffset, header->boardOffset,
+ header->productOffset, header->multiRecordOffset});
+ lastRecordStart *= 8; // header starts in are multiples of 8 bytes
+
+ if (header->multiRecordOffset)
+ {
+ // This FRU has a MultiRecord Area
+ uint8_t endOfList = 0;
+ // Walk the MultiRecord headers until the last record
+ while (!endOfList)
+ {
+ // The MSB in the second byte of the MultiRecord header signals
+ // "End of list"
+ endOfList = fruCache[lastRecordStart + 1] & 0x80;
+ // Third byte in the MultiRecord header is the length
+ areaLength = fruCache[lastRecordStart + 2];
+ // This length is in bytes (not 8 bytes like other headers)
+ areaLength += 5; // The length omits the 5 byte header
+ if (!endOfList)
+ {
+ // Next MultiRecord header
+ lastRecordStart += areaLength;
+ }
+ }
+ }
+ else
+ {
+ // This FRU does not have a MultiRecord Area
+ // Get the length of the area in multiples of 8 bytes
+ if (lastWriteAddr > (lastRecordStart + 1))
+ {
+ // second byte in record area is the length
+ areaLength = fruCache[lastRecordStart + 1];
+ areaLength *= 8; // it is in multiples of 8 bytes
+ }
+ }
+ if (lastWriteAddr >= (areaLength + lastRecordStart))
+ {
+ atEnd = true;
+ }
+ }
+ uint8_t countWritten = 0;
+
+ writeBus = cacheBus;
+ writeAddr = cacheAddr;
+ if (atEnd)
+ {
+ // cancel timer, we're at the end so might as well send it
+ writeTimer->stop();
+ if (!writeFru())
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+ countWritten = std::min(fruCache.size(), static_cast<size_t>(0xFF));
+ }
+ else
+ {
+ // start a timer, if no further data is sent to check to see if it is
+ // valid
+ writeTimer->start(std::chrono::duration_cast<std::chrono::microseconds>(
+ std::chrono::seconds(writeTimeoutSeconds)));
+ countWritten = 0;
+ }
+
+ return ipmi::responseSuccess(countWritten);
+}
+
+/** @brief implements the get FRU inventory area info command
+ * @param fruDeviceId - FRU Device ID
+ *
+ * @returns IPMI completion code plus response data
+ * - inventorySize - Number of possible allocation units
+ * - accessType - Allocation unit size in bytes.
+ */
+ipmi::RspType<uint16_t, // inventorySize
+ uint8_t> // accessType
+ ipmiStorageGetFruInvAreaInfo(ipmi::Context::ptr ctx, uint8_t fruDeviceId)
+{
+ if (fruDeviceId == 0xFF)
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ ipmi::Cc ret = getFru(ctx, fruDeviceId);
+ if (ret != ipmi::ccSuccess)
+ {
+ return ipmi::response(ret);
+ }
+
+ constexpr uint8_t accessType =
+ static_cast<uint8_t>(GetFRUAreaAccessType::byte);
+
+ return ipmi::responseSuccess(fruCache.size(), accessType);
+}
+
+ipmi_ret_t getFruSdrCount(ipmi::Context::ptr ctx, size_t& count)
+{
+ count = deviceHashes.size();
+ return IPMI_CC_OK;
+}
+
+ipmi_ret_t getFruSdrs(ipmi::Context::ptr ctx, size_t index,
+ get_sdr::SensorDataFruRecord& resp)
+{
+ if (deviceHashes.size() < index)
+ {
+ return IPMI_CC_INVALID_FIELD_REQUEST;
+ }
+ auto device = deviceHashes.begin() + index;
+ uint8_t& bus = device->second.first;
+ uint8_t& address = device->second.second;
+
+ boost::container::flat_map<std::string, Value>* fruData = nullptr;
+ auto fru =
+ std::find_if(frus.begin(), frus.end(),
+ [bus, address, &fruData](ManagedEntry& entry) {
+ auto findFruDevice =
+ entry.second.find("xyz.openbmc_project.FruDevice");
+ if (findFruDevice == entry.second.end())
+ {
+ return false;
+ }
+ fruData = &(findFruDevice->second);
+ auto findBus = findFruDevice->second.find("BUS");
+ auto findAddress =
+ findFruDevice->second.find("ADDRESS");
+ if (findBus == findFruDevice->second.end() ||
+ findAddress == findFruDevice->second.end())
+ {
+ return false;
+ }
+ if (std::get<uint32_t>(findBus->second) != bus)
+ {
+ return false;
+ }
+ if (std::get<uint32_t>(findAddress->second) != address)
+ {
+ return false;
+ }
+ return true;
+ });
+ if (fru == frus.end())
+ {
+ return IPMI_CC_RESPONSE_ERROR;
+ }
+
+#ifdef USING_ENTITY_MANAGER_DECORATORS
+
+ boost::container::flat_map<std::string, Value>* entityData = nullptr;
+
+ // todo: this should really use caching, this is a very inefficient lookup
+ boost::system::error_code ec;
+ ManagedObjectType entities = ctx->bus->yield_method_call<ManagedObjectType>(
+ ctx->yield, ec, entityManagerServiceName, "/",
+ "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
+
+ if (ec)
+ {
+ phosphor::logging::log<phosphor::logging::level::ERR>(
+ "GetMangagedObjects for ipmiStorageGetFruInvAreaInfo failed",
+ phosphor::logging::entry("ERROR=%s", ec.message().c_str()));
+
+ return ipmi::ccResponseError;
+ }
+
+ auto entity = std::find_if(
+ entities.begin(), entities.end(),
+ [bus, address, &entityData](ManagedEntry& entry) {
+ auto findFruDevice = entry.second.find(
+ "xyz.openbmc_project.Inventory.Decorator.FruDevice");
+ if (findFruDevice == entry.second.end())
+ {
+ return false;
+ }
+
+ // Integer fields added via Entity-Manager json are uint64_ts by
+ // default.
+ auto findBus = findFruDevice->second.find("Bus");
+ auto findAddress = findFruDevice->second.find("Address");
+
+ if (findBus == findFruDevice->second.end() ||
+ findAddress == findFruDevice->second.end())
+ {
+ return false;
+ }
+ if ((std::get<uint64_t>(findBus->second) != bus) ||
+ (std::get<uint64_t>(findAddress->second) != address))
+ {
+ return false;
+ }
+
+ // At this point we found the device entry and should return
+ // true.
+ auto findIpmiDevice = entry.second.find(
+ "xyz.openbmc_project.Inventory.Decorator.Ipmi");
+ if (findIpmiDevice != entry.second.end())
+ {
+ entityData = &(findIpmiDevice->second);
+ }
+
+ return true;
+ });
+
+ if (entity == entities.end())
+ {
+ if constexpr (DEBUG)
+ {
+ std::fprintf(stderr, "Ipmi or FruDevice Decorator interface "
+ "not found for Fru\n");
+ }
+ }
+
+#endif
+
+ std::string name;
+ auto findProductName = fruData->find("BOARD_PRODUCT_NAME");
+ auto findBoardName = fruData->find("PRODUCT_PRODUCT_NAME");
+ if (findProductName != fruData->end())
+ {
+ name = std::get<std::string>(findProductName->second);
+ }
+ else if (findBoardName != fruData->end())
+ {
+ name = std::get<std::string>(findBoardName->second);
+ }
+ else
+ {
+ name = "UNKNOWN";
+ }
+ if (name.size() > maxFruSdrNameSize)
+ {
+ name = name.substr(0, maxFruSdrNameSize);
+ }
+ size_t sizeDiff = maxFruSdrNameSize - name.size();
+
+ resp.header.record_id_lsb = 0x0; // calling code is to implement these
+ resp.header.record_id_msb = 0x0;
+ resp.header.sdr_version = ipmiSdrVersion;
+ resp.header.record_type = get_sdr::SENSOR_DATA_FRU_RECORD;
+ resp.header.record_length = sizeof(resp.body) + sizeof(resp.key) - sizeDiff;
+ resp.key.deviceAddress = 0x20;
+ resp.key.fruID = device->first;
+ resp.key.accessLun = 0x80; // logical / physical fru device
+ resp.key.channelNumber = 0x0;
+ resp.body.reserved = 0x0;
+ resp.body.deviceType = 0x10;
+ resp.body.deviceTypeModifier = 0x0;
+
+ uint8_t entityID = 0;
+ uint8_t entityInstance = 0x1;
+
+#ifdef USING_ENTITY_MANAGER_DECORATORS
+ if (entityData)
+ {
+ auto entityIdProperty = entityData->find("EntityId");
+ auto entityInstanceProperty = entityData->find("EntityInstance");
+
+ if (entityIdProperty != entityData->end())
+ {
+ entityID = static_cast<uint8_t>(
+ std::get<uint64_t>(entityIdProperty->second));
+ }
+ if (entityInstanceProperty != entityData->end())
+ {
+ entityInstance = static_cast<uint8_t>(
+ std::get<uint64_t>(entityInstanceProperty->second));
+ }
+ }
+#endif
+
+ resp.body.entityID = entityID;
+ resp.body.entityInstance = entityInstance;
+
+ resp.body.oem = 0x0;
+ resp.body.deviceIDLen = name.size();
+ name.copy(resp.body.deviceID, name.size());
+
+ return IPMI_CC_OK;
+}
+
+static bool getSELLogFiles(std::vector<std::filesystem::path>& selLogFiles)
+{
+ // Loop through the directory looking for ipmi_sel log files
+ for (const std::filesystem::directory_entry& dirEnt :
+ std::filesystem::directory_iterator(
+ dynamic_sensors::ipmi::sel::selLogDir))
+ {
+ std::string filename = dirEnt.path().filename();
+ if (boost::starts_with(filename,
+ dynamic_sensors::ipmi::sel::selLogFilename))
+ {
+ // If we find an ipmi_sel log file, save the path
+ selLogFiles.emplace_back(dynamic_sensors::ipmi::sel::selLogDir /
+ filename);
+ }
+ }
+ // As the log files rotate, they are appended with a ".#" that is higher for
+ // the older logs. Since we don't expect more than 10 log files, we
+ // can just sort the list to get them in order from newest to oldest
+ std::sort(selLogFiles.begin(), selLogFiles.end());
+
+ return !selLogFiles.empty();
+}
+
+static int countSELEntries()
+{
+ // Get the list of ipmi_sel log files
+ std::vector<std::filesystem::path> selLogFiles;
+ if (!getSELLogFiles(selLogFiles))
+ {
+ return 0;
+ }
+ int numSELEntries = 0;
+ // Loop through each log file and count the number of logs
+ for (const std::filesystem::path& file : selLogFiles)
+ {
+ std::ifstream logStream(file);
+ if (!logStream.is_open())
+ {
+ continue;
+ }
+
+ std::string line;
+ while (std::getline(logStream, line))
+ {
+ numSELEntries++;
+ }
+ }
+ return numSELEntries;
+}
+
+static bool findSELEntry(const int recordID,
+ const std::vector<std::filesystem::path>& selLogFiles,
+ std::string& entry)
+{
+ // Record ID is the first entry field following the timestamp. It is
+ // preceded by a space and followed by a comma
+ std::string search = " " + std::to_string(recordID) + ",";
+
+ // Loop through the ipmi_sel log entries
+ for (const std::filesystem::path& file : selLogFiles)
+ {
+ std::ifstream logStream(file);
+ if (!logStream.is_open())
+ {
+ continue;
+ }
+
+ while (std::getline(logStream, entry))
+ {
+ // Check if the record ID matches
+ if (entry.find(search) != std::string::npos)
+ {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+static uint16_t
+ getNextRecordID(const uint16_t recordID,
+ const std::vector<std::filesystem::path>& selLogFiles)
+{
+ uint16_t nextRecordID = recordID + 1;
+ std::string entry;
+ if (findSELEntry(nextRecordID, selLogFiles, entry))
+ {
+ return nextRecordID;
+ }
+ else
+ {
+ return ipmi::sel::lastEntry;
+ }
+}
+
+static int fromHexStr(const std::string& hexStr, std::vector<uint8_t>& data)
+{
+ for (unsigned int i = 0; i < hexStr.size(); i += 2)
+ {
+ try
+ {
+ data.push_back(static_cast<uint8_t>(
+ std::stoul(hexStr.substr(i, 2), nullptr, 16)));
+ }
+ catch (std::invalid_argument& e)
+ {
+ phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
+ return -1;
+ }
+ catch (std::out_of_range& e)
+ {
+ phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
+ return -1;
+ }
+ }
+ return 0;
+}
+
+ipmi::RspType<uint8_t, // SEL version
+ uint16_t, // SEL entry count
+ uint16_t, // free space
+ uint32_t, // last add timestamp
+ uint32_t, // last erase timestamp
+ uint8_t> // operation support
+ ipmiStorageGetSELInfo()
+{
+ constexpr uint8_t selVersion = ipmi::sel::selVersion;
+ uint16_t entries = countSELEntries();
+ uint32_t addTimeStamp = dynamic_sensors::ipmi::sel::getFileTimestamp(
+ dynamic_sensors::ipmi::sel::selLogDir /
+ dynamic_sensors::ipmi::sel::selLogFilename);
+ uint32_t eraseTimeStamp = dynamic_sensors::ipmi::sel::erase_time::get();
+ constexpr uint8_t operationSupport =
+ dynamic_sensors::ipmi::sel::selOperationSupport;
+ constexpr uint16_t freeSpace =
+ 0xffff; // Spec indicates that more than 64kB is free
+
+ return ipmi::responseSuccess(selVersion, entries, freeSpace, addTimeStamp,
+ eraseTimeStamp, operationSupport);
+}
+
+using systemEventType = std::tuple<
+ uint32_t, // Timestamp
+ uint16_t, // Generator ID
+ uint8_t, // EvM Rev
+ uint8_t, // Sensor Type
+ uint8_t, // Sensor Number
+ uint7_t, // Event Type
+ bool, // Event Direction
+ std::array<uint8_t, dynamic_sensors::ipmi::sel::systemEventSize>>; // Event
+ // Data
+using oemTsEventType = std::tuple<
+ uint32_t, // Timestamp
+ std::array<uint8_t, dynamic_sensors::ipmi::sel::oemTsEventSize>>; // Event
+ // Data
+using oemEventType =
+ std::array<uint8_t, dynamic_sensors::ipmi::sel::oemEventSize>; // Event Data
+
+ipmi::RspType<uint16_t, // Next Record ID
+ uint16_t, // Record ID
+ uint8_t, // Record Type
+ std::variant<systemEventType, oemTsEventType,
+ oemEventType>> // Record Content
+ ipmiStorageGetSELEntry(uint16_t reservationID, uint16_t targetID,
+ uint8_t offset, uint8_t size)
+{
+ // Only support getting the entire SEL record. If a partial size or non-zero
+ // offset is requested, return an error
+ if (offset != 0 || size != ipmi::sel::entireRecord)
+ {
+ return ipmi::responseRetBytesUnavailable();
+ }
+
+ // Check the reservation ID if one is provided or required (only if the
+ // offset is non-zero)
+ if (reservationID != 0 || offset != 0)
+ {
+ if (!checkSELReservation(reservationID))
+ {
+ return ipmi::responseInvalidReservationId();
+ }
+ }
+
+ // Get the ipmi_sel log files
+ std::vector<std::filesystem::path> selLogFiles;
+ if (!getSELLogFiles(selLogFiles))
+ {
+ return ipmi::responseSensorInvalid();
+ }
+
+ std::string targetEntry;
+
+ if (targetID == ipmi::sel::firstEntry)
+ {
+ // The first entry will be at the top of the oldest log file
+ std::ifstream logStream(selLogFiles.back());
+ if (!logStream.is_open())
+ {
+ return ipmi::responseUnspecifiedError();
+ }
+
+ if (!std::getline(logStream, targetEntry))
+ {
+ return ipmi::responseUnspecifiedError();
+ }
+ }
+ else if (targetID == ipmi::sel::lastEntry)
+ {
+ // The last entry will be at the bottom of the newest log file
+ std::ifstream logStream(selLogFiles.front());
+ if (!logStream.is_open())
+ {
+ return ipmi::responseUnspecifiedError();
+ }
+
+ std::string line;
+ while (std::getline(logStream, line))
+ {
+ targetEntry = line;
+ }
+ }
+ else
+ {
+ if (!findSELEntry(targetID, selLogFiles, targetEntry))
+ {
+ return ipmi::responseSensorInvalid();
+ }
+ }
+
+ // The format of the ipmi_sel message is "<Timestamp>
+ // <ID>,<Type>,<EventData>,[<Generator ID>,<Path>,<Direction>]".
+ // First get the Timestamp
+ size_t space = targetEntry.find_first_of(" ");
+ if (space == std::string::npos)
+ {
+ return ipmi::responseUnspecifiedError();
+ }
+ std::string entryTimestamp = targetEntry.substr(0, space);
+ // Then get the log contents
+ size_t entryStart = targetEntry.find_first_not_of(" ", space);
+ if (entryStart == std::string::npos)
+ {
+ return ipmi::responseUnspecifiedError();
+ }
+ std::string_view entry(targetEntry);
+ entry.remove_prefix(entryStart);
+ // Use split to separate the entry into its fields
+ std::vector<std::string> targetEntryFields;
+ boost::split(targetEntryFields, entry, boost::is_any_of(","),
+ boost::token_compress_on);
+ if (targetEntryFields.size() < 3)
+ {
+ return ipmi::responseUnspecifiedError();
+ }
+ std::string& recordIDStr = targetEntryFields[0];
+ std::string& recordTypeStr = targetEntryFields[1];
+ std::string& eventDataStr = targetEntryFields[2];
+
+ uint16_t recordID;
+ uint8_t recordType;
+ try
+ {
+ recordID = std::stoul(recordIDStr);
+ recordType = std::stoul(recordTypeStr, nullptr, 16);
+ }
+ catch (const std::invalid_argument&)
+ {
+ return ipmi::responseUnspecifiedError();
+ }
+ uint16_t nextRecordID = getNextRecordID(recordID, selLogFiles);
+ std::vector<uint8_t> eventDataBytes;
+ if (fromHexStr(eventDataStr, eventDataBytes) < 0)
+ {
+ return ipmi::responseUnspecifiedError();
+ }
+
+ if (recordType == dynamic_sensors::ipmi::sel::systemEvent)
+ {
+ // Get the timestamp
+ std::tm timeStruct = {};
+ std::istringstream entryStream(entryTimestamp);
+
+ uint32_t timestamp = ipmi::sel::invalidTimeStamp;
+ if (entryStream >> std::get_time(&timeStruct, "%Y-%m-%dT%H:%M:%S"))
+ {
+ timestamp = std::mktime(&timeStruct);
+ }
+
+ // Set the event message revision
+ uint8_t evmRev = dynamic_sensors::ipmi::sel::eventMsgRev;
+
+ uint16_t generatorID = 0;
+ uint8_t sensorType = 0;
+ uint16_t sensorAndLun = 0;
+ uint8_t sensorNum = 0xFF;
+ uint7_t eventType = 0;
+ bool eventDir = 0;
+ // System type events should have six fields
+ if (targetEntryFields.size() >= 6)
+ {
+ std::string& generatorIDStr = targetEntryFields[3];
+ std::string& sensorPath = targetEntryFields[4];
+ std::string& eventDirStr = targetEntryFields[5];
+
+ // Get the generator ID
+ try
+ {
+ generatorID = std::stoul(generatorIDStr, nullptr, 16);
+ }
+ catch (const std::invalid_argument&)
+ {
+ std::cerr << "Invalid Generator ID\n";
+ }
+
+ // Get the sensor type, sensor number, and event type for the sensor
+ sensorType = getSensorTypeFromPath(sensorPath);
+ sensorAndLun = getSensorNumberFromPath(sensorPath);
+ sensorNum = static_cast<uint8_t>(sensorAndLun);
+ generatorID |= sensorAndLun >> 8;
+ eventType = getSensorEventTypeFromPath(sensorPath);
+
+ // Get the event direction
+ try
+ {
+ eventDir = std::stoul(eventDirStr) ? 0 : 1;
+ }
+ catch (const std::invalid_argument&)
+ {
+ std::cerr << "Invalid Event Direction\n";
+ }
+ }
+
+ // Only keep the eventData bytes that fit in the record
+ std::array<uint8_t, dynamic_sensors::ipmi::sel::systemEventSize>
+ eventData{};
+ std::copy_n(eventDataBytes.begin(),
+ std::min(eventDataBytes.size(), eventData.size()),
+ eventData.begin());
+
+ return ipmi::responseSuccess(
+ nextRecordID, recordID, recordType,
+ systemEventType{timestamp, generatorID, evmRev, sensorType,
+ sensorNum, eventType, eventDir, eventData});
+ }
+
+ return ipmi::responseUnspecifiedError();
+}
+
+ipmi::RspType<uint16_t> ipmiStorageAddSELEntry(
+ uint16_t recordID, uint8_t recordType, uint32_t timestamp,
+ uint16_t generatorID, uint8_t evmRev, uint8_t sensorType, uint8_t sensorNum,
+ uint8_t eventType, uint8_t eventData1, uint8_t eventData2,
+ uint8_t eventData3)
+{
+ // Per the IPMI spec, need to cancel any reservation when a SEL entry is
+ // added
+ cancelSELReservation();
+
+ uint16_t responseID = 0xFFFF;
+ return ipmi::responseSuccess(responseID);
+}
+
+ipmi::RspType<uint8_t> ipmiStorageClearSEL(ipmi::Context::ptr ctx,
+ uint16_t reservationID,
+ const std::array<uint8_t, 3>& clr,
+ uint8_t eraseOperation)
+{
+ if (!checkSELReservation(reservationID))
+ {
+ return ipmi::responseInvalidReservationId();
+ }
+
+ static constexpr std::array<uint8_t, 3> clrExpected = {'C', 'L', 'R'};
+ if (clr != clrExpected)
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ // Erasure status cannot be fetched, so always return erasure status as
+ // `erase completed`.
+ if (eraseOperation == ipmi::sel::getEraseStatus)
+ {
+ return ipmi::responseSuccess(ipmi::sel::eraseComplete);
+ }
+
+ // Check that initiate erase is correct
+ if (eraseOperation != ipmi::sel::initiateErase)
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ // Per the IPMI spec, need to cancel any reservation when the SEL is
+ // cleared
+ cancelSELReservation();
+
+ // Save the erase time
+ dynamic_sensors::ipmi::sel::erase_time::save();
+
+ // Clear the SEL by deleting the log files
+ std::vector<std::filesystem::path> selLogFiles;
+ if (getSELLogFiles(selLogFiles))
+ {
+ for (const std::filesystem::path& file : selLogFiles)
+ {
+ std::error_code ec;
+ std::filesystem::remove(file, ec);
+ }
+ }
+
+ // Reload rsyslog so it knows to start new log files
+ std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus();
+ sdbusplus::message::message rsyslogReload = dbus->new_method_call(
+ "org.freedesktop.systemd1", "/org/freedesktop/systemd1",
+ "org.freedesktop.systemd1.Manager", "ReloadUnit");
+ rsyslogReload.append("rsyslog.service", "replace");
+ try
+ {
+ sdbusplus::message::message reloadResponse = dbus->call(rsyslogReload);
+ }
+ catch (sdbusplus::exception_t& e)
+ {
+ phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
+ }
+
+ return ipmi::responseSuccess(ipmi::sel::eraseComplete);
+}
+
+ipmi::RspType<uint32_t> ipmiStorageGetSELTime()
+{
+ struct timespec selTime = {};
+
+ if (clock_gettime(CLOCK_REALTIME, &selTime) < 0)
+ {
+ return ipmi::responseUnspecifiedError();
+ }
+
+ return ipmi::responseSuccess(selTime.tv_sec);
+}
+
+ipmi::RspType<> ipmiStorageSetSELTime(uint32_t selTime)
+{
+ // Set SEL Time is not supported
+ return ipmi::responseInvalidCommand();
+}
+
+std::vector<uint8_t> getType12SDRs(uint16_t index, uint16_t recordId)
+{
+ std::vector<uint8_t> resp;
+ if (index == 0)
+ {
+ Type12Record bmc = {};
+ bmc.header.record_id_lsb = recordId;
+ bmc.header.record_id_msb = recordId >> 8;
+ bmc.header.sdr_version = ipmiSdrVersion;
+ bmc.header.record_type = 0x12;
+ bmc.header.record_length = 0x1b;
+ bmc.slaveAddress = 0x20;
+ bmc.channelNumber = 0;
+ bmc.powerStateNotification = 0;
+ bmc.deviceCapabilities = 0xBF;
+ bmc.reserved = 0;
+ bmc.entityID = 0x2E;
+ bmc.entityInstance = 1;
+ bmc.oem = 0;
+ bmc.typeLengthCode = 0xD0;
+ std::string bmcName = "Basbrd Mgmt Ctlr";
+ std::copy(bmcName.begin(), bmcName.end(), bmc.name);
+ uint8_t* bmcPtr = reinterpret_cast<uint8_t*>(&bmc);
+ resp.insert(resp.end(), bmcPtr, bmcPtr + sizeof(Type12Record));
+ }
+ else if (index == 1)
+ {
+ Type12Record me = {};
+ me.header.record_id_lsb = recordId;
+ me.header.record_id_msb = recordId >> 8;
+ me.header.sdr_version = ipmiSdrVersion;
+ me.header.record_type = 0x12;
+ me.header.record_length = 0x16;
+ me.slaveAddress = 0x2C;
+ me.channelNumber = 6;
+ me.powerStateNotification = 0x24;
+ me.deviceCapabilities = 0x21;
+ me.reserved = 0;
+ me.entityID = 0x2E;
+ me.entityInstance = 2;
+ me.oem = 0;
+ me.typeLengthCode = 0xCB;
+ std::string meName = "Mgmt Engine";
+ std::copy(meName.begin(), meName.end(), me.name);
+ uint8_t* mePtr = reinterpret_cast<uint8_t*>(&me);
+ resp.insert(resp.end(), mePtr, mePtr + sizeof(Type12Record));
+ }
+ else
+ {
+ throw std::runtime_error("getType12SDRs:: Illegal index " +
+ std::to_string(index));
+ }
+
+ return resp;
+}
+
+void registerStorageFunctions()
+{
+ createTimers();
+ startMatch();
+
+ // <Get FRU Inventory Area Info>
+ ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnStorage,
+ ipmi::storage::cmdGetFruInventoryAreaInfo,
+ ipmi::Privilege::User, ipmiStorageGetFruInvAreaInfo);
+ // <READ FRU Data>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+ ipmi::storage::cmdReadFruData, ipmi::Privilege::User,
+ ipmiStorageReadFruData);
+
+ // <WRITE FRU Data>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+ ipmi::storage::cmdWriteFruData,
+ ipmi::Privilege::Operator, ipmiStorageWriteFruData);
+
+ // <Get SEL Info>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+ ipmi::storage::cmdGetSelInfo, ipmi::Privilege::User,
+ ipmiStorageGetSELInfo);
+
+ // <Get SEL Entry>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+ ipmi::storage::cmdGetSelEntry, ipmi::Privilege::User,
+ ipmiStorageGetSELEntry);
+
+ // <Add SEL Entry>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+ ipmi::storage::cmdAddSelEntry,
+ ipmi::Privilege::Operator, ipmiStorageAddSELEntry);
+
+ // <Clear SEL>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+ ipmi::storage::cmdClearSel, ipmi::Privilege::Operator,
+ ipmiStorageClearSEL);
+
+ // <Get SEL Time>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+ ipmi::storage::cmdGetSelTime, ipmi::Privilege::User,
+ ipmiStorageGetSELTime);
+
+ // <Set SEL Time>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+ ipmi::storage::cmdSetSelTime,
+ ipmi::Privilege::Operator, ipmiStorageSetSELTime);
+}
+} // namespace storage
+} // namespace ipmi
diff --git a/include/Makefile.am b/include/Makefile.am
index 08824c4..7c90f70 100644
--- a/include/Makefile.am
+++ b/include/Makefile.am
@@ -17,7 +17,11 @@
ipmid/utility.hpp \
ipmid/utils.hpp \
ipmid-host/cmd.hpp \
- ipmid-host/cmd-utils.hpp
+ ipmid-host/cmd-utils.hpp \
+ dbus-sdr/sdrutils.hpp \
+ dbus-sdr/sensorcommands.hpp \
+ dbus-sdr/sensorutils.hpp \
+ dbus-sdr/storagecommands.hpp
# Eventually we will split <ipmid/*> and <host-ipmid/*> headers
# For now they will be the same during migration
diff --git a/include/dbus-sdr/sdrutils.hpp b/include/dbus-sdr/sdrutils.hpp
new file mode 100644
index 0000000..8d27c02
--- /dev/null
+++ b/include/dbus-sdr/sdrutils.hpp
@@ -0,0 +1,118 @@
+/*
+// 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 <boost/algorithm/string.hpp>
+#include <boost/bimap.hpp>
+#include <boost/container/flat_map.hpp>
+#include <cstdio>
+#include <cstring>
+#include <exception>
+#include <filesystem>
+#include <ipmid/api.hpp>
+#include <ipmid/types.hpp>
+#include <map>
+#include <phosphor-logging/log.hpp>
+#include <sdbusplus/bus/match.hpp>
+#include <string>
+#include <vector>
+
+#pragma once
+
+static constexpr bool debug = false;
+
+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<std::string, std::vector<std::string>>,
+ CmpStrVersion>;
+
+using SensorNumMap = boost::bimap<int, std::string>;
+
+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
+{
+bool getSensorSubtree(std::shared_ptr<SensorSubTree>& subtree);
+
+bool getSensorNumMap(std::shared_ptr<SensorNumMap>& sensorNumMap);
+} // namespace details
+
+bool getSensorSubtree(SensorSubTree& subtree);
+
+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<const char*, SensorTypeCodes, CmpStr>
+ sensorTypes{{{"temperature", SensorTypeCodes::temperature},
+ {"voltage", SensorTypeCodes::voltage},
+ {"current", SensorTypeCodes::current},
+ {"fan_tach", SensorTypeCodes::fan},
+ {"fan_pwm", SensorTypeCodes::fan},
+ {"power", SensorTypeCodes::other}}};
+
+std::string getSensorTypeStringFromPath(const std::string& path);
+
+uint8_t getSensorTypeFromPath(const std::string& path);
+
+uint16_t getSensorNumberFromPath(const std::string& path);
+
+uint8_t getSensorEventTypeFromPath(const std::string& path);
+
+std::string getPathFromSensorNumber(uint16_t sensorNum);
+
+namespace ipmi
+{
+std::map<std::string, std::vector<std::string>>
+ getObjectInterfaces(const char* path);
+
+std::map<std::string, Value> getEntityManagerProperties(const char* path,
+ const char* interface);
+
+const std::string* getSensorConfigurationInterface(
+ const std::map<std::string, std::vector<std::string>>&
+ sensorInterfacesResponse);
+
+void updateIpmiFromAssociation(const std::string& path,
+ const DbusInterfaceMap& sensorMap,
+ uint8_t& entityId, uint8_t& entityInstance);
+} // namespace ipmi
diff --git a/include/dbus-sdr/sensorcommands.hpp b/include/dbus-sdr/sensorcommands.hpp
new file mode 100644
index 0000000..b9c845f
--- /dev/null
+++ b/include/dbus-sdr/sensorcommands.hpp
@@ -0,0 +1,168 @@
+/*
+// Copyright (c) 2017 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.
+*/
+
+#pragma once
+#include <cstdint>
+#include <dbus-sdr/sdrutils.hpp>
+
+#pragma pack(push, 1)
+
+struct SensorThresholdResp
+{
+ uint8_t readable;
+ uint8_t lowernc;
+ uint8_t lowercritical;
+ uint8_t lowernonrecoverable;
+ uint8_t uppernc;
+ uint8_t uppercritical;
+ uint8_t uppernonrecoverable;
+};
+
+#pragma pack(pop)
+
+enum class IPMIThresholdRespBits
+{
+ lowerNonCritical,
+ lowerCritical,
+ lowerNonRecoverable,
+ upperNonCritical,
+ upperCritical,
+ upperNonRecoverable
+};
+
+enum class IPMISensorReadingByte2 : uint8_t
+{
+ eventMessagesEnable = (1 << 7),
+ sensorScanningEnable = (1 << 6),
+ readingStateUnavailable = (1 << 5),
+};
+
+enum class IPMISensorReadingByte3 : uint8_t
+{
+ upperNonRecoverable = (1 << 5),
+ upperCritical = (1 << 4),
+ upperNonCritical = (1 << 3),
+ lowerNonRecoverable = (1 << 2),
+ lowerCritical = (1 << 1),
+ lowerNonCritical = (1 << 0),
+};
+
+enum class IPMISensorEventEnableByte2 : uint8_t
+{
+ eventMessagesEnable = (1 << 7),
+ sensorScanningEnable = (1 << 6),
+};
+
+enum class IPMISensorEventEnableThresholds : uint8_t
+{
+ nonRecoverableThreshold = (1 << 6),
+ criticalThreshold = (1 << 5),
+ nonCriticalThreshold = (1 << 4),
+ upperNonRecoverableGoingHigh = (1 << 3),
+ upperNonRecoverableGoingLow = (1 << 2),
+ upperCriticalGoingHigh = (1 << 1),
+ upperCriticalGoingLow = (1 << 0),
+ upperNonCriticalGoingHigh = (1 << 7),
+ upperNonCriticalGoingLow = (1 << 6),
+ lowerNonRecoverableGoingHigh = (1 << 5),
+ lowerNonRecoverableGoingLow = (1 << 4),
+ lowerCriticalGoingHigh = (1 << 3),
+ lowerCriticalGoingLow = (1 << 2),
+ lowerNonCriticalGoingHigh = (1 << 1),
+ lowerNonCriticalGoingLow = (1 << 0),
+};
+
+enum class IPMIGetSensorEventEnableThresholds : uint8_t
+{
+ lowerNonCriticalGoingLow = 0,
+ lowerNonCriticalGoingHigh = 1,
+ lowerCriticalGoingLow = 2,
+ lowerCriticalGoingHigh = 3,
+ lowerNonRecoverableGoingLow = 4,
+ lowerNonRecoverableGoingHigh = 5,
+ upperNonCriticalGoingLow = 6,
+ upperNonCriticalGoingHigh = 7,
+ upperCriticalGoingLow = 8,
+ upperCriticalGoingHigh = 9,
+ upperNonRecoverableGoingLow = 10,
+ upperNonRecoverableGoingHigh = 11,
+};
+
+enum class IPMINetfnSensorCmds : ipmi_cmd_t
+{
+ ipmiCmdGetDeviceSDRInfo = 0x20,
+ ipmiCmdGetDeviceSDR = 0x21,
+ ipmiCmdReserveDeviceSDRRepo = 0x22,
+ ipmiCmdSetSensorThreshold = 0x26,
+ ipmiCmdGetSensorThreshold = 0x27,
+ ipmiCmdGetSensorEventEnable = 0x29,
+ ipmiCmdGetSensorEventStatus = 0x2B,
+ ipmiCmdGetSensorReading = 0x2D,
+ ipmiCmdGetSensorType = 0x2F,
+ ipmiCmdSetSensorReadingAndEventStatus = 0x30,
+};
+
+namespace ipmi
+{
+
+SensorSubTree& getSensorTree()
+{
+ static SensorSubTree sensorTree;
+ return sensorTree;
+}
+
+static ipmi_ret_t getSensorConnection(ipmi::Context::ptr ctx, uint8_t sensnum,
+ std::string& connection,
+ std::string& path)
+{
+ auto& sensorTree = getSensorTree();
+ if (sensorTree.empty() && !getSensorSubtree(sensorTree))
+ {
+ return IPMI_CC_RESPONSE_ERROR;
+ }
+
+ if (ctx == nullptr)
+ {
+ return IPMI_CC_RESPONSE_ERROR;
+ }
+
+ path = getPathFromSensorNumber((ctx->lun << 8) | sensnum);
+ if (path.empty())
+ {
+ return IPMI_CC_INVALID_FIELD_REQUEST;
+ }
+
+ for (const auto& sensor : sensorTree)
+ {
+ if (path == sensor.first)
+ {
+ connection = sensor.second.begin()->first;
+ break;
+ }
+ }
+
+ return 0;
+}
+
+struct IPMIThresholds
+{
+ std::optional<uint8_t> warningLow;
+ std::optional<uint8_t> warningHigh;
+ std::optional<uint8_t> criticalLow;
+ std::optional<uint8_t> criticalHigh;
+};
+
+} // namespace ipmi
diff --git a/include/dbus-sdr/sensorutils.hpp b/include/dbus-sdr/sensorutils.hpp
new file mode 100644
index 0000000..c68a449
--- /dev/null
+++ b/include/dbus-sdr/sensorutils.hpp
@@ -0,0 +1,39 @@
+/*
+// Copyright (c) 2017 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.
+*/
+
+#pragma once
+#include <algorithm>
+#include <cmath>
+#include <iostream>
+
+namespace ipmi
+{
+static constexpr int16_t maxInt10 = 0x1FF;
+static constexpr int16_t minInt10 = -0x200;
+static constexpr int8_t maxInt4 = 7;
+static constexpr int8_t minInt4 = -8;
+
+bool getSensorAttributes(const double max, const double min, int16_t& mValue,
+ int8_t& rExp, int16_t& bValue, int8_t& bExp,
+ bool& bSigned);
+
+uint8_t scaleIPMIValueFromDouble(const double value, const int16_t mValue,
+ const int8_t rExp, const int16_t bValue,
+ const int8_t bExp, const bool bSigned);
+
+uint8_t getScaledIPMIValue(const double value, const double max,
+ const double min);
+} // namespace ipmi
diff --git a/include/dbus-sdr/storagecommands.hpp b/include/dbus-sdr/storagecommands.hpp
new file mode 100644
index 0000000..d0fa110
--- /dev/null
+++ b/include/dbus-sdr/storagecommands.hpp
@@ -0,0 +1,113 @@
+/*
+// Copyright (c) 2017 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.
+*/
+
+#pragma once
+#include "sensorhandler.hpp"
+
+#include <cstdint>
+
+#define USING_ENTITY_MANAGER_DECORATORS
+
+static constexpr uint8_t ipmiSdrVersion = 0x51;
+
+namespace dynamic_sensors::ipmi::sel
+{
+static constexpr uint8_t selOperationSupport = 0x02;
+static constexpr uint8_t systemEvent = 0x02;
+static constexpr size_t systemEventSize = 3;
+static constexpr uint8_t oemTsEventFirst = 0xC0;
+static constexpr uint8_t oemTsEventLast = 0xDF;
+static constexpr size_t oemTsEventSize = 9;
+static constexpr uint8_t oemEventFirst = 0xE0;
+static constexpr uint8_t oemEventLast = 0xFF;
+static constexpr size_t oemEventSize = 13;
+static constexpr uint8_t eventMsgRev = 0x04;
+} // namespace dynamic_sensors::ipmi::sel
+
+enum class SdrRepositoryInfoOps : uint8_t
+{
+ allocCommandSupported = 0x1,
+ reserveSDRRepositoryCommandSupported = 0x2,
+ partialAddSDRSupported = 0x4,
+ deleteSDRSupported = 0x8,
+ reserved = 0x10,
+ modalLSB = 0x20,
+ modalMSB = 0x40,
+ overflow = 0x80
+};
+
+enum class GetFRUAreaAccessType : uint8_t
+{
+ byte = 0x0,
+ words = 0x1
+};
+
+enum class SensorUnits : uint8_t
+{
+ unspecified = 0x0,
+ degreesC = 0x1,
+ volts = 0x4,
+ amps = 0x5,
+ watts = 0x6,
+ rpm = 0x12,
+};
+
+#pragma pack(push, 1)
+struct FRUHeader
+{
+ uint8_t commonHeaderFormat;
+ uint8_t internalOffset;
+ uint8_t chassisOffset;
+ uint8_t boardOffset;
+ uint8_t productOffset;
+ uint8_t multiRecordOffset;
+ uint8_t pad;
+ uint8_t checksum;
+};
+#pragma pack(pop)
+
+#pragma pack(push, 1)
+struct Type12Record
+{
+ get_sdr::SensorDataRecordHeader header;
+ uint8_t slaveAddress;
+ uint8_t channelNumber;
+ uint8_t powerStateNotification;
+ uint8_t deviceCapabilities;
+ uint24_t reserved;
+ uint8_t entityID;
+ uint8_t entityInstance;
+ uint8_t oem;
+ uint8_t typeLengthCode;
+ char name[16];
+};
+#pragma pack(pop)
+
+namespace ipmi
+{
+namespace storage
+{
+
+constexpr const size_t type12Count = 2;
+ipmi_ret_t getFruSdrs(ipmi::Context::ptr ctx, size_t index,
+ get_sdr::SensorDataFruRecord& resp);
+
+ipmi_ret_t getFruSdrCount(ipmi::Context::ptr ctx, size_t& count);
+
+std::vector<uint8_t> getType12SDRs(uint16_t index, uint16_t recordId);
+std::vector<uint8_t> getNMDiscoverySDR(uint16_t index, uint16_t recordId);
+} // namespace storage
+} // namespace ipmi
diff --git a/include/ipmid/types.hpp b/include/ipmid/types.hpp
index fa4d58c..e62c819 100644
--- a/include/ipmid/types.hpp
+++ b/include/ipmid/types.hpp
@@ -16,8 +16,11 @@
using DbusObjectInfo = std::pair<DbusObjectPath, DbusService>;
using DbusProperty = std::string;
-using Value = std::variant<bool, uint8_t, int16_t, uint16_t, int32_t, uint32_t,
- int64_t, uint64_t, double, std::string>;
+using Association = std::tuple<std::string, std::string, std::string>;
+
+using Value =
+ std::variant<bool, uint8_t, int16_t, uint16_t, int32_t, uint32_t, int64_t,
+ uint64_t, double, std::string, std::vector<Association>>;
using PropertyMap = std::map<DbusProperty, Value>;
diff --git a/test/Makefile.am b/test/Makefile.am
index 0faeda7..ff6d910 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -90,3 +90,22 @@
$(CODE_COVERAGE_LDFLAGS)
session_unittest_SOURCES = %reldir%/session/closesession_unittest.cpp
check_PROGRAMS += %reldir%/session_unittest
+
+# Build/add sensorcommands_unittest to test suite
+sensorcommands_unittest_CPPFLAGS = \
+ -Igtest \
+ $(GTEST_CPPFLAGS) \
+ $(AM_CPPFLAGS)
+sensorcommands_unittest_CXXFLAGS = \
+ $(PTHREAD_CFLAGS) \
+ $(CODE_COVERAGE_CXXFLAGS) \
+ $(CODE_COVERAGE_CFLAGS)
+sensorcommands_unittest_LDFLAGS = \
+ -lgtest_main \
+ -lgtest \
+ -pthread \
+ $(OESDK_TESTCASE_FLAGS) \
+ $(CODE_COVERAGE_LDFLAGS)
+sensorcommands_unittest_SOURCES = %reldir%/dbus-sdr/sensorcommands_unittest.cpp
+sensorcommands_unittest_LDADD = $(top_builddir)/dbus-sdr/sensorutils.o
+check_PROGRAMS += %reldir%/sensorcommands_unittest
diff --git a/test/dbus-sdr/sensorcommands_unittest.cpp b/test/dbus-sdr/sensorcommands_unittest.cpp
new file mode 100644
index 0000000..83918c5
--- /dev/null
+++ b/test/dbus-sdr/sensorcommands_unittest.cpp
@@ -0,0 +1,488 @@
+#include "dbus-sdr/sensorutils.hpp"
+
+#include <cmath>
+
+#include "gtest/gtest.h"
+
+// There is a surprising amount of slop in the math,
+// thanks to all the rounding and conversion.
+// The "x" byte value can drift by up to 2 away, I have seen.
+static constexpr int8_t expectedSlopX = 2;
+
+// Unlike expectedSlopX, this is a ratio, not an integer
+// It scales based on the range of "y"
+static constexpr double expectedSlopY = 0.01;
+
+// The algorithm here was copied from ipmitool
+// sdr_convert_sensor_reading() function
+// https://github.com/ipmitool/ipmitool/blob/42a023ff0726c80e8cc7d30315b987fe568a981d/lib/ipmi_sdr.c#L360
+double ipmitool_y_from_x(uint8_t x, int m, int k2_rExp, int b, int k1_bExp,
+ bool bSigned)
+{
+ double result;
+
+ // Rename to exactly match names and types (except analog) from ipmitool
+ uint8_t val = x;
+ double k1 = k1_bExp;
+ double k2 = k2_rExp;
+ int analog = bSigned ? 2 : 0;
+
+ // Begin paste here
+ // Only change is to comment out complicated structure in switch statement
+
+ switch (/*sensor->cmn.unit.*/ analog)
+ {
+ case 0:
+ result = (double)(((m * val) + (b * pow(10, k1))) * pow(10, k2));
+ break;
+ case 1:
+ if (val & 0x80)
+ val++;
+ /* Deliberately fall through to case 2. */
+ case 2:
+ result =
+ (double)(((m * (int8_t)val) + (b * pow(10, k1))) * pow(10, k2));
+ break;
+ default:
+ /* Oops! This isn't an analog sensor. */
+ return 0.0;
+ }
+
+ // End paste here
+ // Ignoring linearization curves and postprocessing that follows,
+ // assuming all sensors are perfectly linear
+ return result;
+}
+
+void testValue(int x, double y, int16_t M, int8_t rExp, int16_t B, int8_t bExp,
+ bool bSigned, double yRange)
+{
+ double yRoundtrip;
+ int result;
+
+ // There is intentionally no exception catching here,
+ // because if getSensorAttributes() returned true,
+ // it is a promise that all of these should work.
+ if (bSigned)
+ {
+ int8_t expect = x;
+ int8_t actual =
+ ipmi::scaleIPMIValueFromDouble(y, M, rExp, B, bExp, bSigned);
+
+ result = actual;
+ yRoundtrip = ipmitool_y_from_x(actual, M, rExp, B, bExp, bSigned);
+
+ EXPECT_NEAR(actual, expect, expectedSlopX);
+ }
+ else
+ {
+ uint8_t expect = x;
+ uint8_t actual =
+ ipmi::scaleIPMIValueFromDouble(y, M, rExp, B, bExp, bSigned);
+
+ result = actual;
+ yRoundtrip = ipmitool_y_from_x(actual, M, rExp, B, bExp, bSigned);
+
+ EXPECT_NEAR(actual, expect, expectedSlopX);
+ }
+
+ // Scale the amount of allowed slop in y based on range, so ratio similar
+ double yTolerance = yRange * expectedSlopY;
+ // double yError = std::abs(y - yRoundtrip);
+
+ EXPECT_NEAR(y, yRoundtrip, yTolerance);
+
+ char szFormat[1024];
+ sprintf(szFormat,
+ "Value | xExpect %4d | xResult %4d "
+ "| M %5d | rExp %3d "
+ "| B %5d | bExp %3d | bSigned %1d | y %18.3f | yRoundtrip %18.3f\n",
+ x, result, M, (int)rExp, B, (int)bExp, (int)bSigned, y, yRoundtrip);
+ std::cout << szFormat;
+}
+
+void testBounds(double yMin, double yMax, bool bExpectedOutcome = true)
+{
+ int16_t mValue;
+ int8_t rExp;
+ int16_t bValue;
+ int8_t bExp;
+ bool bSigned;
+ bool result;
+
+ result = ipmi::getSensorAttributes(yMax, yMin, mValue, rExp, bValue, bExp,
+ bSigned);
+ EXPECT_EQ(result, bExpectedOutcome);
+
+ if (!result)
+ {
+ return;
+ }
+
+ char szFormat[1024];
+ sprintf(szFormat,
+ "Bounds | yMin %18.3f | yMax %18.3f | M %5d"
+ " | rExp %3d | B %5d | bExp %3d | bSigned %1d\n",
+ yMin, yMax, mValue, (int)rExp, bValue, (int)bExp, (int)bSigned);
+ std::cout << szFormat;
+
+ double y50p = (yMin + yMax) / 2.0;
+
+ // Average the average
+ double y25p = (yMin + y50p) / 2.0;
+ double y75p = (y50p + yMax) / 2.0;
+
+ // This range value is only used for tolerance checking, not computation
+ double yRange = yMax - yMin;
+
+ if (bSigned)
+ {
+ int8_t xMin = -128;
+ int8_t x25p = -64;
+ int8_t x50p = 0;
+ int8_t x75p = 64;
+ int8_t xMax = 127;
+
+ testValue(xMin, yMin, mValue, rExp, bValue, bExp, bSigned, yRange);
+ testValue(x25p, y25p, mValue, rExp, bValue, bExp, bSigned, yRange);
+ testValue(x50p, y50p, mValue, rExp, bValue, bExp, bSigned, yRange);
+ testValue(x75p, y75p, mValue, rExp, bValue, bExp, bSigned, yRange);
+ testValue(xMax, yMax, mValue, rExp, bValue, bExp, bSigned, yRange);
+ }
+ else
+ {
+ uint8_t xMin = 0;
+ uint8_t x25p = 64;
+ uint8_t x50p = 128;
+ uint8_t x75p = 192;
+ uint8_t xMax = 255;
+
+ testValue(xMin, yMin, mValue, rExp, bValue, bExp, bSigned, yRange);
+ testValue(x25p, y25p, mValue, rExp, bValue, bExp, bSigned, yRange);
+ testValue(x50p, y50p, mValue, rExp, bValue, bExp, bSigned, yRange);
+ testValue(x75p, y75p, mValue, rExp, bValue, bExp, bSigned, yRange);
+ testValue(xMax, yMax, mValue, rExp, bValue, bExp, bSigned, yRange);
+ }
+}
+
+void testRanges(void)
+{
+ // The ranges from the main TEST function
+ testBounds(0x0, 0xFF);
+ testBounds(-128, 127);
+ testBounds(0, 16000);
+ testBounds(0, 20);
+ testBounds(8000, 16000);
+ testBounds(-10, 10);
+ testBounds(0, 277);
+ testBounds(0, 0, false);
+ testBounds(10, 12);
+
+ // Additional test cases recommended to me by hardware people
+ testBounds(-40, 150);
+ testBounds(0, 1);
+ testBounds(0, 2);
+ testBounds(0, 4);
+ testBounds(0, 8);
+ testBounds(35, 65);
+ testBounds(0, 18);
+ testBounds(0, 25);
+ testBounds(0, 80);
+ testBounds(0, 500);
+
+ // Additional sanity checks
+ testBounds(0, 255);
+ testBounds(-255, 0);
+ testBounds(-255, 255);
+ testBounds(0, 1000);
+ testBounds(-1000, 0);
+ testBounds(-1000, 1000);
+ testBounds(0, 255000);
+ testBounds(-128000000, 127000000);
+ testBounds(-50000, 0);
+ testBounds(-40000, 10000);
+ testBounds(-30000, 20000);
+ testBounds(-20000, 30000);
+ testBounds(-10000, 40000);
+ testBounds(0, 50000);
+ testBounds(-1e3, 1e6);
+ testBounds(-1e6, 1e3);
+
+ // Extreme ranges are now possible
+ testBounds(0, 1e10);
+ testBounds(0, 1e11);
+ testBounds(0, 1e12);
+ testBounds(0, 1e13, false);
+ testBounds(-1e10, 0);
+ testBounds(-1e11, 0);
+ testBounds(-1e12, 0);
+ testBounds(-1e13, 0, false);
+ testBounds(-1e9, 1e9);
+ testBounds(-1e10, 1e10);
+ testBounds(-1e11, 1e11);
+ testBounds(-1e12, 1e12, false);
+
+ // Large multiplier but small offset
+ testBounds(1e4, 1e4 + 255);
+ testBounds(1e5, 1e5 + 255);
+ testBounds(1e6, 1e6 + 255);
+ testBounds(1e7, 1e7 + 255);
+ testBounds(1e8, 1e8 + 255);
+ testBounds(1e9, 1e9 + 255);
+ testBounds(1e10, 1e10 + 255, false);
+
+ // Input validation against garbage
+ testBounds(0, INFINITY, false);
+ testBounds(-INFINITY, 0, false);
+ testBounds(-INFINITY, INFINITY, false);
+ testBounds(0, NAN, false);
+ testBounds(NAN, 0, false);
+ testBounds(NAN, NAN, false);
+
+ // Noteworthy binary integers
+ testBounds(0, std::pow(2.0, 32.0) - 1.0);
+ testBounds(0, std::pow(2.0, 32.0));
+ testBounds(0.0 - std::pow(2.0, 31.0), std::pow(2.0, 31.0));
+ testBounds((0.0 - std::pow(2.0, 31.0)) - 1.0, std::pow(2.0, 31.0));
+
+ // Similar but negative (note additional commented-out below)
+ testBounds(-1e1, (-1e1) + 255);
+ testBounds(-1e2, (-1e2) + 255);
+
+ // Ranges of negative numbers (note additional commented-out below)
+ testBounds(-10400, -10000);
+ testBounds(-15000, -14000);
+ testBounds(-10000, -9000);
+ testBounds(-1000, -900);
+ testBounds(-1000, -800);
+ testBounds(-1000, -700);
+ testBounds(-1000, -740);
+
+ // Very small ranges (note additional commented-out below)
+ testBounds(0, 0.1);
+ testBounds(0, 0.01);
+ testBounds(0, 0.001);
+ testBounds(0, 0.0001);
+ testBounds(0, 0.000001, false);
+
+#if 0
+ // TODO(): The algorithm in this module is better than it was before,
+ // but the resulting value of X is still wrong under certain conditions,
+ // such as when the range between min and max is around 255,
+ // and the offset is fairly extreme compared to the multiplier.
+ // Not sure why this is, but these ranges are contrived,
+ // and real-world examples would most likely never be this way.
+ testBounds(-10290, -10000);
+ testBounds(-10280, -10000);
+ testBounds(-10275,-10000);
+ testBounds(-10270,-10000);
+ testBounds(-10265,-10000);
+ testBounds(-10260,-10000);
+ testBounds(-10255,-10000);
+ testBounds(-10250,-10000);
+ testBounds(-10245,-10000);
+ testBounds(-10256,-10000);
+ testBounds(-10512, -10000);
+ testBounds(-11024, -10000);
+
+ // TODO(): This also fails, due to extreme small range, loss of precision
+ testBounds(0, 0.00001);
+
+ // TODO(): Interestingly, if bSigned is forced false,
+ // causing "x" to have range of (0,255) instead of (-128,127),
+ // these test cases change from failing to passing!
+ // Not sure why this is, perhaps a mathematician might know.
+ testBounds(-10300, -10000);
+ testBounds(-1000,-750);
+ testBounds(-1e3, (-1e3) + 255);
+ testBounds(-1e4, (-1e4) + 255);
+ testBounds(-1e5, (-1e5) + 255);
+ testBounds(-1e6, (-1e6) + 255);
+#endif
+}
+
+TEST(sensorutils, TranslateToIPMI)
+{
+ /*bool getSensorAttributes(double maxValue, double minValue, int16_t
+ &mValue, int8_t &rExp, int16_t &bValue, int8_t &bExp, bool &bSigned); */
+ // normal unsigned sensor
+ double maxValue = 0xFF;
+ double minValue = 0x0;
+ int16_t mValue;
+ int8_t rExp;
+ int16_t bValue;
+ int8_t bExp;
+ bool bSigned;
+ bool result;
+
+ uint8_t scaledVal;
+
+ result = ipmi::getSensorAttributes(maxValue, minValue, mValue, rExp, bValue,
+ bExp, bSigned);
+ EXPECT_EQ(result, true);
+ if (result)
+ {
+ EXPECT_EQ(bSigned, false);
+ EXPECT_EQ(mValue, 1);
+ EXPECT_EQ(rExp, 0);
+ EXPECT_EQ(bValue, 0);
+ EXPECT_EQ(bExp, 0);
+ }
+ double expected = 0x50;
+ scaledVal = ipmi::scaleIPMIValueFromDouble(0x50, mValue, rExp, bValue, bExp,
+ bSigned);
+ EXPECT_NEAR(scaledVal, expected, expected * 0.01);
+
+ // normal signed sensor
+ maxValue = 127;
+ minValue = -128;
+
+ result = ipmi::getSensorAttributes(maxValue, minValue, mValue, rExp, bValue,
+ bExp, bSigned);
+ EXPECT_EQ(result, true);
+
+ if (result)
+ {
+ EXPECT_EQ(bSigned, true);
+ EXPECT_EQ(mValue, 1);
+ EXPECT_EQ(rExp, 0);
+ EXPECT_EQ(bValue, 0);
+ EXPECT_EQ(bExp, 0);
+ }
+
+ // check negative values
+ expected = 236; // 2s compliment -20
+ scaledVal = ipmi::scaleIPMIValueFromDouble(-20, mValue, rExp, bValue, bExp,
+ bSigned);
+ EXPECT_NEAR(scaledVal, expected, expected * 0.01);
+
+ // fan example
+ maxValue = 16000;
+ minValue = 0;
+
+ result = ipmi::getSensorAttributes(maxValue, minValue, mValue, rExp, bValue,
+ bExp, bSigned);
+ EXPECT_EQ(result, true);
+ if (result)
+ {
+ EXPECT_EQ(bSigned, false);
+ EXPECT_EQ(mValue, floor((16000.0 / 0xFF) + 0.5));
+ EXPECT_EQ(rExp, 0);
+ EXPECT_EQ(bValue, 0);
+ EXPECT_EQ(bExp, 0);
+ }
+
+ // voltage sensor example
+ maxValue = 20;
+ minValue = 0;
+
+ result = ipmi::getSensorAttributes(maxValue, minValue, mValue, rExp, bValue,
+ bExp, bSigned);
+ EXPECT_EQ(result, true);
+ if (result)
+ {
+ EXPECT_EQ(bSigned, false);
+ EXPECT_EQ(mValue, floor(((20.0 / 0xFF) / std::pow(10, rExp)) + 0.5));
+ EXPECT_EQ(rExp, -3);
+ EXPECT_EQ(bValue, 0);
+ EXPECT_EQ(bExp, 0);
+ }
+ scaledVal = ipmi::scaleIPMIValueFromDouble(12.2, mValue, rExp, bValue, bExp,
+ bSigned);
+
+ expected = 12.2 / (mValue * std::pow(10, rExp));
+ EXPECT_NEAR(scaledVal, expected, expected * 0.01);
+
+ // shifted fan example
+ maxValue = 16000;
+ minValue = 8000;
+
+ result = ipmi::getSensorAttributes(maxValue, minValue, mValue, rExp, bValue,
+ bExp, bSigned);
+ EXPECT_EQ(result, true);
+
+ if (result)
+ {
+ EXPECT_EQ(bSigned, false);
+ EXPECT_EQ(mValue, floor(((8000.0 / 0xFF) / std::pow(10, rExp)) + 0.5));
+ EXPECT_EQ(rExp, -1);
+ EXPECT_EQ(bValue, 8);
+ EXPECT_EQ(bExp, 4);
+ }
+
+ // signed voltage sensor example
+ maxValue = 10;
+ minValue = -10;
+
+ result = ipmi::getSensorAttributes(maxValue, minValue, mValue, rExp, bValue,
+ bExp, bSigned);
+ EXPECT_EQ(result, true);
+ if (result)
+ {
+ EXPECT_EQ(bSigned, true);
+ EXPECT_EQ(mValue, floor(((20.0 / 0xFF) / std::pow(10, rExp)) + 0.5));
+ EXPECT_EQ(rExp, -3);
+ // Although this seems like a weird magic number,
+ // it is because the range (-128,127) is not symmetrical about zero,
+ // unlike the range (-10,10), so this introduces some distortion.
+ EXPECT_EQ(bValue, 392);
+ EXPECT_EQ(bExp, -1);
+ }
+
+ scaledVal =
+ ipmi::scaleIPMIValueFromDouble(5, mValue, rExp, bValue, bExp, bSigned);
+
+ expected = 5 / (mValue * std::pow(10, rExp));
+ EXPECT_NEAR(scaledVal, expected, expected * 0.01);
+
+ // reading = max example
+ maxValue = 277;
+ minValue = 0;
+
+ result = ipmi::getSensorAttributes(maxValue, minValue, mValue, rExp, bValue,
+ bExp, bSigned);
+ EXPECT_EQ(result, true);
+ if (result)
+ {
+ EXPECT_EQ(bSigned, false);
+ }
+
+ scaledVal = ipmi::scaleIPMIValueFromDouble(maxValue, mValue, rExp, bValue,
+ bExp, bSigned);
+
+ expected = 0xFF;
+ EXPECT_NEAR(scaledVal, expected, expected * 0.01);
+
+ // 0, 0 failure
+ maxValue = 0;
+ minValue = 0;
+ result = ipmi::getSensorAttributes(maxValue, minValue, mValue, rExp, bValue,
+ bExp, bSigned);
+ EXPECT_EQ(result, false);
+
+ // too close *success* (was previously failure!)
+ maxValue = 12;
+ minValue = 10;
+ result = ipmi::getSensorAttributes(maxValue, minValue, mValue, rExp, bValue,
+ bExp, bSigned);
+ EXPECT_EQ(result, true);
+ if (result)
+ {
+ EXPECT_EQ(bSigned, false);
+ EXPECT_EQ(mValue, floor(((2.0 / 0xFF) / std::pow(10, rExp)) + 0.5));
+ EXPECT_EQ(rExp, -4);
+ EXPECT_EQ(bValue, 1);
+ EXPECT_EQ(bExp, 5);
+ }
+}
+
+TEST(sensorUtils, TestRanges)
+{
+ // Additional test ranges, each running through a series of values,
+ // to make sure the values of "x" and "y" go together and make sense,
+ // for the resulting scaling attributes from each range.
+ // Unlike the TranslateToIPMI test, exact matches of the
+ // getSensorAttributes() results (the coefficients) are not required,
+ // because they are tested through actual use, relating "x" to "y".
+ testRanges();
+}