Expose memory error correction type

Memory error correction types are provided by smbios table type-16.
This change use that table to populate 'ECC' field in
xyz.openbmc_project.Inventory.Item.Dimm interface.

Tested:
Tested this on a real machine.

Signed-off-by: Kasun Athukorala <kasunath@google.com>
Change-Id: Iaeb27f81d9b97be1858d1c4564318f5dc69cfa4f
diff --git a/include/dimm.hpp b/include/dimm.hpp
index 1d1fc18..709bfdf 100644
--- a/include/dimm.hpp
+++ b/include/dimm.hpp
@@ -34,6 +34,9 @@
 using DeviceType =
     sdbusplus::xyz::openbmc_project::Inventory::Item::server::Dimm::DeviceType;
 
+using EccType =
+    sdbusplus::xyz::openbmc_project::Inventory::Item::server::Dimm::Ecc;
+
 class Dimm :
     sdbusplus::server::object_t<
         sdbusplus::xyz::openbmc_project::Inventory::Item::server::Dimm>,
@@ -105,6 +108,7 @@
     uint8_t memoryAttributes(uint8_t value) override;
     uint16_t memoryConfiguredSpeedInMhz(uint16_t value) override;
     bool functional(bool value) override;
+    EccType ecc(EccType value) override;
 
   private:
     uint8_t dimmNum;
@@ -125,6 +129,7 @@
                        uint8_t* dataIn);
     void dimmPartNum(const uint8_t positionNum, const uint8_t structLen,
                      uint8_t* dataIn);
+    void updateEccType(uint16_t exPhyArrayHandle);
 };
 
 struct MemoryInfo
@@ -167,6 +172,25 @@
     uint64_t logicalSize;
 } __attribute__((packed));
 
+/**
+ * @brief Struct to represent SMBIOS 3.2 type-16 (Physical Memory Array) data.
+ */
+struct PhysicalMemoryArrayInfo
+{
+    uint8_t type;
+    uint8_t length;
+    uint16_t handle;
+    uint8_t location;
+    uint8_t use;
+    uint8_t memoryErrorCorrection;
+    uint32_t maximumCapacity;
+    uint16_t memoryErrorInformationHandle;
+    uint16_t numberOfMemoryDevices;
+    uint64_t extendedMaximumCapacity;
+} __attribute__((packed));
+static_assert(sizeof(PhysicalMemoryArrayInfo) == 23,
+              "Size of PhysicalMemoryArrayInfo struct is incorrect.");
+
 const std::map<uint8_t, DeviceType> dimmTypeTable = {
     {0x1, DeviceType::Other},         {0x2, DeviceType::Unknown},
     {0x3, DeviceType::DRAM},          {0x4, DeviceType::EDRAM},
@@ -191,6 +215,20 @@
     "CMOS",          "EDO",           "Window DRAM", "Cache DRAM",
     "Non-volatile",  "Registered",    "Unbuffered",  "LRDIMM"};
 
+/**
+ * @brief Map SMBIOS 3.2 Memory Array Error Correction Types to
+ * xyz.openbmc_project.Inventory.Item.Dimm.Ecc types.
+ *
+ * SMBIOS 3.2 Memory Array Error Correction Types 'Unknown', 'None', 'CRC' are
+ * mapped to EccType::NoECC since the DBUs interface does not support those
+ * representations.
+ */
+const std::map<uint8_t, EccType> dimmEccTypeMap = {
+    {0x1, EccType::NoECC},        {0x2, EccType::NoECC},
+    {0x3, EccType::NoECC},        {0x4, EccType::AddressParity},
+    {0x5, EccType::SingleBitECC}, {0x6, EccType::MultiBitECC},
+    {0x7, EccType::NoECC}};
+
 } // namespace smbios
 
 } // namespace phosphor
diff --git a/src/dimm.cpp b/src/dimm.cpp
index 1177e66..990b310 100644
--- a/src/dimm.cpp
+++ b/src/dimm.cpp
@@ -19,6 +19,7 @@
 #include "mdrv2.hpp"
 
 #include <boost/algorithm/string.hpp>
+#include <phosphor-logging/elog-errors.hpp>
 
 namespace phosphor
 {
@@ -28,6 +29,9 @@
 using DeviceType =
     sdbusplus::xyz::openbmc_project::Inventory::Item::server::Dimm::DeviceType;
 
+using EccType =
+    sdbusplus::xyz::openbmc_project::Inventory::Item::server::Dimm::Ecc;
+
 static constexpr uint16_t maxOldDimmSize = 0x7fff;
 void Dimm::memoryInfoUpdate(void)
 {
@@ -76,6 +80,8 @@
     memoryAttributes(memoryInfo->attributes);
     memoryConfiguredSpeedInMhz(memoryInfo->confClockSpeed);
 
+    updateEccType(memoryInfo->phyArrayHandle);
+
     if (!motherboardPath.empty())
     {
         std::vector<std::tuple<std::string, std::string, std::string>> assocs;
@@ -86,6 +92,49 @@
     return;
 }
 
+void Dimm::updateEccType(uint16_t exPhyArrayHandle)
+{
+    uint8_t* dataIn = storage;
+
+    while (dataIn != nullptr)
+    {
+        dataIn = getSMBIOSTypePtr(dataIn, physicalMemoryArrayType);
+        if (dataIn == nullptr)
+        {
+            phosphor::logging::log<phosphor::logging::level::ERR>(
+                "Failed to get SMBIOS table type-16 data.");
+            return;
+        }
+
+        auto info = reinterpret_cast<struct PhysicalMemoryArrayInfo*>(dataIn);
+        if (info->handle == exPhyArrayHandle)
+        {
+            std::map<uint8_t, EccType>::const_iterator it =
+                dimmEccTypeMap.find(info->memoryErrorCorrection);
+            if (it == dimmEccTypeMap.end())
+            {
+                ecc(EccType::NoECC);
+            }
+            else
+            {
+                ecc(it->second);
+            }
+            return;
+        }
+
+        dataIn = smbiosNextPtr(dataIn);
+    }
+    phosphor::logging::log<phosphor::logging::level::ERR>(
+        "Failed find the corresponding SMBIOS table type-16 data for dimm:",
+        phosphor::logging::entry("DIMM:%d", dimmNum));
+}
+
+EccType Dimm::ecc(EccType value)
+{
+    return sdbusplus::xyz::openbmc_project::Inventory::Item::server::Dimm::ecc(
+        value);
+}
+
 uint16_t Dimm::memoryDataWidth(uint16_t value)
 {
     return sdbusplus::xyz::openbmc_project::Inventory::Item::server::Dimm::