Find P10 OCCs by looking in /dev

Find the exact OCCs present in the system by looking for the OCC devices
in /dev.  They look like /dev/occ1, /dev/occ2, etc.  The /dev/occ1 entry
will translate to Status object /org/open_power/control/occ0.

In order to handle the case where the application starts before all
devices show up, keep checking until at least 10 seconds have gone by
without a new device showing up (with at least one device present).

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: I42289d55a6266d710b313af0a49070eeacccd725
diff --git a/occ_manager.cpp b/occ_manager.cpp
index 69f01ea..04276b9 100644
--- a/occ_manager.cpp
+++ b/occ_manager.cpp
@@ -6,6 +6,7 @@
 #include "occ_dbus.hpp"
 #include "utils.hpp"
 
+#include <chrono>
 #include <cmath>
 #include <experimental/filesystem>
 #include <phosphor-logging/elog-errors.hpp>
@@ -50,12 +51,62 @@
 
 void Manager::findAndCreateObjects()
 {
+#ifndef POWER10
     for (auto id = 0; id < MAX_CPUS; ++id)
     {
         // Create one occ per cpu
         auto occ = std::string(OCC_NAME) + std::to_string(id);
         createObjects(occ);
     }
+#else
+    // Create the OCCs based on on the /dev/occX devices
+    auto occs = findOCCsInDev();
+
+    if (occs.empty() || (prevOCCSearch.size() != occs.size()))
+    {
+        // Something changed or no OCCs yet, try again in 10s.
+        // Note on the first pass prevOCCSearch will be empty,
+        // so there will be at least one delay to give things
+        // a chance to settle.
+        prevOCCSearch = occs;
+
+        using namespace std::literals::chrono_literals;
+        discoverTimer->restartOnce(10s);
+    }
+    else
+    {
+        discoverTimer.reset();
+
+        // createObjects requires OCC0 first.
+        std::sort(occs.begin(), occs.end());
+
+        for (auto id : occs)
+        {
+            createObjects(std::string(OCC_NAME) + std::to_string(id));
+        }
+    }
+#endif
+}
+
+std::vector<int> Manager::findOCCsInDev()
+{
+    std::vector<int> occs;
+    std::regex expr{R"(occ(\d+)$)"};
+
+    for (auto& file : fs::directory_iterator("/dev"))
+    {
+        std::smatch match;
+        std::string path{file.path().string()};
+        if (std::regex_search(path, match, expr))
+        {
+            auto num = std::stoi(match[1].str());
+
+            // /dev numbering starts at 1, ours starts at 0.
+            occs.push_back(num - 1);
+        }
+    }
+
+    return occs;
 }
 
 int Manager::cpuCreated(sdbusplus::message::message& msg)
diff --git a/occ_manager.hpp b/occ_manager.hpp
index e6e8cbb..140b14a 100644
--- a/occ_manager.hpp
+++ b/occ_manager.hpp
@@ -71,7 +71,13 @@
             std::bind(std::mem_fn(&Manager::updateOCCActive), this,
                       std::placeholders::_1, std::placeholders::_2)))
 #endif
-
+#ifdef POWER10
+        ,
+        discoverTimer(
+            std::make_unique<
+                sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic>>(
+                sdpEvent, std::bind(&Manager::findAndCreateObjects, this)))
+#endif
     {
 #ifdef I2C_OCC
         // I2C OCC status objects are initialized directly
@@ -88,9 +94,7 @@
     }
 
   private:
-    /** @brief Checks if the CPU inventory is present and if so, creates
-     *         the occ D-Bus objects. Else, registers a handler to be
-     *         called when inventory is created.
+    /** @brief Creates the OCC D-Bus objects.
      */
     void findAndCreateObjects();
 
@@ -186,12 +190,34 @@
     std::unique_ptr<pldm::Interface> pldmHandle = nullptr;
 #endif
 
+#ifdef POWER10
+    /**
+     * @brief Timer used when discovering OCCs in /dev.
+     */
+    std::unique_ptr<
+        sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic>>
+        discoverTimer;
+
+    /**
+     * @brief Used when discovering /dev/occ objects to know if
+     *        any were added since the last check.
+     */
+    std::vector<int> prevOCCSearch;
+#endif
+
     /**
      * @brief Called when poll timer expires and forces a POLL command to the
      * OCC. The poll timer will then be restarted.
      * */
     void pollerTimerExpired();
 
+    /**
+     * @brief Finds the OCC devices in /dev
+     *
+     * @return The IDs of the OCCs - 0, 1, etc.
+     */
+    std::vector<int> findOCCsInDev();
+
 #ifdef READ_OCC_SENSORS
     /**
      * @brief Gets the occ sensor values.