Add support for collecting data for BMC dumps

When occ-control receives a USR1 signal, it will trigger the code to
collect the following data:
- number of OCC objects created
- for each active OCC: state, role, hwmon path, poll response, and WOF
data

The data will be written in JSON format to a file which can be collected
and added to a dump. (/tmp/occ_control_dump.json)

To force the data collection:
  killall -s SIGUSR1 openpower-occ-control

Change-Id: I7a304f7ce0eb1c9109f630f187adf9c95722652e
Signed-off-by: Chris Cain <cjcain@us.ibm.com>
diff --git a/app.cpp b/app.cpp
index f0929d7..24f24fb 100644
--- a/app.cpp
+++ b/app.cpp
@@ -7,6 +7,8 @@
 
 #include <org/open_power/OCC/Device/error.hpp>
 #include <phosphor-logging/lg2.hpp>
+#include <sdeventplus/source/signal.hpp>
+#include <stdplus/signal.hpp>
 #include <xyz/openbmc_project/Common/error.hpp>
 
 using namespace sdbusplus::org::open_power::OCC::Device::Error;
@@ -44,6 +46,22 @@
     open_power::occ::Manager mgr(eventP);
     mgr.createPldmHandle();
 
+    try
+    {
+        // Enable SIGUSR1 handling to collect data on dump request
+        stdplus::signal::block(SIGUSR1);
+        sdeventplus::source::Signal sigUsr1(
+            eventP.get(), SIGUSR1,
+            std::bind(&open_power::occ::Manager::collectDumpData, &mgr,
+                      std::placeholders::_1, std::placeholders::_2));
+        sigUsr1.set_floating(true);
+        lg2::info("USR1 signal handler enabled");
+    }
+    catch (const std::exception& e)
+    {
+        lg2::error("Failed to enable SIGUSR1 handler: {ERR}", "ERR", e.what());
+    }
+
     // Claim the bus since all the house keeping is done now
     bus.request_name(OCC_CONTROL_BUSNAME);
 
diff --git a/occ_manager.cpp b/occ_manager.cpp
index 302a733..6b735f7 100644
--- a/occ_manager.cpp
+++ b/occ_manager.cpp
@@ -6,6 +6,7 @@
 #include "occ_errors.hpp"
 #include "utils.hpp"
 
+#include <nlohmann/json.hpp>
 #include <phosphor-logging/elog-errors.hpp>
 #include <phosphor-logging/lg2.hpp>
 #include <xyz/openbmc_project/Common/error.hpp>
@@ -28,9 +29,11 @@
 constexpr auto maxSuffix = "max";
 
 const auto HOST_ON_FILE = "/run/openbmc/host@0-on";
+const std::string Manager::dumpFile = "/tmp/occ_control_dump.json";
 
 using namespace phosphor::logging;
 using namespace std::literals::chrono_literals;
+using json = nlohmann::json;
 
 template <typename T>
 T readFile(const std::string& path)
@@ -1722,5 +1725,91 @@
     resetInstance = 255;
 }
 
+void Manager::collectDumpData(sdeventplus::source::Signal&,
+                              const struct signalfd_siginfo*)
+{
+    json data;
+    lg2::info("collectDumpData()");
+    data["objectCount"] = std::to_string(statusObjects.size()) + " OCC objects";
+    if (statusObjects.size() > 0)
+    {
+        try
+        {
+            for (auto& occ : statusObjects)
+            {
+                json occData;
+                auto instance = occ->getOccInstanceID();
+                std::string occName = "occ" + std::to_string(instance);
+
+                if (occ->occActive())
+                {
+                    // OCC General Info
+                    occData["occState"] = "ACTIVE";
+                    occData["occRole"] =
+                        occ->isMasterOcc() ? "MASTER" : "SECONDARY";
+                    occData["occHwmonPath"] =
+                        occ->getHwmonPath().generic_string();
+
+                    // OCC Poll Response
+                    std::vector<std::uint8_t> cmd = {0x00, 0x00, 0x01, 0x20};
+                    std::vector<std::uint8_t> rsp;
+                    std::vector<std::string> rspHex;
+                    rsp = passThroughObjects[instance]->send(cmd);
+                    if (rsp.size() > 5)
+                    {
+                        rsp.erase(rsp.begin(),
+                                  rsp.begin() + 5); // Strip rsp header
+                        rspHex = utils::hex_dump(rsp);
+                        occData["pollResponse"] = rspHex;
+                    }
+
+                    // Debug Data: WOF Dynamic Data
+                    cmd = {0x40, 0x00, 0x01, 0x01};
+                    rsp = passThroughObjects[instance]->send(cmd);
+                    if (rsp.size() > 5)
+                    {
+                        rsp.erase(rsp.begin(),
+                                  rsp.begin() + 5); // Strip rsp header
+                        rspHex = utils::hex_dump(rsp);
+                        occData["wofDataDynamic"] = rspHex;
+                    }
+
+                    // Debug Data: WOF Dynamic Data
+                    cmd = {0x40, 0x00, 0x01, 0x0A};
+                    rsp = passThroughObjects[instance]->send(cmd);
+                    if (rsp.size() > 5)
+                    {
+                        rsp.erase(rsp.begin(),
+                                  rsp.begin() + 5); // Strip rsp header
+                        rspHex = utils::hex_dump(rsp);
+                        occData["wofDataStatic"] = rspHex;
+                    }
+                }
+                else
+                {
+                    occData["occState"] = "NOT ACTIVE";
+                }
+
+                data[occName] = occData;
+            }
+        }
+        catch (const std::exception& e)
+        {
+            lg2::error("Failed to collect OCC dump data: {ERR}", "ERR",
+                       e.what());
+        }
+    }
+
+    std::ofstream file{Manager::dumpFile};
+    if (!file)
+    {
+        lg2::error("Failed to open {FILE} for occ-control data", "FILE",
+                   Manager::dumpFile);
+        return;
+    }
+
+    file << std::setw(4) << data;
+}
+
 } // namespace occ
 } // namespace open_power
diff --git a/occ_manager.hpp b/occ_manager.hpp
index a2f1b8a..3758952 100644
--- a/occ_manager.hpp
+++ b/occ_manager.hpp
@@ -13,7 +13,9 @@
 
 #include <sdbusplus/bus.hpp>
 #include <sdeventplus/event.hpp>
+#include <sdeventplus/source/signal.hpp>
 #include <sdeventplus/utility/timer.hpp>
+#include <stdplus/signal.hpp>
 
 #include <cstring>
 #include <functional>
@@ -144,6 +146,15 @@
      * is off */
     void hostPoweredOff();
 
+    /** @brief Collect data to include in BMC dumps
+     *         This will get called when app receives a SIGUSR1 signal
+     */
+    void collectDumpData(sdeventplus::source::Signal&,
+                         const struct signalfd_siginfo*);
+
+    /** @brief Name of file to put the occ-control dump data */
+    static const std::string dumpFile;
+
   private:
     /** @brief Creates the OCC D-Bus objects.
      */
diff --git a/utils.cpp b/utils.cpp
index efb7abd..6498dbd 100644
--- a/utils.cpp
+++ b/utils.cpp
@@ -247,6 +247,39 @@
     return false;
 }
 
+// Convert vector to hex dump string
+std::vector<std::string> hex_dump(const std::vector<std::uint8_t>& data,
+                                  const unsigned int data_len)
+{
+    unsigned int dump_length = data.size();
+    if ((data_len > 0) && (data_len < dump_length))
+    {
+        dump_length = data_len;
+    }
+    std::vector<std::string> dumpString;
+    std::string s;
+    for (uint32_t i = 0; i < dump_length; i++)
+    {
+        if (i % 16 == 0)
+        {
+            s += std::format("{:04X}: ", i);
+        }
+        else if (i % 4 == 0)
+        {
+            s += " ";
+        }
+
+        s += std::format("{:02X}", data.at(i));
+
+        if ((i % 16 == 15) || (i == (dump_length - 1)))
+        {
+            dumpString.push_back(s);
+            s.clear();
+        }
+    }
+    return dumpString;
+}
+
 } // namespace utils
 } // namespace occ
 } // namespace open_power
diff --git a/utils.hpp b/utils.hpp
index 2085bf4..0fec395 100644
--- a/utils.hpp
+++ b/utils.hpp
@@ -96,6 +96,17 @@
  */
 bool isHostRunning();
 
+/**
+ * @brief Convert vector to hex dump string
+ *
+ * @param[in] data       - vector of uint8_ data
+ * @param[in] path       - length of data to use (0 = all data)
+ *
+ * @return vector of strings
+ */
+std::vector<std::string> hex_dump(const std::vector<std::uint8_t>& data,
+                                  const unsigned int data_len = 0);
+
 } // namespace utils
 } // namespace occ
 } // namespace open_power