PEL: Print SRC section into JSON

For BMC created errors, look up the reason code in
the message registry for error description and also
meaning of data stored in hexwords 6-9 (if any).

Added registry message field in peltool list output.

"Primary SRC": {
    "Section Version":          "1",
    "Sub-section type":         "1",
    "Created by":               "0x1000",
    "SRC Version":              "0x02",
    "SRC Format":               "0x55",
    "Power Control Net Fault":  "False",
    "Error Details": {
        "Message":              "PS 0x64 had a PGOOD Fault",
        "PS_NUM":               "0x64"
    },
    "Valid Word Count":         "0x09",
    "Reference Code":           "BD8D1001",
    "Hex Word 2":               "00000055",
    "Hex Word 3":               "00000010",
    "Hex Word 4":               "00000000",
    "Hex Word 5":               "00000000",
    "Hex Word 6":               "00000064",
    "Hex Word 7":               "00000000",
    "Hex Word 8":               "00000000",
    "Hex Word 9":               "00000000"
}

"Primary SRC": {
    "Section Version":          "1",
    "Sub-section type":         "0",
    "Created by":               "0x4552",
    "SRC Version":              "0x02",
    "SRC Format":               "0x2008000",
    "Power Control Net Fault":  "False",
    "Valid Word Count":         "0x04",
    "Reference Code":           "B2001020",
    "Hex Word 2":               "02008000",
    "Hex Word 3":               "00000000",
    "Hex Word 4":               "00000012",
    "Callout Section": {
        "Callout Count":        "1",
        "Callouts": [{
            "FRU Type":         "Symbolic FRU",
            "Priority":         "Medium Priority",
            "Part Number":      "NEXTLVL"
        }]
    }
}

Testing: Manually run peltool and verified out. All unit tests passed.
Signed-off-by: Harisuddin Mohamed Isa <harisuddin@gmail.com>
Change-Id: I124627ba785413ebda02305b7d9f95431922e714
diff --git a/extensions/openpower-pels/callout.hpp b/extensions/openpower-pels/callout.hpp
index 76eb0f9..caf3aec 100644
--- a/extensions/openpower-pels/callout.hpp
+++ b/extensions/openpower-pels/callout.hpp
@@ -65,6 +65,16 @@
     void flatten(Stream& pel) const;
 
     /**
+     * @brief Returns the flags field of a callout
+     *
+     * @return uint8_t - The flags
+     */
+    uint8_t flags() const
+    {
+        return _flags;
+    }
+
+    /**
      * @brief Returns the priority field of a callout
      *
      * @return uint8_t - The priority
diff --git a/extensions/openpower-pels/json_utils.cpp b/extensions/openpower-pels/json_utils.cpp
index c6ea9f6..a25311c 100644
--- a/extensions/openpower-pels/json_utils.cpp
+++ b/extensions/openpower-pels/json_utils.cpp
@@ -139,7 +139,7 @@
 }
 
 void jsonInsert(std::string& jsonStr, const std::string& fieldName,
-                std::string& fieldValue, uint8_t indentCount)
+                std::string fieldValue, uint8_t indentCount)
 {
     const int8_t spacesToAppend =
         colAlign - (indentCount * indentLevel) - fieldName.length() - 3;
diff --git a/extensions/openpower-pels/json_utils.hpp b/extensions/openpower-pels/json_utils.hpp
index 14c2ce4..f3fb767 100644
--- a/extensions/openpower-pels/json_utils.hpp
+++ b/extensions/openpower-pels/json_utils.hpp
@@ -39,7 +39,7 @@
  * @param[in] indentCount - Indent count for the line
  */
 void jsonInsert(std::string& jsonStr, const std::string& fieldName,
-                std::string& fieldValue, uint8_t indentCount);
+                std::string fieldValue, uint8_t indentCount);
 
 /**
  * @brief Inserts key-value array into a JSON string
@@ -51,5 +51,30 @@
  */
 void jsonInsertArray(std::string& jsonStr, const std::string& fieldName,
                      std::vector<std::string>& values, uint8_t indentCount);
+
+/**
+ * @brief Converts an integer to a formatted string
+ * @param[in] format - the format of output string
+ * @param[in] number - the integer to convert
+ * @return std::string - the formatted string
+ */
+template <typename T>
+std::string getNumberString(const char* format, T number)
+{
+    char* value = nullptr;
+    std::string numString;
+
+    static_assert(std::is_integral<T>::value, "Integral required.");
+
+    int len = asprintf(&value, format, number);
+    if (len)
+    {
+        numString = value;
+    }
+    free(value);
+
+    return numString;
+}
+
 } // namespace pels
 } // namespace openpower
diff --git a/extensions/openpower-pels/manager.cpp b/extensions/openpower-pels/manager.cpp
index a705e67..a00da37 100644
--- a/extensions/openpower-pels/manager.cpp
+++ b/extensions/openpower-pels/manager.cpp
@@ -31,6 +31,7 @@
 
 using namespace phosphor::logging;
 namespace fs = std::filesystem;
+namespace rg = openpower::pels::message;
 
 namespace common_error = sdbusplus::xyz::openbmc_project::Common::Error;
 
@@ -130,7 +131,7 @@
                         const std::vector<std::string>& additionalData,
                         const std::vector<std::string>& associations)
 {
-    auto entry = _registry.lookup(message);
+    auto entry = _registry.lookup(message, rg::LookupType::name);
     std::string msg;
 
     if (entry)
diff --git a/extensions/openpower-pels/pel_values.cpp b/extensions/openpower-pels/pel_values.cpp
index cb00150..7e7f93b 100644
--- a/extensions/openpower-pels/pel_values.cpp
+++ b/extensions/openpower-pels/pel_values.cpp
@@ -206,12 +206,12 @@
  * The possible values for the Callout Priority field in the SRC.
  */
 const PELValues calloutPriorityValues = {
-    {'H', "high", "Mandatory, replace all with this type as a unit"},
-    {'M', "medium", "Medium Priority"},
-    {'A', "medium_group_a", "Medium Priority A, replace these as a group"},
-    {'B', "medium_group_b", "Medium Priority B, replace these as a group"},
-    {'C', "medium_group_c", "Medium Priority C, replace these as a group"},
-    {'L', "low", "Lowest priority replacement"}};
+    {0x48, "high", "Mandatory, replace all with this type as a unit"},
+    {0x4D, "medium", "Medium Priority"},
+    {0x41, "medium_group_a", "Medium Priority A, replace these as a group"},
+    {0x42, "medium_group_b", "Medium Priority B, replace these as a group"},
+    {0x43, "medium_group_c", "Medium Priority C, replace these as a group"},
+    {0x4C, "low", "Lowest priority replacement"}};
 
 PELValues::const_iterator findByValue(uint32_t value, const PELValues& fields)
 {
@@ -235,7 +235,6 @@
  * @brief Map for section IDs
  */
 const std::map<std::string, std::string> sectionTitles = {
-
     {"PH", "Private Header"},
     {"UH", "User Header"},
     {"PS", "Primary SRC"},
@@ -244,7 +243,7 @@
     {"MT", "Failing MTMS"},
     {"DH", "Dump Location"},
     {"SW", "Firmware Error"},
-    {"LP", "Impacted Part"},
+    {"LP", "Impacted Partition"},
     {"LR", "Logical Resource"},
     {"HM", "HMC ID"},
     {"EP", "EPOW"},
@@ -253,8 +252,32 @@
     {"CH", "Call Home"},
     {"UD", "User Data"},
     {"EI", "Env Info"},
-    {"ED", "Extended User Data"},
-};
+    {"ED", "Extended User Data"}};
+
+/**
+ * @brief Map for Procedure Descriptions
+ */
+const std::map<std::string, std::string> procedureDesc = {{"TODO", "TODO"}};
+
+/**
+ * @brief Map for Callout Failing Component Types
+ */
+const std::map<uint8_t, std::string> failingComponentType = {
+    {0x10, "Normal Hardware FRU"},
+    {0x20, "Code FRU"},
+    {0x30, "Configuration error, configuration procedure required"},
+    {0x40, "Maintenance Procedure Required"},
+    {0x90, "External FRU"},
+    {0xA0, "External Code FRU"},
+    {0xB0, "Tool FRU"},
+    {0xC0, "Symbolic FRU"},
+    {0xE0, "Symbolic FRU with trusted location code"}};
+
+/**
+ * @brief Map for Boolean value
+ */
+const std::map<bool, std::string> boolString = {{true, "True"},
+                                                {false, "False"}};
 
 /**
  * @brief Map for creator IDs
diff --git a/extensions/openpower-pels/pel_values.hpp b/extensions/openpower-pels/pel_values.hpp
index a52c8b3..2424c63 100644
--- a/extensions/openpower-pels/pel_values.hpp
+++ b/extensions/openpower-pels/pel_values.hpp
@@ -112,6 +112,21 @@
  */
 extern const std::map<TransmissionState, std::string> transmissionStates;
 
+/**
+ * @brief Map for Procedure Descriptions
+ */
+extern const std::map<std::string, std::string> procedureDesc;
+
+/**
+ * @brief Map for Callout Failing Component Types
+ */
+extern const std::map<uint8_t, std::string> failingComponentType;
+
+/**
+ * @brief Map for Boolean value
+ */
+extern const std::map<bool, std::string> boolString;
+
 } // namespace pel_values
 } // namespace pels
 } // namespace openpower
diff --git a/extensions/openpower-pels/registry.cpp b/extensions/openpower-pels/registry.cpp
index d749ab9..3c4e016 100644
--- a/extensions/openpower-pels/registry.cpp
+++ b/extensions/openpower-pels/registry.cpp
@@ -264,37 +264,34 @@
 
 } // namespace helper
 
-std::optional<Entry> Registry::lookup(const std::string& name)
+std::optional<Entry> Registry::lookup(const std::string& name, LookupType type,
+                                      bool toCache)
 {
-    // Look in /etc first in case someone put a test file there
-    fs::path debugFile{fs::path{debugFilePath} / registryFileName};
-    nlohmann::json registry;
-    std::ifstream file;
-
-    if (fs::exists(debugFile))
+    std::optional<nlohmann::json> registryTmp;
+    auto& registryOpt = (_registry) ? _registry : registryTmp;
+    if (!registryOpt)
     {
-        log<level::INFO>("Using debug PEL message registry");
-        file.open(debugFile);
+        registryOpt = readRegistry(_registryFile);
+        if (!registryOpt)
+        {
+            return std::nullopt;
+        }
+        else if (toCache)
+        {
+            // Save message registry in memory for peltool
+            _registry = std::move(registryTmp);
+        }
     }
-    else
-    {
-        file.open(_registryFile);
-    }
-
-    try
-    {
-        registry = nlohmann::json::parse(file);
-    }
-    catch (std::exception& e)
-    {
-        log<level::ERR>("Error parsing message registry JSON",
-                        entry("JSON_ERROR=%s", e.what()));
-        return std::nullopt;
-    }
-
+    auto& reg = (_registry) ? _registry : registryTmp;
+    const auto& registry = reg.value();
     // Find an entry with this name in the PEL array.
-    auto e = std::find_if(registry["PELs"].begin(), registry["PELs"].end(),
-                          [&name](const auto& j) { return name == j["Name"]; });
+    auto e = std::find_if(
+        registry["PELs"].begin(), registry["PELs"].end(),
+        [&name, &type](const auto& j) {
+            return ((name == j["Name"] && type == LookupType::name) ||
+                    (name == j["SRC"]["ReasonCode"] &&
+                     type == LookupType::reasonCode));
+        });
 
     if (e != registry["PELs"].end())
     {
@@ -371,6 +368,14 @@
                 entry.src.powerFault = src["PowerFault"];
             }
 
+            auto& doc = (*e)["Documentation"];
+            entry.doc.message = doc["Message"];
+            entry.doc.description = doc["Description"];
+            if (doc.find("MessageArgSources") != doc.end())
+            {
+                entry.doc.messageArgSources = doc["MessageArgSources"];
+            }
+
             return entry;
         }
         catch (std::exception& e)
@@ -383,6 +388,37 @@
     return std::nullopt;
 }
 
+std::optional<nlohmann::json>
+    Registry::readRegistry(const std::filesystem::path& registryFile)
+{
+    // Look in /etc first in case someone put a test file there
+    fs::path debugFile{fs::path{debugFilePath} / registryFileName};
+    nlohmann::json registry;
+    std::ifstream file;
+
+    if (fs::exists(debugFile))
+    {
+        log<level::INFO>("Using debug PEL message registry");
+        file.open(debugFile);
+    }
+    else
+    {
+        file.open(registryFile);
+    }
+
+    try
+    {
+        registry = nlohmann::json::parse(file);
+    }
+    catch (std::exception& e)
+    {
+        log<level::ERR>("Error parsing message registry JSON",
+                        entry("JSON_ERROR=%s", e.what()));
+        return std::nullopt;
+    }
+    return registry;
+}
+
 } // namespace message
 } // namespace pels
 } // namespace openpower
diff --git a/extensions/openpower-pels/registry.hpp b/extensions/openpower-pels/registry.hpp
index d75c30a..0a75d8e 100644
--- a/extensions/openpower-pels/registry.hpp
+++ b/extensions/openpower-pels/registry.hpp
@@ -13,6 +13,39 @@
 {
 
 constexpr auto registryFileName = "message_registry.json";
+enum class LookupType
+{
+    name = 0,
+    reasonCode = 1
+};
+
+/**
+ * @brief Represents the Documentation related fields in the message registry.
+ *        It is part of the 'Entry' structure that will be filled in when
+ *        an error is looked up in the registry.
+ *
+ * If a field is wrapped by std::optional, it means the field is
+ * optional in the JSON and higher level code knows how to handle it.
+ */
+struct DOC
+{
+    /**
+     * @brief Description of error
+     */
+    std::string description;
+
+    /**
+     * @brief Error message field
+     */
+    std::string message;
+
+    /**
+     * @brief An optional vector of SRC word 6-9 to use as the source of the
+     *        numeric arguments that will be substituted into any placeholder
+     *        in the Message field.
+     */
+    std::optional<std::vector<std::string>> messageArgSources;
+};
 
 /**
  * @brief Represents the SRC related fields in the message registry.
@@ -123,6 +156,11 @@
      * The SRC related fields.
      */
     SRC src;
+
+    /**
+     * The Documentation related fields.
+     */
+    DOC doc;
 };
 
 /**
@@ -155,24 +193,42 @@
     }
 
     /**
-     * @brief Find a registry entry based on its error name.
+     * @brief Find a registry entry based on its error name or reason code.
      *
      * This function does do some basic sanity checking on the JSON contents,
      * but there is also an external program that enforces a schema on the
      * registry JSON that should catch all of these problems ahead of time.
      *
      * @param[in] name - The error name, like xyz.openbmc_project.Error.Foo
-     *
+     *                 - OR
+     *                 - The reason code, like 0x1001
+     * @param[in] type - LookupType enum value
+     * @param[in] toCache - boolean to cache registry in memory
      * @return optional<Entry> A filled in message registry structure if
      *                         found, otherwise an empty optional object.
      */
-    std::optional<Entry> lookup(const std::string& name);
+    std::optional<Entry> lookup(const std::string& name, LookupType type,
+                                bool toCache = false);
 
   private:
     /**
+     * @brief Parse message registry file using nlohmann::json
+     * @param[in] registryFile - The message registry JSON file
+     * @return optional<nlohmann::json> The full message registry object or an
+     *                                  empty optional object upon failure.
+     */
+    std::optional<nlohmann::json>
+        readRegistry(const std::filesystem::path& registryFile);
+
+    /**
      * @brief The path to the registry JSON file.
      */
     std::filesystem::path _registryFile;
+
+    /**
+     * @brief The full message registry object.
+     */
+    std::optional<nlohmann::json> _registry;
 };
 
 namespace helper
diff --git a/extensions/openpower-pels/src.cpp b/extensions/openpower-pels/src.cpp
index b2b86bf..570c6c6 100644
--- a/extensions/openpower-pels/src.cpp
+++ b/extensions/openpower-pels/src.cpp
@@ -15,13 +15,18 @@
  */
 #include "src.hpp"
 
+#include "json_utils.hpp"
+#include "paths.hpp"
+#include "pel_values.hpp"
+
 #include <phosphor-logging/log.hpp>
 
 namespace openpower
 {
 namespace pels
 {
-
+namespace pv = openpower::pels::pel_values;
+namespace rg = openpower::pels::message;
 using namespace phosphor::logging;
 
 void SRC::unflatten(Stream& stream)
@@ -177,5 +182,256 @@
     _valid = failed ? false : true;
 }
 
+std::optional<std::string> SRC::getErrorDetails(message::Registry& registry,
+                                                DetailLevel type,
+                                                bool toCache) const
+{
+    const std::string jsonIndent(indentLevel, 0x20);
+    std::string errorOut;
+    uint8_t errorType =
+        strtoul(asciiString().substr(0, 2).c_str(), nullptr, 16);
+    if (errorType == static_cast<uint8_t>(SRCType::bmcError) ||
+        errorType == static_cast<uint8_t>(SRCType::powerError))
+    {
+        auto entry = registry.lookup("0x" + asciiString().substr(4, 4),
+                                     rg::LookupType::reasonCode, toCache);
+        if (entry)
+        {
+            errorOut.append(jsonIndent + "\"Error Details\": {\n");
+            auto errorMsg = getErrorMessage(*entry);
+            if (errorMsg)
+            {
+                if (type == DetailLevel::message)
+                {
+                    return errorMsg.value();
+                }
+                else
+                {
+                    jsonInsert(errorOut, "Message", errorMsg.value(), 2);
+                }
+            }
+            if (entry->src.hexwordADFields)
+            {
+                std::map<size_t, std::string> adFields =
+                    entry->src.hexwordADFields.value();
+                for (const auto& hexwordMap : adFields)
+                {
+                    jsonInsert(errorOut, hexwordMap.second,
+                               getNumberString("0x%X",
+                                               _hexData[getWordIndexFromWordNum(
+                                                   hexwordMap.first)]),
+                               2);
+                }
+            }
+            errorOut.erase(errorOut.size() - 2);
+            errorOut.append("\n");
+            errorOut.append(jsonIndent + "},\n");
+            return errorOut;
+        }
+    }
+    return std::nullopt;
+}
+
+std::optional<std::string>
+    SRC::getErrorMessage(const message::Entry& regEntry) const
+{
+    try
+    {
+        if (regEntry.doc.messageArgSources)
+        {
+            size_t msgLen = regEntry.doc.message.length();
+            char msg[msgLen + 1];
+            strcpy(msg, regEntry.doc.message.c_str());
+            std::vector<uint32_t> argSourceVals;
+            std::string message;
+            const auto& argValues = regEntry.doc.messageArgSources.value();
+            for (size_t i = 0; i < argValues.size(); ++i)
+            {
+                argSourceVals.push_back(_hexData[getWordIndexFromWordNum(
+                    argValues[i].back() - '0')]);
+            }
+            const char* msgPointer = msg;
+            while (*msgPointer)
+            {
+                if (*msgPointer == '%')
+                {
+                    msgPointer++;
+                    size_t wordIndex = *msgPointer - '0';
+                    if (isdigit(*msgPointer) && wordIndex >= 1 &&
+                        static_cast<uint16_t>(wordIndex) <=
+                            argSourceVals.size())
+                    {
+                        message.append(getNumberString(
+                            "0x%X", argSourceVals[wordIndex - 1]));
+                    }
+                    else
+                    {
+                        message.append("%" + std::string(1, *msgPointer));
+                    }
+                }
+                else
+                {
+                    message.push_back(*msgPointer);
+                }
+                msgPointer++;
+            }
+            return message;
+        }
+        else
+        {
+            return regEntry.doc.message;
+        }
+    }
+    catch (const std::exception& e)
+    {
+        log<level::ERR>("Cannot get error message from registry entry",
+                        entry("ERROR=%s", e.what()));
+    }
+    return std::nullopt;
+}
+
+std::optional<std::string> SRC::getCallouts() const
+{
+    if (!_callouts)
+    {
+        return std::nullopt;
+    }
+    std::string printOut;
+    const std::string jsonIndent(indentLevel, 0x20);
+    const auto& callout = _callouts->callouts();
+    const auto& compDescrp = pv::failingComponentType;
+    printOut.append(jsonIndent + "\"Callout Section\": {\n");
+    jsonInsert(printOut, "Callout Count", std::to_string(callout.size()), 2);
+    printOut.append(jsonIndent + jsonIndent + "\"Callouts\": [");
+    for (auto& entry : callout)
+    {
+        printOut.append("{\n");
+        if (entry->fruIdentity())
+        {
+            jsonInsert(
+                printOut, "FRU Type",
+                compDescrp.at(entry->fruIdentity()->failingComponentType()), 3);
+            jsonInsert(printOut, "Priority",
+                       pv::getValue(entry->priority(),
+                                    pel_values::calloutPriorityValues),
+                       3);
+            if (!entry->locationCode().empty())
+            {
+                jsonInsert(printOut, "Location Code", entry->locationCode(), 3);
+            }
+            if (entry->fruIdentity()->getPN().has_value())
+            {
+                jsonInsert(printOut, "Part Number",
+                           entry->fruIdentity()->getPN().value(), 3);
+            }
+            if (entry->fruIdentity()->getMaintProc().has_value())
+            {
+                jsonInsert(printOut, "Procedure Number",
+                           entry->fruIdentity()->getMaintProc().value(), 3);
+                if (pv::procedureDesc.find(
+                        entry->fruIdentity()->getMaintProc().value()) !=
+                    pv::procedureDesc.end())
+                {
+                    jsonInsert(
+                        printOut, "Description",
+                        pv::procedureDesc.at(
+                            entry->fruIdentity()->getMaintProc().value()),
+                        3);
+                }
+            }
+            if (entry->fruIdentity()->getCCIN().has_value())
+            {
+                jsonInsert(printOut, "CCIN",
+                           entry->fruIdentity()->getCCIN().value(), 3);
+            }
+            if (entry->fruIdentity()->getSN().has_value())
+            {
+                jsonInsert(printOut, "Serial Number",
+                           entry->fruIdentity()->getSN().value(), 3);
+            }
+        }
+        if (entry->pceIdentity())
+        {
+            const auto& pceIdentMtms = entry->pceIdentity()->mtms();
+            if (!pceIdentMtms.machineTypeAndModel().empty())
+            {
+                jsonInsert(printOut, "PCE MTMS",
+                           pceIdentMtms.machineTypeAndModel() + "_" +
+                               pceIdentMtms.machineSerialNumber(),
+                           3);
+            }
+            if (!entry->pceIdentity()->enclosureName().empty())
+            {
+                jsonInsert(printOut, "PCE Name",
+                           entry->pceIdentity()->enclosureName(), 3);
+            }
+        }
+        if (entry->mru())
+        {
+            const auto& mruCallouts = entry->mru()->mrus();
+            std::string mruId;
+            for (auto& element : mruCallouts)
+            {
+                if (!mruId.empty())
+                {
+                    mruId.append(", " + getNumberString("%08X", element.id));
+                }
+                else
+                {
+                    mruId.append(getNumberString("%08X", element.id));
+                }
+            }
+            jsonInsert(printOut, "MRU Id", mruId, 3);
+        }
+        printOut.erase(printOut.size() - 2);
+        printOut.append("\n" + jsonIndent + jsonIndent + "}, ");
+    };
+    printOut.erase(printOut.size() - 2);
+    printOut.append("]\n" + jsonIndent + "}");
+    return printOut;
+}
+
+std::optional<std::string> SRC::getJSON() const
+{
+    std::string ps;
+    jsonInsert(ps, "Section Version", getNumberString("%d", _header.version),
+               1);
+    jsonInsert(ps, "Sub-section type", getNumberString("%d", _header.subType),
+               1);
+    jsonInsert(ps, "Created by", getNumberString("0x%X", _header.componentID),
+               1);
+    jsonInsert(ps, "SRC Version", getNumberString("0x%02X", _version), 1);
+    jsonInsert(ps, "SRC Format", getNumberString("0x%02X", _hexData[0]), 1);
+    jsonInsert(ps, "Power Control Net Fault",
+               pv::boolString.at(isPowerFaultEvent()), 1);
+    rg::Registry registry(getMessageRegistryPath() / rg::registryFileName);
+    auto errorDetails = getErrorDetails(registry, DetailLevel::json);
+    if (errorDetails)
+    {
+        ps.append(errorDetails.value());
+    }
+    jsonInsert(ps, "Valid Word Count", getNumberString("0x%02X", _wordCount),
+               1);
+    std::string refcode = asciiString();
+    refcode = refcode.substr(0, refcode.find(0x20));
+    jsonInsert(ps, "Reference Code", refcode, 1);
+    for (size_t i = 2; i <= _wordCount; i++)
+    {
+        jsonInsert(
+            ps, "Hex Word " + std::to_string(i),
+            getNumberString("%08X", _hexData[getWordIndexFromWordNum(i)]), 1);
+    }
+    auto calloutJson = getCallouts();
+    if (calloutJson)
+    {
+        ps.append(calloutJson.value());
+    }
+    else
+    {
+        ps.erase(ps.size() - 2);
+    }
+    return ps;
+}
+
 } // namespace pels
 } // namespace openpower
diff --git a/extensions/openpower-pels/src.hpp b/extensions/openpower-pels/src.hpp
index dada63a..2296f6f 100644
--- a/extensions/openpower-pels/src.hpp
+++ b/extensions/openpower-pels/src.hpp
@@ -21,6 +21,11 @@
 constexpr uint8_t primaryBMCPosition = 0x10;
 constexpr size_t baseSRCSize = 72;
 
+enum class DetailLevel
+{
+    message = 0x01,
+    json = 0x02
+};
 /**
  * @class SRC
  *
@@ -45,7 +50,10 @@
     enum HeaderFlags
     {
         additionalSections = 0x01,
-        powerFaultEvent = 0x02
+        powerFaultEvent = 0x02,
+        hypDumpInit = 0x04,
+        i5OSServiceEventBit = 0x10,
+        virtualProgressSRC = 0x80
     };
 
     SRC() = delete;
@@ -212,6 +220,23 @@
         return wordNum - 2;
     }
 
+    /**
+     * @brief Get section in JSON.
+     * @return std::optional<std::string> - SRC section's JSON
+     */
+    std::optional<std::string> getJSON() const override;
+
+    /**
+     * @brief Get error details based on refcode and hexwords
+     * @param[in] registry - Registry object
+     * @param[in] type - detail level enum value : single message or full json
+     * @param[in] toCache - boolean to cache registry in memory, default=false
+     * @return std::optional<std::string> - Error details
+     */
+    std::optional<std::string> getErrorDetails(message::Registry& registry,
+                                               DetailLevel type,
+                                               bool toCache = false) const;
+
   private:
     /**
      * @brief Fills in the user defined hex words from the
@@ -281,6 +306,20 @@
     void validate() override;
 
     /**
+     * @brief Get error description from message registry
+     * @param[in] regEntry - The message registry entry for the error
+     * @return std::optional<std::string> - Error message
+     */
+    std::optional<std::string>
+        getErrorMessage(const message::Entry& regEntry) const;
+
+    /**
+     * @brief Get Callout info in JSON
+     * @return std::optional<std::string> - Callout details
+     */
+    std::optional<std::string> getCallouts() const;
+
+    /**
      * @brief The SRC version field
      */
     uint8_t _version;
diff --git a/extensions/openpower-pels/tools/peltool.cpp b/extensions/openpower-pels/tools/peltool.cpp
index 5c040ca..f1d5f59 100644
--- a/extensions/openpower-pels/tools/peltool.cpp
+++ b/extensions/openpower-pels/tools/peltool.cpp
@@ -16,6 +16,7 @@
 #include "config.h"
 
 #include "../bcd_time.hpp"
+#include "../paths.hpp"
 #include "../pel.hpp"
 #include "../pel_types.hpp"
 #include "../pel_values.hpp"
@@ -197,7 +198,7 @@
 }
 
 template <typename T>
-std::string genPELJSON(T itr, bool hidden)
+std::string genPELJSON(T itr, bool hidden, message::Registry& registry)
 {
     std::size_t found;
     std::string val;
@@ -224,9 +225,24 @@
                 val = std::string(tmpValStr);
                 listStr += "\t\"" + val + "\": {\n";
                 // ASCII
-                val = pel.primarySRC() ? pel.primarySRC().value()->asciiString()
-                                       : "No SRC";
-                listStr += "\t\t\"SRC\": \"" + trim(val) + "\",\n";
+                if (pel.primarySRC())
+                {
+                    val = pel.primarySRC().value()->asciiString();
+                    listStr += "\t\t\"SRC\": \"" +
+                               val.substr(0, val.find(0x20)) + "\",\n";
+                    // Registry message
+                    auto regVal = pel.primarySRC().value()->getErrorDetails(
+                        registry, DetailLevel::message, true);
+                    if (regVal)
+                    {
+                        val = regVal.value();
+                        listStr += "\t\t\"Message\": \"" + val + "\",\n";
+                    }
+                }
+                else
+                {
+                    listStr += "\t\t\"SRC\": \"No SRC\",\n";
+                }
                 // platformid
                 sprintf(tmpValStr, "0x%X", pel.privateHeader().plid());
                 val = std::string(tmpValStr);
@@ -308,8 +324,10 @@
                          fileNameToTimestamp((*it).path().filename()));
         }
     }
-    auto buildJSON = [&listStr, &hidden](const auto& i) {
-        listStr += genPELJSON(i, hidden);
+    message::Registry registry(getMessageRegistryPath() /
+                               message::registryFileName);
+    auto buildJSON = [&listStr, &hidden, &registry](const auto& i) {
+        listStr += genPELJSON(i, hidden, registry);
     };
     if (order)
     {
diff --git a/test/openpower-pels/Makefile.include b/test/openpower-pels/Makefile.include
index 6905dc3..2bed701 100644
--- a/test/openpower-pels/Makefile.include
+++ b/test/openpower-pels/Makefile.include
@@ -289,9 +289,13 @@
 	$(top_builddir)/extensions/openpower-pels/callout.o \
 	$(top_builddir)/extensions/openpower-pels/callouts.o \
 	$(top_builddir)/extensions/openpower-pels/fru_identity.o \
+	$(top_builddir)/extensions/openpower-pels/json_utils.o \
+	$(top_builddir)/extensions/openpower-pels/paths.o \
 	$(top_builddir)/extensions/openpower-pels/mru.o \
 	$(top_builddir)/extensions/openpower-pels/mtms.o \
 	$(top_builddir)/extensions/openpower-pels/pce_identity.o \
+	$(top_builddir)/extensions/openpower-pels/pel_values.o \
+	$(top_builddir)/extensions/openpower-pels/registry.o \
 	$(top_builddir)/extensions/openpower-pels/src.o
 src_test_LDFLAGS = $(test_ldflags)
 
@@ -309,9 +313,13 @@
 	$(top_builddir)/extensions/openpower-pels/data_interface.o \
 	$(top_builddir)/extensions/openpower-pels/extended_user_header.o \
 	$(top_builddir)/extensions/openpower-pels/fru_identity.o \
+	$(top_builddir)/extensions/openpower-pels/json_utils.o \
 	$(top_builddir)/extensions/openpower-pels/mru.o \
 	$(top_builddir)/extensions/openpower-pels/mtms.o \
+	$(top_builddir)/extensions/openpower-pels/paths.o \
+	$(top_builddir)/extensions/openpower-pels/pel_values.o \
 	$(top_builddir)/extensions/openpower-pels/pce_identity.o \
+	$(top_builddir)/extensions/openpower-pels/registry.o \
 	$(top_builddir)/extensions/openpower-pels/src.o
 extended_user_header_test_LDFLAGS = $(test_ldflags)
 
diff --git a/test/openpower-pels/pel_manager_test.cpp b/test/openpower-pels/pel_manager_test.cpp
index 8f3df22..08124ff 100644
--- a/test/openpower-pels/pel_manager_test.cpp
+++ b/test/openpower-pels/pel_manager_test.cpp
@@ -122,6 +122,11 @@
             "SRC":
             {
                 "ReasonCode": "0x2030"
+            },
+            "Documentation":
+            {
+                "Description": "A PGOOD Fault",
+                "Message": "PS had a PGOOD Fault"
             }
         }
     ]
diff --git a/test/openpower-pels/registry_test.cpp b/test/openpower-pels/registry_test.cpp
index c193c3f..2944ce0 100644
--- a/test/openpower-pels/registry_test.cpp
+++ b/test/openpower-pels/registry_test.cpp
@@ -35,6 +35,12 @@
             "SRC":
             {
                 "ReasonCode": "0x2030"
+            },
+
+            "Documentation":
+            {
+                "Description": "A PGOOD Fault",
+                "Message": "PS had a PGOOD Fault"
             }
         },
 
@@ -66,6 +72,20 @@
                         "AdditionalDataPropSource": "VOLTAGE"
                     }
                 }
+            },
+
+            "Documentation":
+            {
+                "Description": "A PGOOD Fault",
+                "Message": "PS %1 had a PGOOD Fault",
+                "MessageArgSources":
+                [
+                    "SRCWord6"
+                ],
+                "Notes": [
+                    "In the UserData section there is a JSON",
+                    "dump that provides debug information."
+                ]
             }
         }
     ]
@@ -104,7 +124,7 @@
     auto path = RegistryTest::writeData(registryData);
     Registry registry{path};
 
-    auto entry = registry.lookup("foo");
+    auto entry = registry.lookup("foo", LookupType::name);
     EXPECT_FALSE(entry);
 }
 
@@ -113,7 +133,8 @@
     auto path = RegistryTest::writeData(registryData);
     Registry registry{path};
 
-    auto entry = registry.lookup("xyz.openbmc_project.Power.OverVoltage");
+    auto entry = registry.lookup("xyz.openbmc_project.Power.OverVoltage",
+                                 LookupType::name);
     ASSERT_TRUE(entry);
     EXPECT_EQ(entry->name, "xyz.openbmc_project.Power.OverVoltage");
     EXPECT_EQ(entry->subsystem, 0x62);
@@ -147,6 +168,17 @@
     EXPECT_NE(std::find((*sid).begin(), (*sid).end(), 5), (*sid).end());
     EXPECT_NE(std::find((*sid).begin(), (*sid).end(), 6), (*sid).end());
     EXPECT_NE(std::find((*sid).begin(), (*sid).end(), 7), (*sid).end());
+
+    EXPECT_EQ(entry->doc.description, "A PGOOD Fault");
+    EXPECT_EQ(entry->doc.message, "PS %1 had a PGOOD Fault");
+    auto& hexwordSource = entry->doc.messageArgSources;
+    EXPECT_TRUE(hexwordSource);
+    EXPECT_EQ((*hexwordSource).size(), 1);
+    EXPECT_EQ((*hexwordSource).front(), "SRCWord6");
+
+    entry = registry.lookup("0x2333", LookupType::reasonCode);
+    ASSERT_TRUE(entry);
+    EXPECT_EQ(entry->name, "xyz.openbmc_project.Power.OverVoltage");
 }
 
 // Check the entry that mostly uses defaults
@@ -155,7 +187,8 @@
     auto path = RegistryTest::writeData(registryData);
     Registry registry{path};
 
-    auto entry = registry.lookup("xyz.openbmc_project.Power.Fault");
+    auto entry =
+        registry.lookup("xyz.openbmc_project.Power.Fault", LookupType::name);
     ASSERT_TRUE(entry);
     EXPECT_EQ(entry->name, "xyz.openbmc_project.Power.Fault");
     EXPECT_EQ(entry->subsystem, 0x61);
@@ -180,7 +213,7 @@
 
     Registry registry{path};
 
-    EXPECT_FALSE(registry.lookup("foo"));
+    EXPECT_FALSE(registry.lookup("foo", LookupType::name));
 }
 
 // Test the helper functions the use the pel_values data.