Add PEL creation for SBE chip-op failures

This commit introduces a new function within the SBE dump collection
mechanism to create PELs) when chip operations on SBE fail.

Test:
Make sure the PELs are logged during chip-op failure

Signed-off-by: Dhruvaraj Subhashchandran <dhruvaraj@in.ibm.com>
Change-Id: I23f78b702f4a2b9cf01315c3fbb487dc428bd2a0
diff --git a/dump/create_pel.cpp b/dump/create_pel.cpp
new file mode 100644
index 0000000..916d312
--- /dev/null
+++ b/dump/create_pel.cpp
@@ -0,0 +1,191 @@
+#include "create_pel.hpp"
+
+#include "dump_utils.hpp"
+
+#include <fcntl.h>
+#include <libekb.H>
+#include <unistd.h>
+
+#include <phosphor-logging/elog.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <xyz/openbmc_project/Logging/Create/server.hpp>
+#include <xyz/openbmc_project/Logging/Entry/server.hpp>
+
+#include <cerrno>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <format>
+#include <map>
+#include <stdexcept>
+#include <string>
+#include <tuple>
+#include <vector>
+
+namespace openpower::dump::pel
+{
+
+using namespace phosphor::logging;
+constexpr auto loggingObjectPath = "/xyz/openbmc_project/logging";
+constexpr auto loggingInterface = "xyz.openbmc_project.Logging.Create";
+constexpr auto opLoggingInterface = "org.open_power.Logging.PEL";
+
+uint32_t createSbeErrorPEL(const std::string& event, const sbeError_t& sbeError,
+                           const FFDCData& ffdcData)
+{
+    uint32_t plid = 0;
+    std::unordered_map<std::string, std::string> additionalData = {
+        {"_PID", std::to_string(getpid())}, {"SBE_ERR_MSG", sbeError.what()}};
+    auto bus = sdbusplus::bus::new_default();
+
+    additionalData.emplace("_PID", std::to_string(getpid()));
+    additionalData.emplace("SBE_ERR_MSG", sbeError.what());
+
+    for (auto& data : ffdcData)
+    {
+        additionalData.emplace(data);
+    }
+
+    std::vector<std::tuple<
+        sdbusplus::xyz::openbmc_project::Logging::server::Create::FFDCFormat,
+        uint8_t, uint8_t, sdbusplus::message::unix_fd>>
+        pelFFDCInfo;
+
+    // get SBE ffdc file descriptor
+    auto fd = sbeError.getFd();
+
+    // Negative fd value indicates error case or invalid file
+    // No need of special processing , just log error with additional ffdc.
+    if (fd > 0)
+    {
+        // Refer phosphor-logging/extensions/openpower-pels/README.md section
+        // "Self Boot Engine(SBE) First Failure Data Capture(FFDC) Support"
+        // for details of related to createPEL with SBE FFDC information
+        // usin g CreateWithFFDCFiles api.
+        pelFFDCInfo.emplace_back(
+            std::make_tuple(sdbusplus::xyz::openbmc_project::Logging::server::
+                                Create::FFDCFormat::Custom,
+                            static_cast<uint8_t>(0xCB),
+                            static_cast<uint8_t>(0x01), sbeError.getFd()));
+    }
+    try
+    {
+        auto service = util::getService(bus, opLoggingInterface,
+                                        loggingObjectPath);
+        auto method = bus.new_method_call(service.c_str(), loggingObjectPath,
+                                          opLoggingInterface,
+                                          "CreatePELWithFFDCFiles");
+        auto level =
+            sdbusplus::xyz::openbmc_project::Logging::server::convertForMessage(
+                sdbusplus::xyz::openbmc_project::Logging::server::Entry::Level::
+                    Error);
+        method.append(event, level, additionalData, pelFFDCInfo);
+        auto response = bus.call(method);
+
+        // reply will be tuple containing bmc log id, platform log id
+        std::tuple<uint32_t, uint32_t> reply = {0, 0};
+
+        // parse dbus response into reply
+        response.read(reply);
+        plid = std::get<1>(reply); // platform log id is tuple "second"
+    }
+    catch (const sdbusplus::exception::exception& e)
+    {
+        lg2::error(
+            "D-Bus call exception OBJPATH={OBJPATH}, INTERFACE={INTERFACE}, "
+            "EXCEPTION={ERROR}",
+            "OBJPATH", loggingObjectPath, "INTERFACE", loggingInterface,
+            "ERROR", e);
+
+        throw;
+    }
+    catch (const std::exception& e)
+    {
+        throw;
+    }
+
+    return plid;
+}
+
+FFDCFile::FFDCFile(const json& pHALCalloutData) :
+    calloutData(pHALCalloutData.dump()),
+    calloutFile("/tmp/phalPELCalloutsJson.XXXXXX"), fileFD(-1)
+{
+    prepareFFDCFile();
+}
+
+FFDCFile::~FFDCFile()
+{
+    removeCalloutFile();
+}
+
+int FFDCFile::getFileFD() const
+{
+    return fileFD;
+}
+
+void FFDCFile::prepareFFDCFile()
+{
+    createCalloutFile();
+    writeCalloutData();
+    setCalloutFileSeekPos();
+}
+
+void FFDCFile::createCalloutFile()
+{
+    fileFD = mkostemp(const_cast<char*>(calloutFile.c_str()), O_RDWR);
+
+    if (fileFD == -1)
+    {
+        lg2::error("Failed to create phalPELCallouts file({FILE}), "
+                   "errorno({ERRNO}) and errormsg({ERRORMSG})",
+                   "FILE", calloutFile, "ERRNO", errno, "ERRORMSG",
+                   strerror(errno));
+        throw std::runtime_error("Failed to create phalPELCallouts file");
+    }
+}
+
+void FFDCFile::writeCalloutData()
+{
+    ssize_t rc = write(fileFD, calloutData.c_str(), calloutData.size());
+
+    if (rc == -1)
+    {
+        lg2::error("Failed to write phaPELCallout info in file({FILE}), "
+                   "errorno({ERRNO}), errormsg({ERRORMSG})",
+                   "FILE", calloutFile, "ERRNO", errno, "ERRORMSG",
+                   strerror(errno));
+        throw std::runtime_error("Failed to write phalPELCallouts info");
+    }
+    else if (rc != static_cast<ssize_t>(calloutData.size()))
+    {
+        lg2::warning("Could not write all phal callout info in file({FILE}), "
+                     "written byte({WRITTEN}), total byte({TOTAL})",
+                     "FILE", calloutFile, "WRITTEN", rc, "TOTAL",
+                     calloutData.size());
+    }
+}
+
+void FFDCFile::setCalloutFileSeekPos()
+{
+    int rc = lseek(fileFD, 0, SEEK_SET);
+
+    if (rc == -1)
+    {
+        lg2::error("Failed to set SEEK_SET for phalPELCallouts in "
+                   "file({FILE}), errorno({ERRNO}), errormsg({ERRORMSG})",
+                   "FILE", calloutFile, "ERRNO", errno, "ERRORMSG",
+                   strerror(errno));
+
+        throw std::runtime_error(
+            "Failed to set SEEK_SET for phalPELCallouts file");
+    }
+}
+
+void FFDCFile::removeCalloutFile()
+{
+    close(fileFD);
+    std::remove(calloutFile.c_str());
+}
+
+} // namespace openpower::dump::pel
diff --git a/dump/create_pel.hpp b/dump/create_pel.hpp
new file mode 100644
index 0000000..13d30ff
--- /dev/null
+++ b/dump/create_pel.hpp
@@ -0,0 +1,114 @@
+#pragma once
+
+#include <phal_exception.H>
+
+#include <nlohmann/json.hpp>
+
+#include <string>
+#include <vector>
+namespace openpower::dump::pel
+{
+
+using FFDCData = std::vector<std::pair<std::string, std::string>>;
+
+using json = nlohmann::json;
+
+using namespace openpower::phal;
+
+/**
+ * @brief Create SBE boot error PEL and return id
+ *
+ * @param[in] event - the event type
+ * @param[in] sbeError - SBE error object
+ * @param[in] ffdcData - failure data to append to PEL
+ * @return Platform log id
+ */
+uint32_t createSbeErrorPEL(const std::string& event, const sbeError_t& sbeError,
+                           const FFDCData& ffdcData);
+
+/**
+ * @class FFDCFile
+ * @brief This class is used to create ffdc data file and to get fd
+ */
+class FFDCFile
+{
+  public:
+    FFDCFile() = delete;
+    FFDCFile(const FFDCFile&) = delete;
+    FFDCFile& operator=(const FFDCFile&) = delete;
+    FFDCFile(FFDCFile&&) = delete;
+    FFDCFile& operator=(FFDCFile&&) = delete;
+
+    /**
+     * Used to pass json object to create unique ffdc file by using
+     * passed json data.
+     */
+    explicit FFDCFile(const json& pHALCalloutData);
+
+    /**
+     * Used to remove created ffdc file.
+     */
+    ~FFDCFile();
+
+    /**
+     * Used to get created ffdc file file descriptor id.
+     *
+     * @return file descriptor id
+     */
+    int getFileFD() const;
+
+  private:
+    /**
+     * Used to store callout ffdc data from passed json object.
+     */
+    std::string calloutData;
+
+    /**
+     * Used to store unique ffdc file name.
+     */
+    std::string calloutFile;
+
+    /**
+     * Used to store created ffdc file descriptor id.
+     */
+    int fileFD;
+
+    /**
+     * Used to create ffdc file to pass PEL api for creating
+     * pel records.
+     *
+     * @return NULL
+     */
+    void prepareFFDCFile();
+
+    /**
+     * Create unique ffdc file.
+     *
+     * @return NULL
+     */
+    void createCalloutFile();
+
+    /**
+     * Used write json object value into created file.
+     *
+     * @return NULL
+     */
+    void writeCalloutData();
+
+    /**
+     * Used set ffdc file seek position begining to consume by PEL
+     *
+     * @return NULL
+     */
+    void setCalloutFileSeekPos();
+
+    /**
+     * Used to remove created ffdc file.
+     *
+     * @return NULL
+     */
+    void removeCalloutFile();
+
+}; // FFDCFile end
+
+} // namespace openpower::dump::pel
diff --git a/dump/meson.build b/dump/meson.build
index 45140d0..586b43e 100644
--- a/dump/meson.build
+++ b/dump/meson.build
@@ -16,6 +16,8 @@
     'sbe_dump_collector.cpp',
     'dump_collect_main.cpp',
     'dump_utils.cpp',
+    'create_pel.cpp',
+    'sbe_type.cpp',
 )
 
 executable('dump-collect',
diff --git a/dump/sbe_consts.hpp b/dump/sbe_consts.hpp
index 9fea1f0..d59a698 100644
--- a/dump/sbe_consts.hpp
+++ b/dump/sbe_consts.hpp
@@ -15,4 +15,9 @@
 // Collect the dumps with clock off
 constexpr auto SBE_CLOCK_OFF = 0x2;
 
+// Dump command class
+constexpr auto SBEFIFO_CMD_CLASS_DUMP = 0xAA00;
+
+// Get dump method
+constexpr auto SBEFIFO_CMD_GET_DUMP = 0x01;
 } // namespace openpower::dump::SBE
diff --git a/dump/sbe_dump_collector.cpp b/dump/sbe_dump_collector.cpp
index 119fdff..58a34e7 100644
--- a/dump/sbe_dump_collector.cpp
+++ b/dump/sbe_dump_collector.cpp
@@ -4,12 +4,13 @@
 #include <libpdbg_sbe.h>
 }
 
+#include "create_pel.hpp"
 #include "sbe_consts.hpp"
 #include "sbe_dump_collector.hpp"
+#include "sbe_type.hpp"
 
 #include <libphal.H>
-#include <sys/wait.h>
-#include <unistd.h>
+#include <phal_exception.H>
 
 #include <phosphor-logging/elog-errors.hpp>
 #include <phosphor-logging/lg2.hpp>
@@ -130,6 +131,27 @@
     return futures;
 }
 
+void SbeDumpCollector::logErrorAndCreatePEL(
+    const openpower::phal::sbeError_t& sbeError, uint64_t chipPos,
+    SBETypes sbeType, uint32_t cmdClass, uint32_t cmdType)
+{
+    try
+    {
+        std::string event = sbeTypeAttributes.at(sbeType).chipOpFailure;
+
+        openpower::dump::pel::FFDCData pelAdditionalData = {
+            {"SRC6", std::format("{:X}{:X}", chipPos, (cmdClass | cmdType))}};
+
+        openpower::dump::pel::createSbeErrorPEL(event, sbeError,
+                                                pelAdditionalData);
+    }
+    catch (const std::out_of_range& e)
+    {
+        lg2::error("Unknown SBE Type({SBETYPE}) ErrorMsg({ERROR})", "SBETYPE",
+                   sbeType, "ERROR", e);
+    }
+}
+
 void SbeDumpCollector::collectDumpFromSBE(struct pdbg_target* chip,
                                           const std::filesystem::path& path,
                                           uint32_t id, uint8_t type,
@@ -137,6 +159,8 @@
                                           uint64_t failingUnit)
 {
     auto chipPos = pdbg_target_index(chip);
+    SBETypes sbeType = getSBEType(chip);
+    auto chipName = sbeTypeAttributes.at(sbeType).chipName;
     lg2::info(
         "Collecting dump from proc({PROC}): path({PATH}) id({ID}) "
         "type({TYPE}) clockState({CLOCKSTATE}) failingUnit({FAILINGUNIT})",
@@ -175,13 +199,14 @@
 
         return;
     }
-    writeDumpFile(path, id, clockState, 0, "proc", chipPos, dataPtr, len);
+    writeDumpFile(path, id, clockState, 0, chipName, chipPos, dataPtr, len);
 }
 
 void SbeDumpCollector::writeDumpFile(
     const std::filesystem::path& path, const uint32_t id,
-    const uint8_t clockState, const uint8_t nodeNum, std::string chipName,
-    const uint8_t chipPos, util::DumpDataPtr& dataPtr, const uint32_t len)
+    const uint8_t clockState, const uint8_t nodeNum,
+    const std::string& chipName, const uint8_t chipPos,
+    util::DumpDataPtr& dataPtr, const uint32_t len)
 {
     using namespace sdbusplus::xyz::openbmc_project::Common::Error;
     namespace fileError = sdbusplus::xyz::openbmc_project::Common::File::Error;
diff --git a/dump/sbe_dump_collector.hpp b/dump/sbe_dump_collector.hpp
index 93ecdd4..2e99b53 100644
--- a/dump/sbe_dump_collector.hpp
+++ b/dump/sbe_dump_collector.hpp
@@ -8,6 +8,9 @@
 
 #include "dump_utils.hpp"
 #include "sbe_consts.hpp"
+#include "sbe_type.hpp"
+
+#include <phal_exception.H>
 
 #include <cstdint>
 #include <filesystem>
@@ -131,7 +134,7 @@
      */
     void writeDumpFile(const std::filesystem::path& path, const uint32_t id,
                        const uint8_t clockState, const uint8_t nodeNum,
-                       std::string chipName, const uint8_t chipPos,
+                       const std::string& chipName, const uint8_t chipPos,
                        util::DumpDataPtr& dataPtr, const uint32_t len);
 
     /**
@@ -160,6 +163,33 @@
                    ? 1
                    : 0;
     }
+
+    /**
+     * Logs an error and creates a PEL for SBE chip-op failures.
+     *
+     * @param sbeError - An error object encapsulating details about the SBE
+     * error.
+     * @param chipPos - The position of the chip where the error occurred.
+     * @param sbeType - The type of SBE, used to determine the event log
+     * message.
+     * @param cmdClass - The command class associated with the SBE operation.
+     * @param cmdType - The specific type of command within the command class.
+     *
+     */
+    void logErrorAndCreatePEL(const openpower::phal::sbeError_t& sbeError,
+                              uint64_t chipPos, SBETypes sbeType,
+                              uint32_t cmdClass, uint32_t cmdType);
+
+    /**
+     * Determines the type of SBE for a given chip target.
+     *
+     * @param chip - A pointer to a pdbg_target structure representing the chip.
+     * @return The SBE type for the given chip target.
+     */
+    inline SBETypes getSBEType([[maybe_unused]] struct pdbg_target* chip)
+    {
+        return SBETypes::PROC;
+    }
 };
 
 } // namespace openpower::dump::sbe_chipop
diff --git a/dump/sbe_type.cpp b/dump/sbe_type.cpp
new file mode 100644
index 0000000..41ae7cf
--- /dev/null
+++ b/dump/sbe_type.cpp
@@ -0,0 +1,10 @@
+#include "sbe_type.hpp"
+
+namespace openpower::dump
+{
+
+const std::map<SBETypes, SBEAttributes> sbeTypeAttributes = {
+    {SBETypes::PROC,
+     {"proc", "org.open_power.Processor.Error.SbeChipOpFailure"}}};
+
+} // namespace openpower::dump
diff --git a/dump/sbe_type.hpp b/dump/sbe_type.hpp
new file mode 100644
index 0000000..e46d030
--- /dev/null
+++ b/dump/sbe_type.hpp
@@ -0,0 +1,21 @@
+#pragma once
+
+#include <map>
+#include <string>
+
+namespace openpower::dump
+{
+
+enum class SBETypes
+{
+    PROC,
+};
+
+struct SBEAttributes
+{
+    std::string chipName;
+    std::string chipOpFailure;
+};
+
+extern const std::map<SBETypes, SBEAttributes> sbeTypeAttributes;
+} // namespace openpower::dump