PEL: Save the motherboard CCIN in the SRC

The CCIN field is from the CC keyword of the VINI record in the
motherboard VPD.

Save it in the first half of the second hex word of the SRC.

Also print this field when displaying the SRC section, but only for SRCs
created by this code, which it knows by checking the first 2 characters
of the ASCII string.

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: I25f94f7fbcfd3212adf28c357e55271ea9269add
diff --git a/extensions/openpower-pels/pel.cpp b/extensions/openpower-pels/pel.cpp
index 60821b6..568b4c6 100644
--- a/extensions/openpower-pels/pel.cpp
+++ b/extensions/openpower-pels/pel.cpp
@@ -48,7 +48,7 @@
                                           timestamp);
     _uh = std::make_unique<UserHeader>(entry, severity);
 
-    auto src = std::make_unique<SRC>(entry, additionalData);
+    auto src = std::make_unique<SRC>(entry, additionalData, dataIface);
 
     auto euh = std::make_unique<ExtendedUserHeader>(dataIface, entry, *src);
 
diff --git a/extensions/openpower-pels/src.cpp b/extensions/openpower-pels/src.cpp
index db84347..4067fe8 100644
--- a/extensions/openpower-pels/src.cpp
+++ b/extensions/openpower-pels/src.cpp
@@ -29,6 +29,8 @@
 namespace rg = openpower::pels::message;
 using namespace phosphor::logging;
 
+constexpr size_t ccinSize = 4;
+
 void SRC::unflatten(Stream& stream)
 {
     stream >> _header >> _version >> _flags >> _reserved1B >> _wordCount >>
@@ -80,7 +82,8 @@
     }
 }
 
-SRC::SRC(const message::Entry& regEntry, const AdditionalData& additionalData)
+SRC::SRC(const message::Entry& regEntry, const AdditionalData& additionalData,
+         const DataInterfaceBase& dataIface)
 {
     _header.id = static_cast<uint16_t>(SectionID::primarySRC);
     _header.version = srcSectionVersion;
@@ -106,6 +109,8 @@
                   [](auto& word) { word = 0; });
     setBMCFormat();
     setBMCPosition();
+    setMotherboardCCIN(dataIface);
+
     // Partition dump status and partition boot type always 0 for BMC errors.
     //
     // TODO: Fill in other fields that aren't available yet.
@@ -160,6 +165,29 @@
     }
 }
 
+void SRC::setMotherboardCCIN(const DataInterfaceBase& dataIface)
+{
+    uint32_t ccin = 0;
+    auto ccinString = dataIface.getMotherboardCCIN();
+
+    try
+    {
+        if (ccinString.size() == ccinSize)
+        {
+            ccin = std::stoi(ccinString, 0, 16);
+        }
+    }
+    catch (std::exception& e)
+    {
+        log<level::WARNING>("Could not convert motherboard CCIN to a number",
+                            entry("CCIN=%s", ccinString.c_str()));
+        return;
+    }
+
+    // Set the first 2 bytes
+    _hexData[1] |= ccin << 16;
+}
+
 void SRC::validate()
 {
     bool failed = false;
@@ -182,16 +210,25 @@
     _valid = failed ? false : true;
 }
 
+bool SRC::isBMCSRC() const
+{
+    auto as = asciiString();
+    if (as.length() >= 2)
+    {
+        uint8_t errorType = strtoul(as.substr(0, 2).c_str(), nullptr, 16);
+        return (errorType == static_cast<uint8_t>(SRCType::bmcError) ||
+                errorType == static_cast<uint8_t>(SRCType::powerError));
+    }
+    return false;
+}
+
 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))
+    if (isBMCSRC())
     {
         auto entry = registry.lookup("0x" + asciiString().substr(4, 4),
                                      rg::LookupType::reasonCode, toCache);
@@ -411,6 +448,20 @@
                pv::boolString.at(_flags & hypDumpInit), 1);
     jsonInsert(ps, "Power Control Net Fault",
                pv::boolString.at(isPowerFaultEvent()), 1);
+
+    if (isBMCSRC())
+    {
+        std::string ccinString;
+        uint32_t ccin = _hexData[1] >> 16;
+
+        if (ccin)
+        {
+            ccinString = getNumberString("%04X", ccin);
+        }
+        // The PEL spec calls it a backplane, so call it that here.
+        jsonInsert(ps, "Backplane CCIN", ccinString, 1);
+    }
+
     rg::Registry registry(getMessageRegistryPath() / rg::registryFileName);
     auto errorDetails = getErrorDetails(registry, DetailLevel::json);
     if (errorDetails)
diff --git a/extensions/openpower-pels/src.hpp b/extensions/openpower-pels/src.hpp
index 2296f6f..7cfa311 100644
--- a/extensions/openpower-pels/src.hpp
+++ b/extensions/openpower-pels/src.hpp
@@ -3,6 +3,7 @@
 #include "additional_data.hpp"
 #include "ascii_string.hpp"
 #include "callouts.hpp"
+#include "data_interface.hpp"
 #include "pel_types.hpp"
 #include "registry.hpp"
 #include "section.hpp"
@@ -82,8 +83,10 @@
      * @param[in] regEntry - The message registry entry for this event log
      * @param[in] additionalData - The AdditionalData properties in this event
      *                             log
+     * @param[in] dataIface - The DataInterface object
      */
-    SRC(const message::Entry& regEntry, const AdditionalData& additionalData);
+    SRC(const message::Entry& regEntry, const AdditionalData& additionalData,
+        const DataInterfaceBase& dataIface);
 
     /**
      * @brief Flatten the section into the stream
@@ -237,6 +240,13 @@
                                                DetailLevel type,
                                                bool toCache = false) const;
 
+    /**
+     * @brief Says if this SRC was created by the BMC (i.e. this code).
+     *
+     * @return bool - If created by the BMC or not
+     */
+    bool isBMCSRC() const;
+
   private:
     /**
      * @brief Fills in the user defined hex words from the
@@ -299,6 +309,13 @@
     }
 
     /**
+     * @brief Sets the motherboard CCIN hex word field
+     *
+     * @param[in] dataIface - The DataInterface object
+     */
+    void setMotherboardCCIN(const DataInterfaceBase& dataIface);
+
+    /**
      * @brief Validates the section contents
      *
      * Updates _valid (in Section) with the results.
diff --git a/test/openpower-pels/mocks.hpp b/test/openpower-pels/mocks.hpp
index 8fb6506..2457c0e 100644
--- a/test/openpower-pels/mocks.hpp
+++ b/test/openpower-pels/mocks.hpp
@@ -28,6 +28,7 @@
     MOCK_METHOD(std::string, getBMCState, (), (const override));
     MOCK_METHOD(std::string, getChassisState, (), (const override));
     MOCK_METHOD(std::string, getHostState, (), (const override));
+    MOCK_METHOD(std::string, getMotherboardCCIN, (), (const override));
 
     void changeHostState(bool newState)
     {
diff --git a/test/openpower-pels/src_test.cpp b/test/openpower-pels/src_test.cpp
index b966344..383c7e1 100644
--- a/test/openpower-pels/src_test.cpp
+++ b/test/openpower-pels/src_test.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 #include "extensions/openpower-pels/src.hpp"
+#include "mocks.hpp"
 #include "pel_utils.hpp"
 
 #include <fstream>
@@ -21,6 +22,8 @@
 #include <gtest/gtest.h>
 
 using namespace openpower::pels;
+using ::testing::NiceMock;
+using ::testing::Return;
 namespace fs = std::filesystem;
 
 const auto testRegistry = R"(
@@ -193,7 +196,11 @@
     std::vector<std::string> adData{"TEST1=0x12345678", "TEST2=12345678",
                                     "TEST3=0XDEF", "TEST4=Z"};
     AdditionalData ad{adData};
-    SRC src{entry, ad};
+    NiceMock<MockDataInterface> dataIface;
+
+    EXPECT_CALL(dataIface, getMotherboardCCIN).WillOnce(Return("ABCD"));
+
+    SRC src{entry, ad, dataIface};
 
     EXPECT_TRUE(src.valid());
     EXPECT_TRUE(src.isPowerFaultEvent());
@@ -208,6 +215,7 @@
     EXPECT_EQ(hexwords[2 - 2] & 0x00F00000, 0);    // Partition boot type
     EXPECT_EQ(hexwords[2 - 2] & 0x000000FF, 0x55); // SRC format
     EXPECT_EQ(hexwords[3 - 2] & 0x000000FF, 0x10); // BMC position
+    EXPECT_EQ(hexwords[3 - 2] & 0xFFFF0000, 0xABCD0000); // Motherboard CCIN
 
     // Validate more fields here as the code starts filling them in.
 
@@ -239,6 +247,49 @@
     EXPECT_FALSE(newSRC.callouts());
 }
 
+// Test when the CCIN string isn't a 4 character number
+TEST_F(SRCTest, BadCCINTest)
+{
+    message::Entry entry;
+    entry.src.type = 0xBD;
+    entry.src.reasonCode = 0xABCD;
+    entry.subsystem = 0x42;
+    entry.src.powerFault = false;
+
+    std::vector<std::string> adData{};
+    AdditionalData ad{adData};
+    NiceMock<MockDataInterface> dataIface;
+
+    // First it isn't a number, then it is too long,
+    // then it is empty.
+    EXPECT_CALL(dataIface, getMotherboardCCIN)
+        .WillOnce(Return("X"))
+        .WillOnce(Return("12345"))
+        .WillOnce(Return(""));
+
+    // The CCIN in the first half should still be 0 each time.
+    {
+        SRC src{entry, ad, dataIface};
+        EXPECT_TRUE(src.valid());
+        const auto& hexwords = src.hexwordData();
+        EXPECT_EQ(hexwords[3 - 2] & 0xFFFF0000, 0x00000000);
+    }
+
+    {
+        SRC src{entry, ad, dataIface};
+        EXPECT_TRUE(src.valid());
+        const auto& hexwords = src.hexwordData();
+        EXPECT_EQ(hexwords[3 - 2] & 0xFFFF0000, 0x00000000);
+    }
+
+    {
+        SRC src{entry, ad, dataIface};
+        EXPECT_TRUE(src.valid());
+        const auto& hexwords = src.hexwordData();
+        EXPECT_EQ(hexwords[3 - 2] & 0xFFFF0000, 0x00000000);
+    }
+}
+
 // Test the getErrorDetails function
 TEST_F(SRCTest, MessageSubstitutionTest)
 {
@@ -249,8 +300,9 @@
     std::vector<std::string> adData{"COMPID=0x1", "FREQUENCY=0x4",
                                     "DURATION=30", "ERRORCODE=0x01ABCDEF"};
     AdditionalData ad{adData};
+    NiceMock<MockDataInterface> dataIface;
 
-    SRC src{*entry, ad};
+    SRC src{*entry, ad, dataIface};
     EXPECT_TRUE(src.valid());
 
     auto errorDetails = src.getErrorDetails(registry, DetailLevel::message);