pfr-manager: Add support to read and update CPLD version

Add support to
1. Read and update mainPLDversion and PLDversion from CPLD SGPIO lines.
2. Read and update SVN, RoT and CPLD hash when PFR CPLD is present.

CPLD version format:
When PFR CPLD is present:
MainPLDMajorMinor.PLDMajorMinor>-<SVN.RoT>-<CPLD-Hash>
 Example: 2.7-1.1-<Hash string>

When Non-PFR CPLD is present: <MainPLDMajorMinor.PLDMajorMinor>
 Example: 2.7

Tested:
1. When PFR-CPLD is present.
Command:
busctl introspect xyz.openbmc_project.PFR.Manager /xyz/openbmc_project/
software/cpld_active
Response:
NAME                                    TYPE      SIGNATURE RESULT/VALUE
org.freedesktop.DBus.Introspectable     interface -         -
.Introspect                             method    -         s
org.freedesktop.DBus.Peer               interface -         -
.GetMachineId                           method    -         s
.Ping                                   method    -         -
org.freedesktop.DBus.Properties         interface -         -
.Get                                    method    ss        v
.GetAll                                 method    s         a{sv}
.Set                                    method    ssv       -
.PropertiesChanged                      signal    sa{sv}as  -
xyz.openbmc_project.Software.Activation interface -         -
.Activation                             property  s
"xyz.openbmc_project.Software.Activat... emits-change
.RequestedActivation                    property  s
"xyz.openbmc_project.Software.Activat... emits-change
xyz.openbmc_project.Software.Version    interface -         -                                        -
.Purpose                                property  s
"xyz.openbmc_project.Software.Version... emits-change
.Version                                property  s
"0.8-1.0-e51876c89bec2ebae8f7ca75dfc339… emits-change writable

Command:
busctl call xyz.openbmc_project.PFR.Manager /xyz/openbmc_project/
software/cpld_active org.freedesktop.DBus.Properties Get ss xyz.
openbmc_project.Software.Version Version

Response: v s "0.8-1.0-e51876c89bec2ebae8f7ca75dfc339099e138d5f4f6b
               895a45671c5489d7ba52"

Command:
GET: https://<BMC_IP>/redfish/v1/UpdateService/FirmwareInventory/
     cpld_active
Response:
{
  "@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/
               cpld_active",
  "@odata.type": "#SoftwareInventory.v1_1_0.SoftwareInventory",
  "Description": "Other image",
  "Id": "cpld_active",
  "Name": "Software Inventory",
  "Status": {
    "Health": "OK",
    "HealthRollup": "OK",
    "State": "Enabled"
  },
  "Updateable": true,
  "Version": "0.8-1.0-e51876c89bec2ebae8f7ca75dfc339099e138d5f4f6b
              895a45671c5489d7ba52"
}

2. when non-PFR CPLD is present.

Command:
busctl introspect xyz.openbmc_project.PFR.Manager /xyz/openbmc_project/
software/cpld_active
Response:
NAME                                    TYPE      SIGNATURE RESULT/VALUE
org.freedesktop.DBus.Introspectable     interface -         -
.Introspect                             method    -         s
org.freedesktop.DBus.Peer               interface -         -
.GetMachineId                           method    -         s
.Ping                                   method    -         -
org.freedesktop.DBus.Properties         interface -         -
.Get                                    method    ss        v
.GetAll                                 method    s         a{sv}
.Set                                    method    ssv       -
.PropertiesChanged                      signal    sa{sv}as  -
xyz.openbmc_project.Software.Activation interface -         -
.Activation                             property  s
"xyz.openbmc_project.Software.Activat... emits-change
.RequestedActivation                    property  s
"xyz.openbmc_project.Software.Activat... emits-change
xyz.openbmc_project.Software.Version    interface -         -                                        -
.Purpose                                property  s
"xyz.openbmc_project.Software.Version... emits-change
.Version                                property  s         "2.5"
emits-change writable

Command:
busctl call xyz.openbmc_project.PFR.Manager /xyz/openbmc_project/
software/cpld_active org.freedesktop.DBus.Properties Get ss xyz.
openbmc_project.Software.Version Version

Response: v s "2.5"

Command:
GET: https://<BMC_IP>/redfish/v1/UpdateService/FirmwareInventory/
     cpld_active
Response:
{
  "@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/
               cpld_active",
  "@odata.type": "#SoftwareInventory.v1_1_0.SoftwareInventory",
  "Description": "Other image",
  "Id": "cpld_active",
  "Name": "Software Inventory",
  "Status": {
    "Health": "OK",
    "HealthRollup": "OK",
    "State": "Enabled"
  },
  "Updateable": true,
  "Version": "2.5"
}

Signed-off-by: Chalapathi Venkataramashetty <chalapathix.venkataramashetty@intel.com>
Change-Id: Ib674f650b8e02fcbf1ca64c0113a23963cd00536
diff --git a/libpfr/inc/file.hpp b/libpfr/inc/file.hpp
index e817e7c..3f48666 100644
--- a/libpfr/inc/file.hpp
+++ b/libpfr/inc/file.hpp
@@ -91,6 +91,26 @@
         return value;
     }
 
+    /** @brief Reads the block of data from I2C dev
+     *
+     *  @param[in] Offset       -  Offset value
+     *  @param[in] length       -  length value
+     *  @param[out] value       -  data pointer
+     *  @param[out] bool        -  true or false
+     */
+    bool i2cReadBlockData(const uint8_t& offset, uint8_t length, uint8_t* value)
+    {
+        int ret;
+        ret = i2c_smbus_read_i2c_block_data(fd, offset, length, value);
+
+        if (ret < 0)
+        {
+            throw std::runtime_error("i2c_smbus_read_i2c_block_data() failed");
+            return false;
+        }
+        return true;
+    }
+
     /** @brief Writes the byte data to I2C dev
      *
      *  @param[in] Offset       -  Offset value
diff --git a/libpfr/src/pfr.cpp b/libpfr/src/pfr.cpp
index 0cfbfbf..c95a694 100644
--- a/libpfr/src/pfr.cpp
+++ b/libpfr/src/pfr.cpp
@@ -19,6 +19,8 @@
 #include "file.hpp"
 #include "spiDev.hpp"
 
+#include <gpiod.hpp>
+
 #include <iomanip>
 #include <iostream>
 #include <sstream>
@@ -32,6 +34,7 @@
 static constexpr int i2cSlaveAddress = 0x38;
 
 // CPLD mailbox registers
+static constexpr uint8_t pfrROTId = 0x00;
 static constexpr uint8_t cpldROTVersion = 0x01;
 static constexpr uint8_t cpldROTSvn = 0x02;
 static constexpr uint8_t platformState = 0x03;
@@ -47,6 +50,8 @@
 static constexpr uint8_t pchActiveMinorVersion = 0x16;
 static constexpr uint8_t pchRecoveryMajorVersion = 0x1B;
 static constexpr uint8_t pchRecoveryMinorVersion = 0x1C;
+static constexpr uint8_t CPLDHashRegStart = 0x20;
+static constexpr uint8_t pfrRoTValue = 0xDE;
 
 static constexpr uint8_t ufmLockedMask = (0x1 << 0x04);
 static constexpr uint8_t ufmProvisionedMask = (0x1 << 0x05);
@@ -63,6 +68,18 @@
 static constexpr const uint32_t buildNumOffsetInPFM = 0x40C;
 static constexpr const uint32_t buildHashOffsetInPFM = 0x40D;
 
+static const std::array<std::string, 8> mainCPLDGpioLines = {
+    "MAIN_PLD_MAJOR_REV_BIT3", "MAIN_PLD_MAJOR_REV_BIT2",
+    "MAIN_PLD_MAJOR_REV_BIT1", "MAIN_PLD_MAJOR_REV_BIT0",
+    "MAIN_PLD_MINOR_REV_BIT3", "MAIN_PLD_MINOR_REV_BIT2",
+    "MAIN_PLD_MINOR_REV_BIT1", "MAIN_PLD_MINOR_REV_BIT0"};
+
+static const std::array<std::string, 8> pldGpioLines = {
+    "SGPIO_PLD_MAJOR_REV_BIT3", "SGPIO_PLD_MAJOR_REV_BIT2",
+    "SGPIO_PLD_MAJOR_REV_BIT1", "SGPIO_PLD_MAJOR_REV_BIT0",
+    "SGPIO_PLD_MINOR_REV_BIT3", "SGPIO_PLD_MINOR_REV_BIT2",
+    "SGPIO_PLD_MINOR_REV_BIT1", "SGPIO_PLD_MINOR_REV_BIT0"};
+
 std::string toHexString(const uint8_t val)
 {
     std::stringstream stream;
@@ -71,6 +88,41 @@
     return stream.str();
 }
 
+static std::string readCPLDHash()
+{
+    std::stringstream hashStrStream;
+    static constexpr uint8_t hashLength = 32;
+    std::array<uint8_t, hashLength> hashValue = {0};
+    try
+    {
+        I2CFile cpldDev(i2cBusNumber, i2cSlaveAddress, O_RDWR | O_CLOEXEC);
+        if (cpldDev.i2cReadBlockData(CPLDHashRegStart, hashLength,
+                                     hashValue.data()))
+        {
+            for (const auto& i : hashValue)
+            {
+                hashStrStream << std::setfill('0') << std::setw(2) << std::hex
+                              << static_cast<int>(i);
+            }
+        }
+        else
+        {
+            // read failed
+            phosphor::logging::log<phosphor::logging::level::ERR>(
+                "Failed to read CPLD Hash string");
+            return "";
+        }
+    }
+    catch (const std::exception& e)
+    {
+        phosphor::logging::log<phosphor::logging::level::ERR>(
+            "Exception caught in readCPLDHash.",
+            phosphor::logging::entry("MSG=%s", e.what()));
+        return "";
+    }
+    return hashStrStream.str();
+}
+
 static std::string readVersionFromCPLD(const uint8_t majorReg,
                                        const uint8_t minorReg)
 {
@@ -153,11 +205,135 @@
     return version;
 }
 
+static bool getGPIOInput(const std::string& name, gpiod::line& gpioLine,
+                         uint8_t* value)
+{
+    // Find the GPIO line
+    gpioLine = gpiod::find_line(name);
+    if (!gpioLine)
+    {
+        phosphor::logging::log<phosphor::logging::level::ERR>(
+            "Failed to find the GPIO line: ",
+            phosphor::logging::entry("MSG=%s", name.c_str()));
+        return false;
+    }
+    try
+    {
+        gpioLine.request({__FUNCTION__, gpiod::line_request::DIRECTION_INPUT});
+    }
+    catch (std::exception& e)
+    {
+        phosphor::logging::log<phosphor::logging::level::ERR>(
+            "Failed to request the GPIO line",
+            phosphor::logging::entry("MSG=%s", e.what()));
+        gpioLine.release();
+        return false;
+    }
+    try
+    {
+        *value = gpioLine.get_value();
+    }
+    catch (std::exception& e)
+    {
+        phosphor::logging::log<phosphor::logging::level::ERR>(
+            "Failed to get the value of GPIO line",
+            phosphor::logging::entry("MSG=%s", e.what()));
+        gpioLine.release();
+        return false;
+    }
+    gpioLine.release();
+    return true;
+}
+
+static std::string readCPLDVersion()
+{
+    // CPLD SGPIO lines
+    gpiod::line mainCPLDLine;
+    gpiod::line pldLine;
+    // read main pld and pld version
+    uint8_t mainCPLDVer = 0;
+    uint8_t pldVer = 0;
+    // main CPLD
+    for (const auto& gLine : mainCPLDGpioLines)
+    {
+        uint8_t value = 0;
+        if (getGPIOInput(gLine, mainCPLDLine, &value))
+        {
+            mainCPLDVer <<= 1;
+            mainCPLDVer = mainCPLDVer | value;
+        }
+        else
+        {
+            phosphor::logging::log<phosphor::logging::level::ERR>(
+                "Failed to read GPIO line: ",
+                phosphor::logging::entry("MSG=%s", gLine.c_str()));
+            mainCPLDVer = 0;
+            break;
+        }
+    }
+
+    // pld lines
+    for (const auto& gLine : pldGpioLines)
+    {
+        uint8_t value = 0;
+        if (getGPIOInput(gLine, pldLine, &value))
+        {
+            pldVer <<= 1;
+            pldVer = pldVer | value;
+        }
+        else
+        {
+            phosphor::logging::log<phosphor::logging::level::ERR>(
+                "Failed to read GPIO line: ",
+                phosphor::logging::entry("MSG=%s", gLine.c_str()));
+            pldVer = 0;
+            break;
+        }
+    }
+
+    std::string svnRoTHash = "";
+
+    // check if reg 0x00 read 0xde
+    uint8_t cpldRoTValue = 0;
+    I2CFile cpldDev(i2cBusNumber, i2cSlaveAddress, O_RDWR | O_CLOEXEC);
+    cpldRoTValue = cpldDev.i2cReadByteData(pfrROTId);
+
+    if (cpldRoTValue == pfrRoTValue)
+    {
+        // read SVN and RoT version
+        std::string svnRoTver = readVersionFromCPLD(cpldROTVersion, cpldROTSvn);
+
+        // read CPLD hash
+        std::string cpldHash = readCPLDHash();
+        svnRoTHash = "-" + svnRoTver + "-" + cpldHash;
+    }
+    else
+    {
+        phosphor::logging::log<phosphor::logging::level::INFO>(
+            "PFR-CPLD not present.");
+    }
+
+    // CPLD version format:
+    // When PFR CPLD is present
+    // <MainPLDMajorMinor.PLDMajorMinor>-<SVN.RoT>-<CPLD-Hash>
+    // Example: 2.7-1.1-<Hash string>
+
+    // When Non-PFR CPLD is present -> <MainPLDMajorMinor.PLDMajorMinor>
+    // Example: 2.7
+
+    std::string version =
+        std::to_string(mainCPLDVer) + "." + std::to_string(pldVer) + svnRoTHash;
+    return version;
+}
+
 std::string getFirmwareVersion(const ImageType& imgType)
 {
     switch (imgType)
     {
         case (ImageType::cpldActive):
+        {
+            return readCPLDVersion();
+        }
         case (ImageType::cpldRecovery):
         {
             // TO-DO: Need to update once CPLD supported Firmware is available
diff --git a/service/CMakeLists.txt b/service/CMakeLists.txt
index 7b58e9e..6df4a2b 100644
--- a/service/CMakeLists.txt
+++ b/service/CMakeLists.txt
@@ -46,5 +46,6 @@
 target_link_libraries(${PROJECT_NAME} phosphor_logging)
 target_link_libraries(${PROJECT_NAME} pfr)
 target_link_libraries(${PROJECT_NAME} i2c)
+target_link_libraries(${PROJECT_NAME} gpiodcxx)
 
 install(TARGETS ${PROJECT_NAME} DESTINATION bin)