ipmbsensor: add support for an Ampere SMPro on an IPMB

On ADLINK systems, the SMPro can be reached via the MMC (Module
Management Controller), which is a second BMC. It sits on an IPMB
bus.

Add support for reading power, voltage, current and temperature values
from the SMPro.

Tested: built ipmbsensor and ran new ipmb unit tests.

Change-Id: Ib9862486a18f77fb58d3acd59de7686750029b56
Signed-off-by: Rebecca Cran <rebecca@bsdio.com>
diff --git a/src/IpmbSensor.cpp b/src/IpmbSensor.cpp
index 30531bf..fbc4b7d 100644
--- a/src/IpmbSensor.cpp
+++ b/src/IpmbSensor.cpp
@@ -249,6 +249,33 @@
                     0x02,          0x00, 0x00, 0x00};
         readingFormat = ReadingFormat::byte3;
     }
+    else if (type == IpmbType::SMPro)
+    {
+        // This is an Ampere SMPro reachable via a BMC.  For example,
+        // this architecture is used on ADLINK Ampere Altra systems.
+        // See the Ampere Family SoC BMC Interface Specification at
+        // https://amperecomputing.com/customer-connect/products/altra-family-software---firmware
+        // for details of the sensors.
+        commandAddress = 0;
+        netfn = 0x30;
+        command = 0x31;
+        commandData = {0x9e, deviceAddress};
+        switch (subType)
+        {
+            case IpmbSubType::temp:
+                readingFormat = ReadingFormat::nineBit;
+                break;
+            case IpmbSubType::power:
+                readingFormat = ReadingFormat::tenBit;
+                break;
+            case IpmbSubType::curr:
+            case IpmbSubType::volt:
+                readingFormat = ReadingFormat::fifteenBit;
+                break;
+            default:
+                throw std::runtime_error("Invalid sensor type");
+        }
+    }
     else
     {
         throw std::runtime_error("Invalid sensor type");
@@ -296,6 +323,51 @@
             resp = data[3];
             return true;
         }
+        case (ReadingFormat::nineBit):
+        case (ReadingFormat::tenBit):
+        case (ReadingFormat::fifteenBit):
+        {
+            if (data.size() != 2)
+            {
+                if (errCount == 0U)
+                {
+                    std::cerr << "Invalid data length returned\n";
+                }
+                return false;
+            }
+
+            // From the Altra Family SoC BMC Interface Specification:
+            // 0xFFFF – This sensor data is either missing or is not supported
+            // by the device.
+            if ((data[0] == 0xff) && (data[1] == 0xff))
+            {
+                return false;
+            }
+
+            if (readingFormat == ReadingFormat::nineBit)
+            {
+                int16_t value = data[0];
+                if ((data[1] & 0x1) != 0)
+                {
+                    // Sign extend to 16 bits
+                    value |= 0xFF00;
+                }
+                resp = value;
+            }
+            else if (readingFormat == ReadingFormat::tenBit)
+            {
+                uint16_t value = ((data[1] & 0x3) << 8) + data[0];
+                resp = value;
+            }
+            else if (readingFormat == ReadingFormat::fifteenBit)
+            {
+                uint16_t value = ((data[1] & 0x7F) << 8) + data[0];
+                // Convert mV to V
+                resp = value / 1000.0;
+            }
+
+            return true;
+        }
         case (ReadingFormat::elevenBit):
         {
             if (data.size() < 5)
@@ -464,6 +536,10 @@
     {
         type = IpmbType::meSensor;
     }
+    else if (sensorClass == "SMPro")
+    {
+        type = IpmbType::SMPro;
+    }
     else
     {
         std::cerr << "Invalid class " << sensorClass << "\n";
diff --git a/src/IpmbSensor.hpp b/src/IpmbSensor.hpp
index d22e36d..953e44a 100644
--- a/src/IpmbSensor.hpp
+++ b/src/IpmbSensor.hpp
@@ -20,7 +20,8 @@
     PXE1410CVR,
     IR38363VR,
     ADM1278HSC,
-    mpsVR
+    mpsVR,
+    SMPro
 };
 
 enum class IpmbSubType
@@ -37,9 +38,12 @@
 {
     byte0,
     byte3,
+    nineBit,
+    tenBit,
     elevenBit,
     elevenBitShift,
-    linearElevenBit
+    linearElevenBit,
+    fifteenBit
 };
 
 namespace ipmi
diff --git a/tests/test_IpmbSensor.cpp b/tests/test_IpmbSensor.cpp
index 3168993..5ba7521 100644
--- a/tests/test_IpmbSensor.cpp
+++ b/tests/test_IpmbSensor.cpp
@@ -20,4 +20,272 @@
                                            responseValue, errCount));
     EXPECT_EQ(responseValue, 42.0);
 }
+
+TEST(IPMBSensor, NineBitValidPositive)
+{
+    std::vector<uint8_t> data;
+    data.push_back(0x2a);
+    data.push_back(0x00);
+
+    double responseValue = 0.0;
+    size_t errCount = 0;
+    EXPECT_TRUE(IpmbSensor::processReading(ReadingFormat::nineBit, 0, data,
+                                           responseValue, errCount));
+    EXPECT_EQ(responseValue, 42.0);
+    EXPECT_EQ(errCount, 0);
+}
+
+TEST(IPMBSensor, NineBitValidNegative)
+{
+    std::vector<uint8_t> data;
+    data.push_back(0x9c);
+    data.push_back(0x01);
+
+    double responseValue = 0.0;
+    size_t errCount = 0;
+    EXPECT_TRUE(IpmbSensor::processReading(ReadingFormat::nineBit, 0, data,
+                                           responseValue, errCount));
+    EXPECT_EQ(responseValue, -100.0);
+    EXPECT_EQ(errCount, 0);
+}
+
+TEST(IPMBSensor, NineBitMin)
+{
+    std::vector<uint8_t> data;
+    data.push_back(0x01);
+    data.push_back(0x01);
+
+    double responseValue = 0.0;
+    size_t errCount = 0;
+    EXPECT_TRUE(IpmbSensor::processReading(ReadingFormat::nineBit, 0, data,
+                                           responseValue, errCount));
+    EXPECT_EQ(responseValue, -255.0);
+    EXPECT_EQ(errCount, 0);
+}
+
+// The Altra Family SoC BMC Interface Specification says the maximum 9-bit value
+// is 256, but that can't be represented in 9 bits, so test the max as 255.
+TEST(IPMBSensor, NineBitMax)
+{
+    std::vector<uint8_t> data;
+    data.push_back(0xff);
+    data.push_back(0x00);
+
+    double responseValue = 0.0;
+    size_t errCount = 0;
+    EXPECT_TRUE(IpmbSensor::processReading(ReadingFormat::nineBit, 0, data,
+                                           responseValue, errCount));
+    EXPECT_EQ(responseValue, 255.0);
+    EXPECT_EQ(errCount, 0);
+}
+
+TEST(IPMBSensor, NineBitTooShort)
+{
+    std::vector<uint8_t> data;
+    data.push_back(0x00);
+
+    double responseValue = 0.0;
+    size_t errCount = 0;
+    EXPECT_FALSE(IpmbSensor::processReading(ReadingFormat::nineBit, 0, data,
+                                            responseValue, errCount));
+}
+
+TEST(IPMBSensor, NineBitTooLong)
+{
+    std::vector<uint8_t> data;
+    data.push_back(0x00);
+    data.push_back(0x00);
+    data.push_back(0x00);
+
+    double responseValue = 0.0;
+    size_t errCount = 0;
+    EXPECT_FALSE(IpmbSensor::processReading(ReadingFormat::nineBit, 0, data,
+                                            responseValue, errCount));
+}
+
+TEST(IPMBSensor, NineBitInvalid)
+{
+    std::vector<uint8_t> data;
+    data.push_back(0xff);
+    data.push_back(0xff);
+
+    double responseValue = 0.0;
+    size_t errCount = 0;
+    EXPECT_FALSE(IpmbSensor::processReading(ReadingFormat::nineBit, 0, data,
+                                            responseValue, errCount));
+}
+
+TEST(IPMBSensor, TenBitValid1)
+{
+    std::vector<uint8_t> data;
+    data.push_back(0x08);
+    data.push_back(0x00);
+
+    double responseValue = 0.0;
+    size_t errCount = 0;
+    EXPECT_TRUE(IpmbSensor::processReading(ReadingFormat::tenBit, 0, data,
+                                           responseValue, errCount));
+    EXPECT_EQ(responseValue, 8.0);
+    EXPECT_EQ(errCount, 0);
+}
+
+TEST(IPMBSensor, TenBitValid2)
+{
+    std::vector<uint8_t> data;
+    data.push_back(0x30);
+    data.push_back(0x02);
+
+    double responseValue = 0.0;
+    size_t errCount = 0;
+    EXPECT_TRUE(IpmbSensor::processReading(ReadingFormat::tenBit, 0, data,
+                                           responseValue, errCount));
+
+    EXPECT_EQ(responseValue, 560.0);
+    EXPECT_EQ(errCount, 0);
+}
+
+TEST(IPMBSensor, TenBitMin)
+{
+    std::vector<uint8_t> data;
+    data.push_back(0x00);
+    data.push_back(0x00);
+
+    double responseValue = 0.0;
+    size_t errCount = 0;
+    EXPECT_TRUE(IpmbSensor::processReading(ReadingFormat::tenBit, 0, data,
+                                           responseValue, errCount));
+
+    EXPECT_EQ(responseValue, 0.0);
+    EXPECT_EQ(errCount, 0);
+}
+
+TEST(IPMBSensor, TenBitValidMax)
+{
+    std::vector<uint8_t> data;
+    data.push_back(0xff);
+    data.push_back(0x03);
+
+    double responseValue = 0.0;
+    size_t errCount = 0;
+    EXPECT_TRUE(IpmbSensor::processReading(ReadingFormat::tenBit, 0, data,
+                                           responseValue, errCount));
+
+    EXPECT_EQ(responseValue, 1023.0);
+    EXPECT_EQ(errCount, 0);
+}
+
+TEST(IPMBSensor, TenBitTooShort)
+{
+    std::vector<uint8_t> data;
+    data.push_back(0xff);
+
+    double responseValue = 0.0;
+    size_t errCount = 0;
+    EXPECT_FALSE(IpmbSensor::processReading(ReadingFormat::tenBit, 0, data,
+                                            responseValue, errCount));
+}
+
+TEST(IPMBSensor, TenBitTooLong)
+{
+    std::vector<uint8_t> data;
+    data.push_back(0x00);
+    data.push_back(0x00);
+    data.push_back(0x00);
+
+    double responseValue = 0.0;
+    size_t errCount = 0;
+    EXPECT_FALSE(IpmbSensor::processReading(ReadingFormat::tenBit, 0, data,
+                                            responseValue, errCount));
+}
+
+TEST(IPMBSensor, TenBitInvalid)
+{
+    std::vector<uint8_t> data;
+    data.push_back(0xff);
+    data.push_back(0xff);
+
+    double responseValue = 0.0;
+    size_t errCount = 0;
+    EXPECT_FALSE(IpmbSensor::processReading(ReadingFormat::tenBit, 0, data,
+                                            responseValue, errCount));
+}
+
+TEST(IPMBSensor, FifteenBitValid1)
+{
+    std::vector<uint8_t> data;
+    data.push_back(0xda);
+    data.push_back(0x02);
+
+    double responseValue = 0.0;
+    size_t errCount = 0;
+    EXPECT_TRUE(IpmbSensor::processReading(ReadingFormat::fifteenBit, 0, data,
+                                           responseValue, errCount));
+    EXPECT_EQ(responseValue, 0.730);
+    EXPECT_EQ(errCount, 0);
+}
+
+TEST(IPMBSensor, FifteenBitMin)
+{
+    std::vector<uint8_t> data;
+    data.push_back(0x00);
+    data.push_back(0x00);
+
+    double responseValue = 0.0;
+    size_t errCount = 0;
+    EXPECT_TRUE(IpmbSensor::processReading(ReadingFormat::fifteenBit, 0, data,
+                                           responseValue, errCount));
+    EXPECT_EQ(responseValue, 0.0);
+    EXPECT_EQ(errCount, 0);
+}
+
+TEST(IPMBSensor, FifteenBitMax)
+{
+    std::vector<uint8_t> data;
+    data.push_back(0xff);
+    data.push_back(0x7f);
+
+    double responseValue = 0.0;
+    size_t errCount = 0;
+    EXPECT_TRUE(IpmbSensor::processReading(ReadingFormat::fifteenBit, 0, data,
+                                           responseValue, errCount));
+    EXPECT_EQ(responseValue, 32.767);
+    EXPECT_EQ(errCount, 0);
+}
+
+TEST(IPMBSensor, FifteenBitTooShort)
+{
+    std::vector<uint8_t> data;
+    data.push_back(0xff);
+
+    double responseValue = 0.0;
+    size_t errCount = 0;
+    EXPECT_FALSE(IpmbSensor::processReading(ReadingFormat::fifteenBit, 0, data,
+                                            responseValue, errCount));
+}
+
+TEST(IPMBSensor, FifteenBitTooLong)
+{
+    std::vector<uint8_t> data;
+    data.push_back(0x00);
+    data.push_back(0x00);
+    data.push_back(0x00);
+
+    double responseValue = 0.0;
+    size_t errCount = 0;
+    EXPECT_FALSE(IpmbSensor::processReading(ReadingFormat::fifteenBit, 0, data,
+                                            responseValue, errCount));
+}
+
+TEST(IPMBSensor, FifteenBitInvalid)
+{
+    std::vector<uint8_t> data;
+    data.push_back(0xff);
+    data.push_back(0xff);
+
+    double responseValue = 0.0;
+    size_t errCount = 0;
+    EXPECT_FALSE(IpmbSensor::processReading(ReadingFormat::fifteenBit, 0, data,
+                                            responseValue, errCount));
+}
+
 } // namespace