dump-collect: Add application for SBE dump collection

This series of commits introduces the `dump-collect` application, a new
command-line tool designed to facilitate the collection of dumps from
the SBE. The tool is capable of asynchronous operation, allowing it
to initiate dump collection from different SBEs at the same time.

Key Highlights:
- The base implementation sets up the application to accept necessary
   parameters for dump collection, including dump type, dump ID,
   destination path for collected data, and the ID of the failing unit.
- An implementation of the dump collection class and the initiation
  method enables the start of the dump collection process
  asynchronously.
- The `dump-collect` application is designed to be invoked from scripts,
  which collect various data in the case of system failures.

Tests:
  Validated parameters passed
  Starting dump collection method

Signed-off-by: Dhruvaraj Subhashchandran <dhruvaraj@in.ibm.com>
Change-Id: If6f44075d33af20e09a442d7968d235dc6e8ea16
diff --git a/dump/dump_collect_main.cpp b/dump/dump_collect_main.cpp
new file mode 100644
index 0000000..c77b089
--- /dev/null
+++ b/dump/dump_collect_main.cpp
@@ -0,0 +1,82 @@
+#include "sbe_consts.hpp"
+#include "sbe_dump_collector.hpp"
+
+#include <CLI/App.hpp>
+#include <CLI/Config.hpp>
+#include <CLI/Formatter.hpp>
+
+#include <filesystem>
+#include <iostream>
+
+int main(int argc, char** argv)
+{
+    using namespace openpower::dump::sbe_chipop;
+    using std::filesystem::path;
+    using namespace openpower::dump::SBE;
+
+    CLI::App app{"Dump Collector Application", "dump-collect"};
+    app.description(
+        "Collects dumps from the Self Boot Engine (SBE) based on "
+        "provided parameters.\nSupports different types of dumps and requires "
+        "specific options based on the dump type.");
+
+    int type = 0;
+    uint32_t id;
+    std::string pathStr;
+    std::optional<uint64_t> failingUnit;
+
+    app.add_option("--type, -t", type, "Type of the dump")
+        ->required()
+        ->check(
+            CLI::IsMember({SBE_DUMP_TYPE_HARDWARE, SBE_DUMP_TYPE_HOSTBOOT}));
+
+    app.add_option("--id, -i", id, "ID of the dump")->required();
+
+    app.add_option("--path, -p", pathStr,
+                   "Path to store the collected dump files")
+        ->required();
+
+    app.add_option("--failingunit, -f", failingUnit, "ID of the failing unit");
+
+    try
+    {
+        CLI11_PARSE(app, argc, argv);
+    }
+    catch (const CLI::ParseError& e)
+    {
+        return app.exit(e);
+    }
+
+    if (type == SBE_DUMP_TYPE_HARDWARE && !failingUnit.has_value())
+    {
+        std::cerr << "Failing unit ID is required for Hardware type dumps\n";
+        return EXIT_FAILURE;
+    }
+
+    // Directory creation should happen here, after successful parsing
+    std::filesystem::path dirPath{pathStr};
+    if (!std::filesystem::exists(dirPath))
+    {
+        std::filesystem::create_directories(dirPath);
+    }
+
+    SbeDumpCollector dumpCollector;
+
+    auto failingUnitId = 0xFFFFFF; // Default or unspecified value
+    if (failingUnit.has_value())
+    {
+        failingUnitId = failingUnit.value();
+    }
+
+    try
+    {
+        dumpCollector.collectDump(type, id, failingUnitId, pathStr);
+    }
+    catch (const std::exception& e)
+    {
+        std::cerr << "Failed to collect dump: " << e.what() << std::endl;
+        std::exit(EXIT_FAILURE);
+    }
+
+    return 0;
+}
diff --git a/dump/dump_utils.cpp b/dump/dump_utils.cpp
new file mode 100644
index 0000000..81b1cc3
--- /dev/null
+++ b/dump/dump_utils.cpp
@@ -0,0 +1,56 @@
+#include "dump_utils.hpp"
+
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/elog.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <phosphor-logging/log.hpp>
+#include <xyz/openbmc_project/Common/File/error.hpp>
+
+#include <format>
+#include <fstream>
+#include <string>
+
+namespace openpower::dump::util
+{
+using namespace phosphor::logging;
+
+std::string getService(sdbusplus::bus::bus& bus, const std::string& intf,
+                       const std::string& path)
+{
+    constexpr auto MAPPER_BUSNAME = "xyz.openbmc_project.ObjectMapper";
+    constexpr auto MAPPER_PATH = "/xyz/openbmc_project/object_mapper";
+    constexpr auto MAPPER_INTERFACE = "xyz.openbmc_project.ObjectMapper";
+    try
+    {
+        auto mapper = bus.new_method_call(MAPPER_BUSNAME, MAPPER_PATH,
+                                          MAPPER_INTERFACE, "GetObject");
+
+        mapper.append(path, std::vector<std::string>({intf}));
+
+        auto mapperResponseMsg = bus.call(mapper);
+        std::map<std::string, std::vector<std::string>> mapperResponse;
+        mapperResponseMsg.read(mapperResponse);
+
+        if (mapperResponse.empty())
+        {
+            lg2::error(
+                "Empty mapper response for GetObject interface({INTERFACE}), "
+                "path({PATH})",
+                "INTERFACE", intf, "PATH", path);
+
+            throw std::runtime_error("Empty mapper response for GetObject");
+        }
+        return mapperResponse.begin()->first;
+    }
+    catch (const sdbusplus::exception::exception& ex)
+    {
+        lg2::error(
+            "Mapper call failed for GetObject errorMsg({ERROR}), path({PATH}),"
+            "interface({INTERFACE})",
+            "ERROR", ex, "PATH", path, "INTERFACE", intf);
+
+        throw;
+    }
+}
+
+} // namespace openpower::dump::util
diff --git a/dump/dump_utils.hpp b/dump/dump_utils.hpp
new file mode 100644
index 0000000..8918024
--- /dev/null
+++ b/dump/dump_utils.hpp
@@ -0,0 +1,84 @@
+#pragma once
+
+#include <sdbusplus/server.hpp>
+
+#include <filesystem>
+#include <map>
+#include <string>
+#include <variant>
+namespace openpower::dump::util
+{
+
+using DumpCreateParams =
+    std::map<std::string, std::variant<std::string, uint64_t>>;
+
+/** @struct DumpPtr
+ * @brief a structure holding the data pointer
+ * @details This is a RAII container for the dump data
+ * returned by the SBE
+ */
+struct DumpDataPtr
+{
+  public:
+    /** @brief Destructor for the object, free the allocated memory.
+     */
+    ~DumpDataPtr()
+    {
+        // The memory is allocated using malloc
+        free(dataPtr);
+    }
+    /** @brief Returns the pointer to the data
+     */
+    uint8_t** getPtr()
+    {
+        return &dataPtr;
+    }
+    /** @brief Returns the stored data
+     */
+    uint8_t* getData()
+    {
+        return dataPtr;
+    }
+
+  private:
+    /** The pointer to the data */
+    uint8_t* dataPtr = nullptr;
+};
+
+/**
+ * @brief Get DBUS service for input interface via mapper call
+ *
+ * @param[in] bus -  DBUS Bus Object
+ * @param[in] intf - DBUS Interface
+ * @param[in] path - DBUS Object Path
+ *
+ * @return distinct dbus name for input interface/path
+ **/
+std::string getService(sdbusplus::bus::bus& bus, const std::string& intf,
+                       const std::string& path);
+
+/**
+ * @brief Set the property value based on the inputs
+ *
+ * @param[in] interface - the interface the property is on
+ * @param[in] propertName - the name of the property
+ * @param[in] path - the D-Bus path
+ * @param[in] service - the D-Bus service
+ * @param[in] bus - the D-Bus object
+ * @param[in] value - the value to set the property to
+ */
+template <typename T>
+void setProperty(const std::string& interface, const std::string& propertyName,
+                 const std::string& path, sdbusplus::bus::bus& bus,
+                 const T& value)
+{
+    constexpr auto PROPERTY_INTF = "org.freedesktop.DBus.Properties";
+
+    auto service = getService(bus, interface, path);
+    auto method = bus.new_method_call(service.c_str(), path.c_str(),
+                                      PROPERTY_INTF, "Set");
+    method.append(interface, propertyName, value);
+    auto reply = bus.call(method);
+}
+
+} // namespace openpower::dump::util
diff --git a/dump/meson.build b/dump/meson.build
new file mode 100644
index 0000000..45140d0
--- /dev/null
+++ b/dump/meson.build
@@ -0,0 +1,26 @@
+# SPDX-License-Identifier: Apache-2.0
+
+cxx = meson.get_compiler('cpp')
+
+collect_deps = [
+   CLI11_dep,
+   phosphorlogging,
+   cxx.find_library('pdbg'),
+   cxx.find_library('libdt-api'),
+   cxx.find_library('phal'),
+]
+
+# source files
+
+collect_src = files(
+    'sbe_dump_collector.cpp',
+    'dump_collect_main.cpp',
+    'dump_utils.cpp',
+)
+
+executable('dump-collect',
+    collect_src,
+    dependencies: collect_deps,
+    implicit_include_directories: true,
+    install: true
+)
diff --git a/dump/sbe_consts.hpp b/dump/sbe_consts.hpp
new file mode 100644
index 0000000..9fea1f0
--- /dev/null
+++ b/dump/sbe_consts.hpp
@@ -0,0 +1,18 @@
+#pragma once
+namespace openpower::dump::SBE
+{
+// Dump type to the sbe_dump chipop
+constexpr auto SBE_DUMP_TYPE_HOSTBOOT = 0x5;
+constexpr auto SBE_DUMP_TYPE_HARDWARE = 0x1;
+
+// SBE dump type
+constexpr auto SBE_DUMP_TYPE_SBE = 0xA;
+
+// Clock state requested
+// Collect the dump with clocks on
+constexpr auto SBE_CLOCK_ON = 0x1;
+
+// Collect the dumps with clock off
+constexpr auto SBE_CLOCK_OFF = 0x2;
+
+} // namespace openpower::dump::SBE
diff --git a/dump/sbe_dump_collector.cpp b/dump/sbe_dump_collector.cpp
new file mode 100644
index 0000000..5f0f83f
--- /dev/null
+++ b/dump/sbe_dump_collector.cpp
@@ -0,0 +1,120 @@
+extern "C"
+{
+#include <libpdbg.h>
+#include <libpdbg_sbe.h>
+}
+
+#include "sbe_consts.hpp"
+#include "sbe_dump_collector.hpp"
+
+#include <libphal.H>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <phosphor-logging/lg2.hpp>
+#include <phosphor-logging/log.hpp>
+
+#include <format>
+#include <stdexcept>
+
+namespace openpower::dump::sbe_chipop
+{
+
+using namespace phosphor::logging;
+using namespace openpower::dump::SBE;
+
+void SbeDumpCollector::collectDump(uint8_t type, uint32_t id,
+                                   uint64_t failingUnit,
+                                   const std::filesystem::path& path)
+{
+    lg2::error("Starting dump collection: type:{TYPE} id:{ID} "
+               "failingUnit:{FAILINGUNIT}, path:{PATH}",
+               "TYPE", type, "ID", id, "FAILINGUNIT", failingUnit, "PATH",
+               path.string());
+
+    initializePdbg();
+
+    std::vector<struct pdbg_target*> targets;
+
+    struct pdbg_target* target = nullptr;
+    pdbg_for_each_class_target("proc", target)
+    {
+        if (pdbg_target_probe(target) != PDBG_TARGET_ENABLED ||
+            !openpower::phal::pdbg::isTgtFunctional(target))
+        {
+            continue;
+        }
+
+        targets.push_back(target);
+    }
+
+    std::vector<uint8_t> clockStates = {SBE_CLOCK_ON, SBE_CLOCK_OFF};
+    for (auto cstate : clockStates)
+    {
+        auto futures = spawnDumpCollectionProcesses(type, id, path, failingUnit,
+                                                    cstate, targets);
+
+        // Wait for all asynchronous tasks to complete
+        for (auto& future : futures)
+        {
+            future.wait();
+        }
+        lg2::info(
+            "Dump collection completed for clock state({CSTATE}): type({TYPE}) "
+            "id({ID}) failingUnit({FAILINGUNIT}), path({PATH})",
+            "CSTATE", cstate, "TYPE", type, "ID", id, "FAILINGUNIT",
+            failingUnit, "PATH", path.string());
+    }
+
+    lg2::info("Dump collection completed");
+}
+
+void SbeDumpCollector::initializePdbg()
+{
+    openpower::phal::pdbg::init();
+}
+
+std::vector<std::future<void>> SbeDumpCollector::spawnDumpCollectionProcesses(
+    uint8_t type, uint32_t id, const std::filesystem::path& path,
+    uint64_t failingUnit, uint8_t cstate,
+    const std::vector<struct pdbg_target*>& targets)
+{
+    std::vector<std::future<void>> futures;
+
+    for (auto target : targets)
+    {
+        if (pdbg_target_probe(target) != PDBG_TARGET_ENABLED ||
+            !openpower::phal::pdbg::isTgtFunctional(target))
+        {
+            continue;
+        }
+
+        // Launch an asynchronous task instead of forking
+        auto future =
+            std::async(std::launch::async,
+                       [this, target, path, id, type, cstate, failingUnit]() {
+            this->collectDumpFromSBE(target, path, id, type, cstate,
+                                     failingUnit);
+        });
+
+        futures.push_back(std::move(future));
+    }
+
+    return futures;
+}
+
+void SbeDumpCollector::collectDumpFromSBE(struct pdbg_target* chip,
+                                          const std::filesystem::path& path,
+                                          uint32_t id, uint8_t type,
+                                          uint8_t clockState,
+                                          uint64_t failingUnit)
+{
+    auto chipPos = pdbg_target_index(chip);
+    lg2::info(
+        "Collecting dump from proc({PROC}): path({PATH}) id({ID}) "
+        "type({TYPE}) clockState({CLOCKSTATE}) failingUnit({FAILINGUNIT})",
+        "PROC", chipPos, "PATH", path.string(), "ID", id, "TYPE", type,
+        "CLOCKSTATE", clockState, "FAILINGUNIT", failingUnit);
+}
+
+} // namespace openpower::dump::sbe_chipop
diff --git a/dump/sbe_dump_collector.hpp b/dump/sbe_dump_collector.hpp
new file mode 100644
index 0000000..000022c
--- /dev/null
+++ b/dump/sbe_dump_collector.hpp
@@ -0,0 +1,119 @@
+#pragma once
+
+extern "C"
+{
+#include <libpdbg.h>
+#include <libpdbg_sbe.h>
+}
+
+#include <cstdint>
+#include <filesystem>
+#include <future>
+#include <vector>
+
+namespace openpower::dump::sbe_chipop
+{
+
+/**
+ * @class SbeDumpCollector
+ * @brief Manages the collection of dumps from SBEs on failure.
+ *
+ * This class provides functionalities to orchestrate the collection of
+ * diagnostic dumps from Self Boot Engines across multiple processors
+ * in response to failures or for diagnostic purposes.
+ */
+class SbeDumpCollector
+{
+  public:
+    /**
+     * @brief Constructs a new SbeDumpCollector object.
+     */
+    SbeDumpCollector() = default;
+
+    /**
+     * @brief Destroys the SbeDumpCollector object.
+     */
+    ~SbeDumpCollector() = default;
+
+    /**
+     * @brief Orchestrates the collection of dumps from all available SBEs.
+     *
+     * Initiates the process of collecting diagnostic dumps from SBEs. This
+     * involves identifying available processors, initiating the dump
+     * collection process, and managing the collected dump files.
+     *
+     * @param type The type of dump to collect.
+     * @param id A unique identifier for the dump collection operation.
+     * @param failingUnit The identifier of the failing unit prompting the dump
+     * collection.
+     * @param path The filesystem path where collected dumps should be stored.
+     */
+    void collectDump(uint8_t type, uint32_t id, uint64_t failingUnit,
+                     const std::filesystem::path& path);
+
+  private:
+    /**
+     * @brief Collects a dump from a single SBE.
+     *
+     * Executes the low-level operations required to collect a diagnostic
+     * dump from the specified SBE.
+     *
+     * @param chip A pointer to the pdbg_target structure representing the SBE.
+     * @param path The filesystem path where the dump should be stored.
+     * @param id The unique identifier for this dump collection operation.
+     * @param type The type of dump to collect.
+     * @param clockState The clock state of the SBE during dump collection.
+     * @param failingUnit The identifier of the failing unit.
+     */
+    void collectDumpFromSBE(struct pdbg_target* chip,
+                            const std::filesystem::path& path, uint32_t id,
+                            uint8_t type, uint8_t clockState,
+                            uint64_t failingUnit);
+
+    /**
+     * @brief Initializes the PDBG library.
+     *
+     * Prepares the PDBG library for interacting with processor targets. This
+     * must be called before any PDBG-related operations are performed.
+     */
+    void initializePdbg();
+
+    /**
+     * @brief Launches asynchronous dump collection tasks for a set of targets.
+     *
+     * This method initiates the dump collection process asynchronously for each
+     * target provided in the `targets` vector. It launches a separate
+     * asynchronous task for each target, where each task calls
+     * `collectDumpFromSBE` with the specified parameters, including the clock
+     * state.
+     *
+     * @param type The type of the dump to collect. This could be a hardware
+     * dump, software dump, etc., as defined by the SBE dump type enumeration.
+     * @param id A unique identifier for the dump collection operation. This ID
+     * is used to tag the collected dump for identification.
+     * @param path The filesystem path where the collected dumps should be
+     * stored. Each dump file will be stored under this directory.
+     * @param failingUnit The identifier of the unit or component that is
+     * failing or suspected to be the cause of the issue prompting the dump
+     * collection. This is used for diagnostic purposes.
+     * @param cstate The clock state during the dump collection. This parameter
+     *               dictates whether the dump should be collected with the
+     * clocks running (SBE_CLOCK_ON) or with the clocks stopped (SBE_CLOCK_OFF).
+     * @param targets A vector of `pdbg_target*` representing the targets from
+     * which dumps should be collected. Each target corresponds to a physical or
+     * logical component in the system, such as a processor or an SBE.
+     *
+     * @return A vector of `std::future<void>` objects. Each future represents
+     * the completion state of an asynchronous dump collection task. The caller
+     *         can wait on these futures to determine when all dump collection
+     * tasks have completed. Exceptions thrown by the asynchronous tasks are
+     * captured by the futures and can be rethrown when the futures are
+     * accessed.
+     */
+    std::vector<std::future<void>> spawnDumpCollectionProcesses(
+        uint8_t type, uint32_t id, const std::filesystem::path& path,
+        uint64_t failingUnit, uint8_t cstate,
+        const std::vector<struct pdbg_target*>& targets);
+};
+
+} // namespace openpower::dump::sbe_chipop
diff --git a/meson.build b/meson.build
index 94dabd2..9c01593 100644
--- a/meson.build
+++ b/meson.build
@@ -48,6 +48,10 @@
 
 configure_file(output: 'config.h', configuration: conf_data)
 
+if get_option('dump-collection').enabled()
+    subdir('dump')
+endif
+
 deps = [
     CLI11_dep,
     sdbusplus_dep,
diff --git a/meson.options b/meson.options
index c271593..124926d 100644
--- a/meson.options
+++ b/meson.options
@@ -1,5 +1,12 @@
+# SPDX-License-Identifier: Apache-2.0
+
 # Feature to enable hostboot dump collection when watchdog times out
 option('hostboot-dump-collection',
        type: 'feature',
        value: 'disabled',
        description : 'Enables hostboot dump collection')
+
+# Feature to enable the dump collection
+option('dump-collection', type: 'feature',
+       value : 'disabled',
+       description : 'Enables dump collection')