PEL: Add the DRAM manufacturer info of DIMM callout in UD section

Add the called out DIMMs DRAM manufacturer info to the user data
section of the PEL to assist the service engineers in identifying the
manufacturer of the faulty DRAMs packaged within the DIMM module
directly from the logs, aiding in quick resolution.

The changes also moves the pdbg target and libekb initialization to
the PEL startup which avoids the need of multiple initialization as
the existing design.

When a PEL calls out a DIMM FRU, the DRAM manufacturer ID and the
expanded location code of those DIMMs are added to the SysInfo user
data section  of the generated PEL in JSON format under the key 'DIMMs
Additional Info'.

In case of  any errors occur during the collection or processing of
the manufacturer data, the error messages will be logged in the 'PEL
Internal Debug Data' section as a JSON array under the key 'DIMMs Info
Fetch Error' as a separate user data section.

Tested :

Below is a portion of PEL(callout section and User Data section are
shown) which callout the DIMM P0-C32.

```
"Hex Word 9":               "00000000",
    "Callout Section": {
        "Callout Count":        "1",
        "Callouts": [{
            "FRU Type":         "Normal Hardware FRU",
            "Priority":         "Mandatory, replace all with this
                                 type as a unit",
            "Location Code":    "UXXX.YYY.WWW004A-P0-C32",
            "Part Number":      "7777777",
            "CCIN":             "1234",
            "Serial Number":    "YYYYYY"
        }]
    }
```
"User Data": {
    "Section Version": "1",
    "Sub-section type": "1",
    "Created by": "bmc error logging",
    "BMCLoad": "0.65 0.69 0.64",
    "BMCState": "Ready",
    "BMCUptime": "0y 0d 0h 17m 43s",
    "BootState": "Unspecified",
    "ChassisState": "Off",
    "DIMMs Additional Info": [
        {
            "DRAM Manufacturer ID": [
                "0x88",
                "0xAA"
            ]
            "Location Code": "UXXX.YYY.WWW004A-P0-C32",
        }
    ],
    "FW Version ID": "fw1060.20-4-1060.2432.20240729a (NL1060_068)",
    "HostState": "Off",
    "System IM": "50001001"
}
```

Change-Id: I2ff81c66e63b99e8e84378ec78f586fb9b6322d7
Signed-off-by: Arya K Padman <aryakpadman@gmail.com>
diff --git a/test/openpower-pels/meson.build b/test/openpower-pels/meson.build
index b6c6a94..d2a44c9 100644
--- a/test/openpower-pels/meson.build
+++ b/test/openpower-pels/meson.build
@@ -71,7 +71,6 @@
     libpel_sources,
     peltool_sources,
     '../common.cpp',
-    '../../util.cpp',
     include_directories: include_directories(
         '../../',
         '../../gen',
diff --git a/test/openpower-pels/mocks.hpp b/test/openpower-pels/mocks.hpp
index dd9e3c1..3b74871 100644
--- a/test/openpower-pels/mocks.hpp
+++ b/test/openpower-pels/mocks.hpp
@@ -66,6 +66,8 @@
     MOCK_METHOD(std::vector<uint32_t>, getLogIDWithHwIsolation, (),
                 (const override));
     MOCK_METHOD(std::vector<uint8_t>, getRawProgressSRC, (), (const override));
+    MOCK_METHOD(std::optional<std::vector<uint8_t>>, getDIProperty,
+                (const std::string&), (const override));
 
     void changeHostState(bool newState)
     {
diff --git a/test/openpower-pels/pel_test.cpp b/test/openpower-pels/pel_test.cpp
index 8369b05..45328cc 100644
--- a/test/openpower-pels/pel_test.cpp
+++ b/test/openpower-pels/pel_test.cpp
@@ -21,6 +21,7 @@
 
 #include <filesystem>
 #include <fstream>
+#include <optional>
 
 #include <gtest/gtest.h>
 
@@ -908,7 +909,10 @@
             }
         )"_json;
 
-        EXPECT_EQ(actualJSON, expectedJSON);
+        EXPECT_TRUE(
+            actualJSON.contains("/PEL Internal Debug Data/SRC"_json_pointer));
+        EXPECT_EQ(actualJSON["PEL Internal Debug Data"]["SRC"],
+                  expectedJSON["PEL Internal Debug Data"]["SRC"]);
     }
 
     {
@@ -946,7 +950,10 @@
             "[\"Problem looking up I2C callouts on 14 153: "
             "[json.exception.out_of_range.403] key '153' not found\"]}}"_json;
 
-        EXPECT_EQ(actualJSON, expectedJSON);
+        EXPECT_TRUE(
+            actualJSON.contains("/PEL Internal Debug Data/SRC"_json_pointer));
+        EXPECT_EQ(actualJSON["PEL Internal Debug Data"]["SRC"],
+                  expectedJSON["PEL Internal Debug Data"]["SRC"]);
     }
 
     fs::remove_all(dataPath);
@@ -1267,3 +1274,250 @@
         checkJournalSection(pel.optionalSections().back(), expected4);
     }
 }
+
+// API to collect and parse the User Data section of the PEL.
+nlohmann::json getDIMMInfo(const auto& pel)
+{
+    nlohmann::json dimmInfo{};
+    auto hasDIMMInfo = [&dimmInfo](const auto& optionalSection) {
+        if (optionalSection->header().id !=
+            static_cast<uint16_t>(SectionID::userData))
+        {
+            return false;
+        }
+        else
+        {
+            auto userData = static_cast<UserData*>(optionalSection.get());
+
+            // convert the userdata section to string and then parse in to json
+            // format
+            std::string userDataString{userData->data().begin(),
+                                       userData->data().end()};
+            nlohmann::json userDataJson = nlohmann::json::parse(userDataString);
+
+            if (userDataJson.contains("DIMMs Additional Info"))
+            {
+                dimmInfo = userDataJson.at("DIMMs Additional Info");
+            }
+            else if (
+                userDataJson.contains(
+                    "/PEL Internal Debug Data/DIMMs Info Fetch Error"_json_pointer))
+            {
+                dimmInfo = userDataJson.at(
+                    "/PEL Internal Debug Data/DIMMs Info Fetch Error"_json_pointer);
+            }
+            else
+            {
+                return false;
+            }
+            return true;
+        }
+    };
+    std::ranges::any_of(pel.optionalSections(), hasDIMMInfo);
+
+    return dimmInfo;
+}
+
+// Test whether the DIMM callouts manufacturing info is getting added to the
+// SysInfo User Data section of the PEL
+TEST_F(PELTest, TestDimmsCalloutInfo)
+{
+    {
+        message::Entry entry;
+        uint64_t timestamp = 5;
+        AdditionalData ad;
+        NiceMock<MockDataInterface> dataIface;
+        NiceMock<MockJournal> journal;
+        PelFFDC ffdc;
+
+        // When callouts contain DIMM callouts.
+        entry.callouts = R"(
+        [
+            {
+                "CalloutList": [
+                    {
+                        "Priority": "high",
+                        "LocCode": "P0-DIMM0"
+                    },
+                    {
+                        "Priority": "low",
+                        "LocCode": "P0-DIMM1"
+                    }
+                ]
+            }
+        ]
+        )"_json;
+
+        EXPECT_CALL(dataIface, expandLocationCode("P0-DIMM0", 0))
+            .WillOnce(Return("U98D-P0-DIMM0"));
+        EXPECT_CALL(dataIface, expandLocationCode("P0-DIMM1", 0))
+            .WillOnce(Return("U98D-P0-DIMM1"));
+
+        EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-DIMM0", 0, false))
+            .WillOnce(Return(std::vector<std::string>{
+                "/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm0"}));
+        EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-DIMM1", 0, false))
+            .WillOnce(Return(std::vector<std::string>{
+                "/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm1"}));
+
+        std::vector<uint8_t> diValue{128, 74};
+        EXPECT_CALL(dataIface, getDIProperty("U98D-P0-DIMM0"))
+            .WillOnce(Return(diValue));
+        EXPECT_CALL(dataIface, getDIProperty("U98D-P0-DIMM1"))
+            .WillOnce(Return(diValue));
+
+        // Add some location code in expanded format to DIMM cache memory
+        dataIface.addDIMMLocCode("U98D-P0-DIMM0", true);
+        dataIface.addDIMMLocCode("U98D-P0-DIMM1", true);
+
+        PEL pel{entry, 42,   timestamp, phosphor::logging::Entry::Level::Error,
+                ad,    ffdc, dataIface, journal};
+        nlohmann::json dimmInfoJson = getDIMMInfo(pel);
+
+        nlohmann::json expected_data = R"(
+        [
+            {
+                "Location Code": "U98D-P0-DIMM0",
+                "DRAM Manufacturer ID": [
+                    "0x80",
+                    "0x4a"
+                ]
+            },
+            {
+                "Location Code": "U98D-P0-DIMM1",
+                "DRAM Manufacturer ID": [
+                    "0x80",
+                    "0x4a"
+                ]
+            }
+        ]
+        )"_json;
+        EXPECT_EQ(expected_data, dimmInfoJson);
+    }
+}
+
+// When PEL has FRU callouts but PHAL is not enabled.
+TEST_F(PELTest, TestDimmsCalloutInfoWithNoPHAL)
+{
+    message::Entry entry;
+    uint64_t timestamp = 5;
+    AdditionalData ad;
+    NiceMock<MockDataInterface> dataIface;
+    NiceMock<MockJournal> journal;
+    PelFFDC ffdc;
+
+    entry.callouts = R"(
+        [
+            {
+                "CalloutList": [
+                    {
+                        "Priority": "high",
+                        "LocCode": "P0-DIMM0"
+                    },
+                    {
+                        "Priority": "low",
+                        "LocCode": "P0-DIMM1"
+                    }
+                ]
+            }
+        ]
+        )"_json;
+
+    EXPECT_CALL(dataIface, expandLocationCode("P0-DIMM0", 0))
+        .WillOnce(Return("U98D-P0-DIMM0"));
+    EXPECT_CALL(dataIface, expandLocationCode("P0-DIMM1", 0))
+        .WillOnce(Return("U98D-P0-DIMM1"));
+
+    EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-DIMM0", 0, false))
+        .WillOnce(Return(std::vector<std::string>{
+            "/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm0"}));
+    EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-DIMM1", 0, false))
+        .WillOnce(Return(std::vector<std::string>{
+            "/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm1"}));
+
+    PEL pel{entry, 42,   timestamp, phosphor::logging::Entry::Level::Error,
+            ad,    ffdc, dataIface, journal};
+
+    nlohmann::json dimmInfoJson = getDIMMInfo(pel);
+
+    nlohmann::json expected_data = R"(
+        [
+            "PHAL feature is not enabled, so the LocationCode:[U98D-P0-DIMM0] cannot be determined as DIMM",
+            "PHAL feature is not enabled, so the LocationCode:[U98D-P0-DIMM1] cannot be determined as DIMM"
+        ]
+    )"_json;
+
+    EXPECT_EQ(expected_data, dimmInfoJson);
+}
+
+// When the PEL doesn't contain any type of callouts
+TEST_F(PELTest, TestDimmsCalloutInfoWithNoCallouts)
+{
+    message::Entry entry;
+    uint64_t timestamp = 5;
+    AdditionalData ad;
+    NiceMock<MockDataInterface> dataIface;
+    NiceMock<MockJournal> journal;
+    PelFFDC ffdc;
+
+    PEL pel{entry, 42,   timestamp, phosphor::logging::Entry::Level::Error,
+            ad,    ffdc, dataIface, journal};
+
+    nlohmann::json dimmInfoJson = getDIMMInfo(pel);
+
+    nlohmann::json expected_data{};
+
+    EXPECT_EQ(expected_data, dimmInfoJson);
+}
+
+// When the PEL has DIMM callouts, but failed to fetch DI property value
+TEST_F(PELTest, TestDimmsCalloutInfoDIFailure)
+{
+    {
+        message::Entry entry;
+        uint64_t timestamp = 5;
+        AdditionalData ad;
+        NiceMock<MockDataInterface> dataIface;
+        NiceMock<MockJournal> journal;
+        PelFFDC ffdc;
+
+        entry.callouts = R"(
+        [
+            {
+                "CalloutList": [
+                    {
+                        "Priority": "high",
+                        "LocCode": "P0-DIMM0"
+                    }
+                ]
+            }
+        ]
+        )"_json;
+
+        EXPECT_CALL(dataIface, expandLocationCode("P0-DIMM0", 0))
+            .WillOnce(Return("U98D-P0-DIMM0"));
+
+        EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-DIMM0", 0, false))
+            .WillOnce(Return(std::vector<std::string>{
+                "/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm0"}));
+
+        EXPECT_CALL(dataIface, getDIProperty("U98D-P0-DIMM0"))
+            .WillOnce(Return(std::nullopt));
+
+        // Add some location code in expanded format to DIMM cache memory
+        dataIface.addDIMMLocCode("U98D-P0-DIMM0", true);
+
+        PEL pel{entry, 42,   timestamp, phosphor::logging::Entry::Level::Error,
+                ad,    ffdc, dataIface, journal};
+
+        nlohmann::json dimmInfoJson = getDIMMInfo(pel);
+
+        nlohmann::json expected_data = R"(
+            [
+                "Failed reading DI property from VINI Interface for the LocationCode:[U98D-P0-DIMM0]"
+            ]
+        )"_json;
+
+        EXPECT_EQ(expected_data, dimmInfoJson);
+    }
+}