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";