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/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();
+}