PEL: Support resolution property

Support resolution property to add callouts

Tested: Created new PEL using busctl and checked for the property to
see if the value is updated. The error log daemon was restarted and the
property is checked again to make sure the value is restored.

Verified serialization of new error log by recreating it on a old code
version to make sure logs are created w/o the new property and the
daemon did not crash.

Result:
root@rainier:~# busctl get-property xyz.openbmc_project.Logging \
 /xyz/openbmc_project/logging/entry/1 xyz.openbmc_project.Logging.\
 Entry Resolution
s "1. Priority: High, Procedure: BMCSP02\n2. Priority: Medium, PN: \
SVCDOCS\n"

Test with location Code:
root@p10bmc:~# busctl get-property xyz.openbmc_project.Logging \
/xyz/openbmc_project/logging/entry/3 xyz.openbmc_project.Logging.Entry \
Resolution
s "1. Location Code: U78DA.ND0.1234567-P0, Priority: Medium, PN: SVCDOCS\n2. \
Priority: Low, Procedure: BMCSP02\n"

Signed-off-by: Vijay Lobo <vijaylobo@gmail.com>
Change-Id: I44eebbf794efeb8e752fff98de7c638c927982cd
diff --git a/config.h.meson b/config.h.meson
index e5df62c..cd91f80 100644
--- a/config.h.meson
+++ b/config.h.meson
@@ -34,7 +34,8 @@
 
 static constexpr auto FIRST_CEREAL_CLASS_VERSION_WITH_FWLEVEL = "2";
 static constexpr auto FIRST_CEREAL_CLASS_VERSION_WITH_UPDATE_TS = "3";
-static constexpr auto FIRST_CEREAL_CLASS_VERSION_WITH_EVENTID= "4";
-static constexpr size_t CLASS_VERSION = 4;
+static constexpr auto FIRST_CEREAL_CLASS_VERSION_WITH_EVENTID = "4";
+static constexpr auto FIRST_CEREAL_CLASS_VERSION_WITH_RESOLUTION = "5";
+static constexpr size_t CLASS_VERSION = 5;
 
 // vim: ft=cpp
diff --git a/elog_entry.cpp b/elog_entry.cpp
index f0b3e45..1bd70c2 100644
--- a/elog_entry.cpp
+++ b/elog_entry.cpp
@@ -56,6 +56,21 @@
     return current;
 }
 
+std::string Entry::resolution(std::string value)
+{
+    auto current =
+        sdbusplus::xyz::openbmc_project::Logging::server::Entry::resolution();
+    if (value != current)
+    {
+        current =
+            sdbusplus::xyz::openbmc_project::Logging::server::Entry::resolution(
+                value);
+        serialize(*this);
+    }
+
+    return current;
+}
+
 sdbusplus::message::unix_fd Entry::getEntry()
 {
     FILE* fp = fopen(path().c_str(), "rb");
diff --git a/elog_entry.hpp b/elog_entry.hpp
index 45e8aac..3a6b313 100644
--- a/elog_entry.hpp
+++ b/elog_entry.hpp
@@ -121,6 +121,14 @@
 
     using sdbusplus::xyz::openbmc_project::Logging::server::Entry::eventId;
 
+    /** @brief Update resolution string of the error.
+     *  @param[in] value - The resolution
+     *  @returns New property value
+     */
+    std::string resolution(std::string value) override;
+
+    using sdbusplus::xyz::openbmc_project::Logging::server::Entry::resolution;
+
     /** @brief Delete this d-bus object.
      */
     void delete_() override;
diff --git a/elog_serialize.cpp b/elog_serialize.cpp
index fce433d..070164c 100644
--- a/elog_serialize.cpp
+++ b/elog_serialize.cpp
@@ -31,7 +31,7 @@
 {
     a(e.id(), e.severity(), e.timestamp(), e.message(), e.additionalData(),
       e.associations(), e.resolved(), e.version(), e.updateTimestamp(),
-      e.eventId());
+      e.eventId(), e.resolution());
 }
 
 /** @brief Function required by Cereal to perform deserialization.
@@ -56,6 +56,7 @@
     std::string fwVersion{};
     uint64_t updateTimestamp{};
     std::string eventId{};
+    std::string resolution{};
 
     if (version < std::stoul(FIRST_CEREAL_CLASS_VERSION_WITH_FWLEVEL))
     {
@@ -74,11 +75,16 @@
         a(id, severity, timestamp, message, additionalData, associations,
           resolved, fwVersion, updateTimestamp);
     }
-    else
+    else if (version < std::stoul(FIRST_CEREAL_CLASS_VERSION_WITH_RESOLUTION))
     {
         a(id, severity, timestamp, message, additionalData, associations,
           resolved, fwVersion, updateTimestamp, eventId);
     }
+    else
+    {
+        a(id, severity, timestamp, message, additionalData, associations,
+          resolved, fwVersion, updateTimestamp, eventId, resolution);
+    }
 
     e.id(id);
     e.severity(severity);
@@ -93,6 +99,7 @@
                   VersionPurpose::BMC);
     e.updateTimestamp(updateTimestamp);
     e.eventId(eventId);
+    e.resolution(resolution);
 }
 
 fs::path serialize(const Entry& e, const fs::path& dir)
diff --git a/extensions/openpower-pels/manager.cpp b/extensions/openpower-pels/manager.cpp
index c90cacc..3387970 100644
--- a/extensions/openpower-pels/manager.cpp
+++ b/extensions/openpower-pels/manager.cpp
@@ -172,6 +172,7 @@
         // Check if firmware should quiesce system due to error
         checkPelAndQuiesce(pel);
         updateEventId(pel);
+        updateResolution(pel);
     }
     else
     {
@@ -369,6 +370,7 @@
     // Check if firmware should quiesce system due to error
     checkPelAndQuiesce(pel);
     updateEventId(pel);
+    updateResolution(pel);
 }
 
 sdbusplus::message::unix_fd Manager::getPEL(uint32_t pelID)
@@ -666,6 +668,82 @@
     }
 }
 
+std::string Manager::getResolution(const openpower::pels::PEL& pel) const
+{
+    std::string str;
+    std::string resolution;
+    auto src = pel.primarySRC();
+    if (src)
+    {
+        // First extract the callout pointer and then go through
+        const auto& callouts = (*src)->callouts();
+        namespace pv = openpower::pels::pel_values;
+        // All PELs dont have callout, check before parsing callout data
+        if (callouts)
+        {
+            const auto& entries = callouts->callouts();
+            // Entry starts with index 1
+            uint8_t index = 1;
+            for (auto& entry : entries)
+            {
+                resolution += std::to_string(index) + ". ";
+                // Adding Location code to resolution
+                if (!entry->locationCode().empty())
+                    resolution +=
+                        "Location Code: " + entry->locationCode() + ", ";
+                if (entry->fruIdentity())
+                {
+                    // Get priority and set the resolution string
+                    str = pv::getValue(entry->priority(),
+                                       pel_values::calloutPriorityValues,
+                                       pel_values::registryNamePos);
+                    str[0] = toupper(str[0]);
+                    resolution += "Priority: " + str + ", ";
+                    if (entry->fruIdentity()->getPN().has_value())
+                    {
+                        resolution +=
+                            "PN: " + entry->fruIdentity()->getPN().value() +
+                            ", ";
+                    }
+                    if (entry->fruIdentity()->getSN().has_value())
+                    {
+                        resolution +=
+                            "SN: " + entry->fruIdentity()->getSN().value() +
+                            ", ";
+                    }
+                    if (entry->fruIdentity()->getCCIN().has_value())
+                    {
+                        resolution +=
+                            "CCIN: " + entry->fruIdentity()->getCCIN().value() +
+                            ", ";
+                    }
+                    // Add the maintenance procedure
+                    if (entry->fruIdentity()->getMaintProc().has_value())
+                    {
+                        resolution +=
+                            "Procedure: " +
+                            entry->fruIdentity()->getMaintProc().value() + ", ";
+                    }
+                }
+                resolution.resize(resolution.size() - 2);
+                resolution += "\n";
+                index++;
+            }
+        }
+    }
+    return resolution;
+}
+
+void Manager::updateResolution(std::unique_ptr<openpower::pels::PEL>& pel)
+{
+    std::string callouts = getResolution(*pel);
+    auto entryN = _logManager.entries.find(pel->obmcLogID());
+    if (entryN != _logManager.entries.end())
+    {
+        entryN->second->resolution(callouts);
+    }
+}
+
 void Manager::setEntryPath(uint32_t obmcLogID)
 {
     Repository::LogID id{Repository::LogID::Obmc(obmcLogID)};
diff --git a/extensions/openpower-pels/manager.hpp b/extensions/openpower-pels/manager.hpp
index aee3eab..aa4a393 100644
--- a/extensions/openpower-pels/manager.hpp
+++ b/extensions/openpower-pels/manager.hpp
@@ -212,6 +212,13 @@
     static std::vector<uint8_t> eselToRawData(const std::string& esel);
 
     /**
+     * @brief Generate resolution string from the PEL
+     *
+     * @param[in] pel - The PEL to use
+     */
+    std::string getResolution(const openpower::pels::PEL& pel) const;
+
+    /**
      * @brief Generate event ID from the PEL
      *
      * @param[in] pel - The PEL to use
@@ -387,6 +394,15 @@
     void setServiceProviderNotifyFlag(uint32_t obmcLogID);
 
     /**
+     * @brief Update resolution D-bus property for this error log
+     *
+     * Update the resolution property of D-bus with callouts extracted from PEL
+     *
+     * @param[in] pel - The PEL to use
+     */
+    void updateResolution(std::unique_ptr<openpower::pels::PEL>& pel);
+
+    /**
      * @brief Reference to phosphor-logging's Manager class
      */
     phosphor::logging::internal::Manager& _logManager;
diff --git a/extensions/openpower-pels/pel_values.cpp b/extensions/openpower-pels/pel_values.cpp
index 277954e..11128bd 100644
--- a/extensions/openpower-pels/pel_values.cpp
+++ b/extensions/openpower-pels/pel_values.cpp
@@ -315,13 +315,21 @@
     {TransmissionState::sent, "Sent"},
     {TransmissionState::acked, "Acked"}};
 
-std::string getValue(const uint8_t field, const pel_values::PELValues& values)
+std::string getValue(const uint8_t field, const pel_values::PELValues& values,
+                     const uint8_t position)
 {
 
     auto tmp = pel_values::findByValue(field, values);
     if (tmp != values.end())
     {
-        return std::get<pel_values::descriptionPos>(*tmp);
+        if (position == pel_values::registryNamePos)
+        {
+            return std::get<pel_values::registryNamePos>(*tmp);
+        }
+        else
+        {
+            return std::get<pel_values::descriptionPos>(*tmp);
+        }
     }
     else
     {
diff --git a/extensions/openpower-pels/pel_values.hpp b/extensions/openpower-pels/pel_values.hpp
index 5f8b852..d737267 100644
--- a/extensions/openpower-pels/pel_values.hpp
+++ b/extensions/openpower-pels/pel_values.hpp
@@ -35,8 +35,10 @@
  * @return std::string - the value
  * @param[in] uint8_t - field to get value for
  * @param[in] PELValues - lookup table
+ * @param[in] uint8_t - position in the pel_values table to read
  */
-std::string getValue(const uint8_t field, const pel_values::PELValues& values);
+std::string getValue(const uint8_t field, const pel_values::PELValues& values,
+                     const uint8_t position = pel_values::descriptionPos);
 
 /**
  * @brief Helper function to get value vector from lookup tables.
diff --git a/test/openpower-pels/pel_manager_test.cpp b/test/openpower-pels/pel_manager_test.cpp
index 0facc33..7bf350f 100644
--- a/test/openpower-pels/pel_manager_test.cpp
+++ b/test/openpower-pels/pel_manager_test.cpp
@@ -231,6 +231,14 @@
             {
                 "ReasonCode": "0x2030"
             },
+            "Callouts": [
+                {
+                    "CalloutList": [
+                        {"Priority": "high", "Procedure": "bmc_code"},
+                        {"Priority": "medium", "SymbolicFRU": "service_docs"}
+                    ]
+                }
+            ],
             "Documentation":
             {
                 "Description": "A PGOOD Fault",
@@ -294,6 +302,10 @@
     EXPECT_EQ(manager.getEventId(pel),
               "BD612030 00000055 00000010 00000000 00000000 00000000 00000000 "
               "00000000 00000000");
+    // Check if resolution property creation is good
+    EXPECT_EQ(manager.getResolution(pel),
+              "1. Priority: High, Procedure: BMCSP02\n2. Priority: Medium, PN: "
+              "SVCDOCS\n");
 
     // Remove it
     manager.erase(33);