Callout support for SBE when watchdog times out

This commit provides additional callout support for SBE when the
host does not respond within watchdog timeout interval during SBE
boot window.

Testing:
Steps used:
 1. obmcutil poweroff
 2. istep -s0
 3. systemctl start org.open_power.Dump.Manager.service
 4. systemctl start openpower-debug-collector-watchdog@0.service
 5. Check the journal log, and get the PEL id to print PEL info

PEL info:
    "Callout Section": {
        "Callout Count":        "2",
        "Callouts": [{
            "FRU Type":         "Maintenance Procedure Required",
            "Priority":         "Mandatory, replace all with this type
as a unit",
            "Procedure":        "BMC0002"
        }, {
            "FRU Type":         "Normal Hardware FRU",
            "Priority":         "Medium Priority",
            "Location Code":    "xxxxxxxxxxxxx",
            "Part Number":      "xxxxx",
            "CCIN":             "xxxx",
            "Serial Number":    "xxxxxxx"
        }
...
"User Data 2": {
    "Section Version": "1",
    "Sub-section type": "1",
    "Created by": "0x2000",
    "Data": [
        {
            "Priority": "H",
            "Procedure": "BMC0002"
        },
        {
            "Deconfigured": false,
            "Guarded": false,
            "LocationCode": "Ufcs-xx-xxx",
            "Priority": "M"
        }
    ]
}

Signed-off-by: Shantappa Teekappanavar <sbteeks@yahoo.com>
Change-Id: I5e182cb415a807d97c98812a6713905d39fdbc9a
diff --git a/watchdog/ffdc_file.cpp b/watchdog/ffdc_file.cpp
new file mode 100644
index 0000000..4fe5875
--- /dev/null
+++ b/watchdog/ffdc_file.cpp
@@ -0,0 +1,91 @@
+#include "ffdc_file.hpp"
+
+#include <errno.h> // for errno
+#include <fcntl.h> // for open()
+#include <fmt/format.h>
+#include <string.h>    // for strerror()
+#include <sys/stat.h>  // for open()
+#include <sys/types.h> // for open()
+
+#include <phosphor-logging/log.hpp>
+
+#include <stdexcept>
+#include <string>
+
+namespace watchdog
+{
+namespace dump
+{
+using namespace phosphor::logging;
+
+FFDCFile::FFDCFile(const json& calloutDataObject) :
+    calloutData(calloutDataObject.dump())
+{
+    prepareFFDCFile();
+}
+
+FFDCFile::~FFDCFile()
+{
+    // Close file descriptor.  Does nothing if descriptor was already closed.
+    if (descriptor.close() == -1)
+    {
+        log<level::ERR>(fmt::format("Unable to close FFDC file: errormsg({})",
+                                    strerror(errno))
+                            .c_str());
+    }
+
+    // Delete temporary file.  Does nothing if file was already deleted.
+    tempFile.remove();
+}
+
+void FFDCFile::prepareFFDCFile()
+{
+    // Open the temporary file for both reading and writing
+    int fd = open(tempFile.getPath().c_str(), O_RDWR);
+    if (fd == -1)
+    {
+        throw std::runtime_error{std::string{"Unable to open FFDC file: "} +
+                                 strerror(errno)};
+    }
+
+    ssize_t rc = write(fd, calloutData.c_str(), calloutData.size());
+
+    if (rc == -1)
+    {
+        log<level::ERR>(fmt::format("Failed to write callout info "
+                                    "in file({}), errorno({}), errormsg({})",
+                                    tempFile.getPath().c_str(), errno,
+                                    strerror(errno))
+                            .c_str());
+        throw std::runtime_error("Failed to write phalPELCallouts info");
+    }
+    else if (rc != static_cast<ssize_t>(calloutData.size()))
+    {
+        log<level::WARNING>(fmt::format("Could not write all callout "
+                                        "info in file({}), written byte({}) "
+                                        "and total byte({})",
+                                        tempFile.getPath().c_str(), rc,
+                                        calloutData.size())
+                                .c_str());
+    }
+
+    int retCode = lseek(fd, 0, SEEK_SET);
+
+    if (retCode == -1)
+    {
+        log<level::ERR>(
+            fmt::format("Failed to seek file postion to the beginning"
+                        "in file({}), errorno({}) "
+                        "and errormsg({})",
+                        tempFile.getPath().c_str(), errno, strerror(errno))
+                .c_str());
+        throw std::runtime_error(
+            "Failed to seek file postion to the beginning of the file");
+    }
+
+    // Store file descriptor in FileDescriptor object
+    descriptor.set(fd);
+}
+
+} // namespace dump
+} // namespace watchdog
diff --git a/watchdog/ffdc_file.hpp b/watchdog/ffdc_file.hpp
new file mode 100644
index 0000000..c7cc883
--- /dev/null
+++ b/watchdog/ffdc_file.hpp
@@ -0,0 +1,103 @@
+#pragma once
+
+#include "file_descriptor.hpp"
+#include "utils.hpp"
+#include "xyz/openbmc_project/Logging/Create/server.hpp"
+
+#include <nlohmann/json.hpp>
+
+#include <cstdint>
+#include <filesystem>
+
+namespace watchdog
+{
+namespace dump
+{
+
+namespace fs = std::filesystem;
+using FFDCFormat =
+    sdbusplus::xyz::openbmc_project::Logging::server::Create::FFDCFormat;
+using FFDCTuple =
+    std::tuple<FFDCFormat, uint8_t, uint8_t, sdbusplus::message::unix_fd>;
+
+using ::nlohmann::json;
+/**
+ * @class FFDCFile
+ *
+ * File that contains FFDC (first failure data capture) data in json format.
+ *
+ * This class is used to store FFDC json callout data in an error log.
+ */
+class FFDCFile
+{
+  public:
+    // Specify which compiler-generated methods we want
+    FFDCFile() = delete;
+    FFDCFile(const FFDCFile&) = delete;
+    FFDCFile(FFDCFile&&) = default;
+    FFDCFile& operator=(const FFDCFile&) = delete;
+    FFDCFile& operator=(FFDCFile&&) = default;
+    ~FFDCFile();
+
+    /**
+     * @brief Constructor
+     *
+     * @details Creates the FFDC file by using passed json data.
+     *
+     * Throws an exception if an error occurs.
+     */
+    explicit FFDCFile(const json& calloutData);
+
+    /**
+     * @brief Returns the file descriptor for the file.
+     *
+     * @details The file is open for both reading and writing.
+     *
+     * @return file descriptor
+     */
+    int getFileDescriptor() const
+    {
+        // Return the integer file descriptor within the FileDescriptor object
+        return descriptor();
+    }
+
+    /**
+     * @brief Returns the absolute path to the file.
+     *
+     * @return absolute path
+     */
+    const fs::path& getPath() const
+    {
+        return tempFile.getPath();
+    }
+
+  private:
+    /**
+     * @brief Temporary file where FFDC data is stored.
+     *
+     * @details The TemporaryFile destructor will automatically delete the file
+     * if it was not explicitly deleted using remove().
+     */
+    TemporaryFile tempFile{};
+
+    /**
+     * @brief File descriptor for reading from/writing to the file.
+     *
+     * @details The FileDescriptor destructor will automatically close the file
+     * if it was not explicitly closed using remove().
+     */
+    FileDescriptor descriptor{};
+
+    /**
+     * @brief Used to store callout ffdc data from passed json object
+     */
+    std::string calloutData;
+
+    /**
+     * @brief Creates FFDC file for creating PEL records.
+     */
+    void prepareFFDCFile();
+};
+
+} // namespace dump
+} // namespace watchdog
diff --git a/watchdog/file_descriptor.hpp b/watchdog/file_descriptor.hpp
new file mode 100644
index 0000000..8416d6b
--- /dev/null
+++ b/watchdog/file_descriptor.hpp
@@ -0,0 +1,140 @@
+#pragma once
+
+#include <unistd.h> // for close()
+
+namespace watchdog
+{
+namespace dump
+{
+
+/**
+ * @class FileDescriptor
+ *
+ * This class manages an open file descriptor.
+ *
+ * The file descriptor can be closed by calling close().  Otherwise it will be
+ * closed by the destructor.
+ *
+ * FileDescriptor objects cannot be copied, but they can be moved.  This enables
+ * them to be stored in containers like std::vector.
+ */
+class FileDescriptor
+{
+  public:
+    FileDescriptor() = default;
+    FileDescriptor(const FileDescriptor&) = delete;
+    FileDescriptor& operator=(const FileDescriptor&) = delete;
+
+    /**
+     * @brief Constructor
+     *
+     * @param[in] fd - File descriptor
+     */
+    explicit FileDescriptor(int fd) : fd(fd)
+    {}
+
+    /**
+     * @brief Move constructor.
+     *
+     * @details description ownership of a file descriptor.
+     *
+     * @param other - FileDescriptor object being moved
+     */
+    FileDescriptor(FileDescriptor&& other) : fd(other.fd)
+    {
+        other.fd = -1;
+    }
+
+    /**
+     * @brief Move assignment operator.
+     *
+     * @details description the file descriptor owned by this object, if any.
+     * Then transfers ownership of the file descriptor owned by the other
+     * object.
+     *
+     * @param other - FileDescriptor object being moved
+     */
+    FileDescriptor& operator=(FileDescriptor&& other)
+    {
+        // Verify not assigning object to itself (a = std::move(a))
+        if (this != &other)
+        {
+            set(other.fd);
+            other.fd = -1;
+        }
+        return *this;
+    }
+
+    /**
+     * @brief brief description.
+     *
+     * @details Closes the file descriptor if necessary.
+     */
+    ~FileDescriptor()
+    {
+        close();
+    }
+
+    /**
+     * @brief Returns the file descriptor.
+     *
+     * @return File descriptor.  Returns -1 if this object does not contain an
+     *         open file descriptor.
+     */
+    int operator()() const
+    {
+        return fd;
+    }
+
+    /**
+     * @brief Check whether this object contains an open file descriptor.
+     *
+     * @return true if object contains an open file descriptor, false otherwise.
+     */
+    operator bool() const
+    {
+        return fd != -1;
+    }
+
+    /**
+     * @brief Closes the file descriptor.
+     *
+     * @details Does nothing if the file descriptor was not set or was already
+     * closed.
+     *
+     * @return 0 if descriptor was successfully closed.  Returns -1 if an error
+     *         occurred; errno will be set appropriately.
+     */
+    int close()
+    {
+        int rc = 0;
+        if (fd >= 0)
+        {
+            rc = ::close(fd);
+            fd = -1;
+        }
+        return rc;
+    }
+
+    /**
+     * @brief Sets the file descriptor.
+     *
+     * @details Closes the previous file descriptor if necessary.
+     *
+     * @param[in] descriptor - File descriptor
+     */
+    void set(int descriptor)
+    {
+        (void)close();
+        fd = descriptor;
+    }
+
+  private:
+    /**
+     * @brief File descriptor.
+     */
+    int fd = -1;
+};
+
+} // namespace dump
+} // namespace watchdog
diff --git a/watchdog/meson.build b/watchdog/meson.build
index a75d0e3..53d1234 100644
--- a/watchdog/meson.build
+++ b/watchdog/meson.build
@@ -5,6 +5,8 @@
     'watchdog_handler.cpp',
     'watchdog_common.cpp',
     'watchdog_main.cpp',
+    'utils.cpp',
+    'ffdc_file.cpp',
 )
 
 # Library dependencies
diff --git a/watchdog/utils.cpp b/watchdog/utils.cpp
new file mode 100644
index 0000000..ae4a80b
--- /dev/null
+++ b/watchdog/utils.cpp
@@ -0,0 +1,80 @@
+#include "utils.hpp"
+
+#include <errno.h>  // for errno
+#include <stdlib.h> // for mkstemp()
+#include <string.h> // for strerror()
+#include <unistd.h> // for close()
+
+#include <stdexcept>
+#include <string>
+
+namespace watchdog
+{
+namespace dump
+{
+
+TemporaryFile::TemporaryFile()
+{
+    // Build template path required by mkstemp()
+    std::string templatePath =
+        fs::temp_directory_path() / "openpower-debug-collector-XXXXXX";
+
+    // Generate unique file name, create file, and open it.  The XXXXXX
+    // characters are replaced by mkstemp() to make the file name unique.
+    int fd = mkstemp(templatePath.data());
+    if (fd == -1)
+    {
+        throw std::runtime_error{
+            std::string{"Unable to create temporary file: "} + strerror(errno)};
+    }
+
+    // Store path to temporary file
+    path = templatePath;
+
+    // Close file descriptor
+    if (close(fd) == -1)
+    {
+        // Save errno value; will likely change when we delete temporary file
+        int savedErrno = errno;
+
+        // Delete temporary file.  The destructor won't be called because the
+        // exception below causes this constructor to exit without completing.
+        remove();
+
+        throw std::runtime_error{
+            std::string{"Unable to close temporary file: "} +
+            strerror(savedErrno)};
+    }
+}
+
+TemporaryFile& TemporaryFile::operator=(TemporaryFile&& file)
+{
+    // Verify not assigning object to itself (a = std::move(a))
+    if (this != &file)
+    {
+        // Delete temporary file owned by this object
+        remove();
+
+        // Move temporary file path from other object, transferring ownership
+        path = std::move(file.path);
+
+        // Clear path in other object; after move path is in unspecified state
+        file.path.clear();
+    }
+    return *this;
+}
+
+void TemporaryFile::remove()
+{
+    if (!path.empty())
+    {
+        // Delete temporary file from file system
+        fs::remove(path);
+
+        // Clear path to indicate file has been deleted
+        path.clear();
+    }
+}
+
+} // namespace dump
+} // namespace watchdog
diff --git a/watchdog/utils.hpp b/watchdog/utils.hpp
new file mode 100644
index 0000000..1b0d613
--- /dev/null
+++ b/watchdog/utils.hpp
@@ -0,0 +1,117 @@
+#pragma once
+
+#include <filesystem>
+#include <utility>
+
+namespace watchdog
+{
+namespace dump
+{
+
+namespace fs = std::filesystem;
+
+/**
+ * @class TemporaryFile
+ *
+ * A temporary file in the file system.
+ *
+ * The temporary file is created by the constructor.  The absolute path to the
+ * file can be obtained using getPath().
+ *
+ * The temporary file can be deleted by calling remove().  Otherwise the file
+ * will be deleted by the destructor.
+ *
+ * TemporaryFile objects cannot be copied, but they can be moved.  This enables
+ * them to be stored in containers like std::vector.
+ */
+class TemporaryFile
+{
+  public:
+    // Specify which compiler-generated methods we want
+    TemporaryFile(const TemporaryFile&) = delete;
+    TemporaryFile& operator=(const TemporaryFile&) = delete;
+
+    /**
+     * @brief Constructor.
+     *
+     * @details Creates a temporary file in the temporary directory (normally
+     * /tmp).
+     *
+     * Throws an exception if the file cannot be created.
+     */
+    TemporaryFile();
+
+    /**
+     * @brief Move constructor.
+     *
+     * @details Transfers ownership of a temporary file.
+     *
+     * @param file TemporaryFile object being moved
+     */
+    TemporaryFile(TemporaryFile&& file) : path{std::move(file.path)}
+    {
+        // Clear path in other object; after move path is in unspecified state
+        file.path.clear();
+    }
+
+    /**
+     * @brief Move assignment operator.
+     *
+     * @details Deletes the temporary file owned by this object.  Then transfers
+     * ownership of the temporary file owned by the other object.
+     *
+     * Throws an exception if an error occurs during the deletion.
+     *
+     * @param file TemporaryFile object being moved
+     */
+    TemporaryFile& operator=(TemporaryFile&& file);
+
+    /**
+     * @brief Destructor.
+     *
+     * @details description the temporary file if necessary.
+     */
+    ~TemporaryFile()
+    {
+        try
+        {
+            remove();
+        }
+        catch (...)
+        {
+            // Destructors should not throw exceptions
+        }
+    }
+
+    /**
+     * @brief Deletes the temporary file.
+     *
+     * @details Does nothing if the file has already been deleted.
+     *
+     * Throws an exception if an error occurs during the deletion.
+     */
+    void remove();
+
+    /**
+     * @brief Returns the absolute path to the temporary file.
+     *
+     * @details Returns an empty path if the file has been deleted.
+     *
+     * @return temporary file path
+     */
+    const fs::path& getPath() const
+    {
+        return path;
+    }
+
+  private:
+    /**
+     * @brief Absolute path to the temporary file.
+     *
+     * @details Empty when file has been deleted.
+     */
+    fs::path path{};
+};
+
+} // namespace dump
+} // namespace watchdog
diff --git a/watchdog/watchdog_dbus.cpp b/watchdog/watchdog_dbus.cpp
index dff46f4..ea1ea21 100644
--- a/watchdog/watchdog_dbus.cpp
+++ b/watchdog/watchdog_dbus.cpp
@@ -3,7 +3,6 @@
 #include <phosphor-logging/log.hpp>
 #include <watchdog_dbus.hpp>
 #include <watchdog_logging.hpp>
-#include <xyz/openbmc_project/State/Boot/Progress/server.hpp>
 
 #include <string>
 #include <vector>
diff --git a/watchdog/watchdog_dbus.hpp b/watchdog/watchdog_dbus.hpp
index 5de5691..f9568f7 100644
--- a/watchdog/watchdog_dbus.hpp
+++ b/watchdog/watchdog_dbus.hpp
@@ -1,7 +1,8 @@
 #pragma once
 
+#include "ffdc_file.hpp"
+
 #include <sdbusplus/bus.hpp>
-#include <xyz/openbmc_project/Logging/Create/server.hpp>
 
 #include <string>
 
@@ -17,12 +18,6 @@
     RC_DBUS_ERROR = 2
 };
 
-using FFDCFormat =
-    sdbusplus::xyz::openbmc_project::Logging::server::Create::FFDCFormat;
-
-using FFDCTuple =
-    std::tuple<FFDCFormat, uint8_t, uint8_t, sdbusplus::message::unix_fd>;
-
 /**
  * @brief Create a dbus method
  *
diff --git a/watchdog/watchdog_logging.cpp b/watchdog/watchdog_logging.cpp
index 1e993c0..1b7cd9f 100644
--- a/watchdog/watchdog_logging.cpp
+++ b/watchdog/watchdog_logging.cpp
@@ -1,5 +1,6 @@
 #include <unistd.h>
 
+#include <phosphor-logging/log.hpp>
 #include <watchdog_common.hpp>
 #include <watchdog_dbus.hpp>
 #include <watchdog_handler.hpp>
diff --git a/watchdog/watchdog_main.cpp b/watchdog/watchdog_main.cpp
index 274867a..4fa4887 100644
--- a/watchdog/watchdog_main.cpp
+++ b/watchdog/watchdog_main.cpp
@@ -35,6 +35,48 @@
     transitionHost(HOST_STATE_QUIESCE_TGT);
 }
 
+/**
+ * @brief get SBE special callout information
+ *
+ * @details This function adds the special sbe callout in the user provided
+ * json callout list. includes BMC0002 procedure callout with high priority
+ * and processor callout with medium priority.
+ *
+ * @param[in] procTarget - pdbg processor target
+ * @param[out] jsonCalloutDataList - reference to json callout list
+ */
+static void getSBECallout(struct pdbg_target* procTarget,
+                          json& jsonCalloutDataList)
+{
+    using namespace openpower::phal::pdbg;
+    json jsonProcedCallout;
+
+    // Add procedure callout
+    jsonProcedCallout["Procedure"] = "BMC0002";
+    jsonProcedCallout["Priority"] = "H";
+    jsonCalloutDataList.emplace_back(jsonProcedCallout);
+    try
+    {
+        ATTR_LOCATION_CODE_Type locationCode;
+        // Initialize with default data.
+        memset(&locationCode, '\0', sizeof(locationCode));
+        // Get location code information
+        openpower::phal::pdbg::getLocationCode(procTarget, locationCode);
+        json jsonProcCallout;
+        jsonProcCallout["LocationCode"] = locationCode;
+        jsonProcCallout["Deconfigured"] = false;
+        jsonProcCallout["Guarded"] = false;
+        jsonProcCallout["Priority"] = "M";
+        jsonCalloutDataList.emplace_back(jsonProcCallout);
+    }
+    catch (const std::exception& e)
+    {
+        log<level::ERR>(fmt::format("getLocationCode({}): Exception({})",
+                                    pdbg_target_path(procTarget), e.what())
+                            .c_str());
+    }
+}
+
 void handleSbeBootError(struct pdbg_target* procTarget, const uint32_t timeout)
 {
     using namespace openpower::phal;
@@ -93,6 +135,26 @@
                             static_cast<uint8_t>(0x01), sbeError.getFd()));
     }
 
+    std::unique_ptr<FFDCFile> ffdcFilePtr;
+    try
+    {
+        json jsonCalloutDataList;
+        jsonCalloutDataList = json::array();
+        getSBECallout(procTarget, jsonCalloutDataList);
+        ffdcFilePtr = std::make_unique<FFDCFile>(jsonCalloutDataList);
+        ffdc.push_back(std::make_tuple(
+            sdbusplus::xyz::openbmc_project::Logging::server::Create::
+                FFDCFormat::JSON,
+            static_cast<uint8_t>(0xCA), static_cast<uint8_t>(0x01),
+            ffdcFilePtr->getFileDescriptor()));
+    }
+    catch (const std::exception& e)
+    {
+        log<level::ERR>(
+            fmt::format("Skipping SBE special callout due to Exception({})",
+                        e.what())
+                .c_str());
+    }
     auto pelId = createPel(event, additionalData, ffdc);
 
     if (dumpIsRequired)
diff --git a/watchdog/watchdog_main.hpp b/watchdog/watchdog_main.hpp
index 16cae7c..bb90ee2 100644
--- a/watchdog/watchdog_main.hpp
+++ b/watchdog/watchdog_main.hpp
@@ -1,5 +1,4 @@
 #pragma once
-
 #include <stdint.h>
 
 /**