Add Inoitfy for device changes

Watch for i2c device changes and trigger a rescan
if we see any. Also reorder start dependencies in
entity manager to start faster.

Change-Id: I49facfc725c182ae5d75af2b29e6665f2c8ac0e1
Signed-off-by: James Feist <james.feist@linux.intel.com>
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7c73b4c..b578ed2 100755
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -38,7 +38,7 @@
     ExternalProject_Add(boost-dbus
         PREFIX ${CMAKE_CURRENT_BINARY_DIR}/boost-dbus
         GIT_REPOSITORY ssh://git-amr-2.devtools.intel.com:29418/openbmc-boost-dbus
-        GIT_TAG e7dae9ce93226e6a7bf3d9101f457d29afac40c2
+        GIT_TAG 216455021500e013a8d95a2d412ade31177353b7
         CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTERNAL_INSTALL_LOCATION} -DBOOST_ROOT=${BOOST_ROOT}
         CONFIGURE_COMMAND ""
         BUILD_COMMAND ""
diff --git a/src/EntityManager.cpp b/src/EntityManager.cpp
index 19a370b..45d18e9 100644
--- a/src/EntityManager.cpp
+++ b/src/EntityManager.cpp
@@ -36,7 +36,7 @@
 constexpr const char *TEMPLATE_CHAR = "$";
 constexpr const size_t PROPERTIES_CHANGED_UNTIL_FLUSH_COUNT = 20;
 constexpr const size_t MAX_MAPPER_DEPTH = 99;
-constexpr const size_t SLEEP_AFTER_PROPERTIES_CHANGE_SECONDS = 3;
+constexpr const size_t SLEEP_AFTER_PROPERTIES_CHANGE_SECONDS = 5;
 
 namespace fs = std::experimental::filesystem;
 struct cmp_str
@@ -87,8 +87,7 @@
 void registerCallbacks(
     std::vector<std::pair<std::unique_ptr<dbus::match>,
                           std::shared_ptr<dbus::filter>>> &dbusMatches,
-    std::atomic_bool &threadRunning, nlohmann::json &systemConfiguration,
-    dbus::DbusObjectServer &objServer);
+    nlohmann::json &systemConfiguration, dbus::DbusObjectServer &objServer);
 
 // calls the mapper to find all exposed objects of an interface type
 // and creates a vector<flat_map> that contains all the key value pairs
@@ -867,46 +866,57 @@
 void propertiesChangedCallback(
     std::vector<std::pair<std::unique_ptr<dbus::match>,
                           std::shared_ptr<dbus::filter>>> &dbusMatches,
-    std::atomic_bool &threadRunning, nlohmann::json &systemConfiguration,
-    dbus::DbusObjectServer &objServer, std::shared_ptr<dbus::filter> dbusFilter)
+    nlohmann::json &systemConfiguration, dbus::DbusObjectServer &objServer,
+    std::shared_ptr<dbus::filter> dbusFilter)
 {
     static std::future<void> future;
+    static std::atomic_bool threadRunning(false);
+    static std::atomic_bool pendingCallback(false);
     bool notRunning = false;
     if (threadRunning.compare_exchange_strong(notRunning, true))
     {
         future = std::async(std::launch::async, [&] {
-            std::this_thread::sleep_for(
-                std::chrono::seconds(SLEEP_AFTER_PROPERTIES_CHANGE_SECONDS));
-            auto oldConfiguration = systemConfiguration;
-            DBUS_PROBE_OBJECTS.clear();
-            rescan(systemConfiguration);
-            auto newConfiguration = systemConfiguration;
-            for (auto it = newConfiguration.begin();
-                 it != newConfiguration.end();)
-            {
-                auto findKey = oldConfiguration.find(it.key());
-                if (findKey != oldConfiguration.end())
-                {
-                    it = newConfiguration.erase(it);
-                }
-                else
-                {
-                    it++;
-                }
-            }
-            // todo: for now, only add new configurations, unload to come later
-            // unloadOverlays();
-            loadOverlays(newConfiguration);
-            // this line to be removed in future
-            writeJsonFiles(systemConfiguration);
-            // only post new items to bus for now
-            postToDbus(newConfiguration, objServer);
 
-            registerCallbacks(dbusMatches, threadRunning, systemConfiguration,
-                              objServer);
+            do
+            {
+                std::this_thread::sleep_for(std::chrono::seconds(
+                    SLEEP_AFTER_PROPERTIES_CHANGE_SECONDS));
+                auto oldConfiguration = systemConfiguration;
+                DBUS_PROBE_OBJECTS.clear();
+                pendingCallback = false;
+                rescan(systemConfiguration);
+                auto newConfiguration = systemConfiguration;
+                for (auto it = newConfiguration.begin();
+                     it != newConfiguration.end();)
+                {
+                    auto findKey = oldConfiguration.find(it.key());
+                    if (findKey != oldConfiguration.end())
+                    {
+                        it = newConfiguration.erase(it);
+                    }
+                    else
+                    {
+                        it++;
+                    }
+                }
+
+                registerCallbacks(dbusMatches, systemConfiguration, objServer);
+                // todo: for now, only add new configurations, unload to come
+                // later
+                // unloadOverlays();
+                loadOverlays(newConfiguration);
+                // this line to be removed in future
+                writeJsonFiles(systemConfiguration);
+                // only post new items to bus for now
+                postToDbus(newConfiguration, objServer);
+            } while (pendingCallback);
             threadRunning = false;
         });
     }
+    else
+    {
+        pendingCallback = true;
+    }
     if (dbusFilter != nullptr)
     {
         dbusFilter->async_dispatch([&, dbusFilter](boost::system::error_code ec,
@@ -915,9 +925,8 @@
             {
                 std::cerr << "properties changed callback error " << ec << "\n";
             }
-            propertiesChangedCallback(dbusMatches, threadRunning,
-                                      systemConfiguration, objServer,
-                                      dbusFilter);
+            propertiesChangedCallback(dbusMatches, systemConfiguration,
+                                      objServer, dbusFilter);
         });
     }
 }
@@ -925,8 +934,7 @@
 void registerCallbacks(
     std::vector<std::pair<std::unique_ptr<dbus::match>,
                           std::shared_ptr<dbus::filter>>> &dbusMatches,
-    std::atomic_bool &threadRunning, nlohmann::json &systemConfiguration,
-    dbus::DbusObjectServer &objServer)
+    nlohmann::json &systemConfiguration, dbus::DbusObjectServer &objServer)
 {
     static boost::container::flat_set<std::string> watchedObjects;
 
@@ -954,8 +962,8 @@
             {
                 std::cerr << "register callbacks callback error " << ec << "\n";
             }
-            propertiesChangedCallback(dbusMatches, threadRunning,
-                                      systemConfiguration, objServer, filter);
+            propertiesChangedCallback(dbusMatches, systemConfiguration,
+                                      objServer, filter);
         });
         dbusMatches.emplace_back(std::move(propertyChange), filter);
     }
@@ -966,38 +974,37 @@
     // setup connection to dbus
     boost::asio::io_service io;
     SYSTEM_BUS = std::make_shared<dbus::connection>(io, dbus::bus::system);
+
     dbus::DbusObjectServer objServer(SYSTEM_BUS);
     SYSTEM_BUS->request_name("xyz.openbmc_project.EntityManager");
-
-    nlohmann::json systemConfiguration = nlohmann::json::object();
-    rescan(systemConfiguration);
-    // this line to be removed in future
-    writeJsonFiles(systemConfiguration);
-    unloadAllOverlays();
-    loadOverlays(systemConfiguration);
-    postToDbus(systemConfiguration, objServer);
-
-    auto object = std::make_shared<dbus::DbusObject>(
-        SYSTEM_BUS, "/xyz/openbmc_project/EntityManager");
-    objServer.register_object(object);
-    auto iface = std::make_shared<dbus::DbusInterface>(
-        "xyz.openbmc_project.EntityManager", SYSTEM_BUS);
-    object->register_interface(iface);
-
-    std::atomic_bool threadRunning(false);
-    // to keep reference to the match / filter objects so they don't get
-    // destroyed
     std::vector<
         std::pair<std::unique_ptr<dbus::match>, std::shared_ptr<dbus::filter>>>
         dbusMatches;
+
+    nlohmann::json systemConfiguration = nlohmann::json::object();
+    auto iface = std::make_shared<dbus::DbusInterface>(
+        "xyz.openbmc_project.EntityManager", SYSTEM_BUS);
+    io.post([&]() {
+        unloadAllOverlays();
+        propertiesChangedCallback(dbusMatches, systemConfiguration, objServer,
+                                  nullptr);
+        auto object = std::make_shared<dbus::DbusObject>(
+            SYSTEM_BUS, "/xyz/openbmc_project/EntityManager");
+        objServer.register_object(object);
+
+        object->register_interface(iface);
+
+    });
+
+    // to keep reference to the match / filter objects so they don't get
+    // destroyed
+
     iface->register_method("ReScan", [&]() {
-        propertiesChangedCallback(dbusMatches, threadRunning,
-                                  systemConfiguration, objServer, nullptr);
+        propertiesChangedCallback(dbusMatches, systemConfiguration, objServer,
+                                  nullptr);
         return std::tuple<>(); // this is a bug in boost-dbus, needs some sort
                                // of return
     });
-    registerCallbacks(dbusMatches, threadRunning, systemConfiguration,
-                      objServer);
 
     io.run();
 
diff --git a/src/FruDevice.cpp b/src/FruDevice.cpp
index 4eea9a5..cb268a9 100644
--- a/src/FruDevice.cpp
+++ b/src/FruDevice.cpp
@@ -28,6 +28,7 @@
 #include <iostream>
 #include <sys/ioctl.h>
 #include <regex>
+#include <sys/inotify.h>
 
 namespace fs = std::experimental::filesystem;
 static constexpr bool DEBUG = false;
@@ -36,6 +37,8 @@
 const static constexpr char *BASEBOARD_FRU_LOCATION =
     "/etc/fru/baseboard.fru.bin";
 
+const static constexpr char *I2C_DEV_LOCATION = "/dev";
+
 static constexpr std::array<const char *, 5> FRU_AREAS = {
     "INTERNAL", "CHASSIS", "BOARD", "PRODUCT", "MULTIRECORD"};
 const static constexpr char *POWER_OBJECT_NAME = "/org/openbmc/control/power0";
@@ -436,45 +439,52 @@
                                              std::shared_ptr<dbus::DbusObject>>
                       &dbusObjectMap,
                   std::shared_ptr<dbus::connection> systemBus,
-                  dbus::DbusObjectServer &objServer)
+                  dbus::DbusObjectServer &objServer,
+                  std::atomic_bool &pendingCallback)
 {
-    auto devDir = fs::path("/dev/");
-    auto matchString = std::string("i2c*");
-    std::vector<fs::path> i2cBuses;
 
-    if (!find_files(devDir, matchString, i2cBuses, 0))
+    do
     {
-        std::cerr << "unable to find i2c devices\n";
-        return;
-    }
-    // scanning muxes in reverse order seems to have adverse effects
-    // sorting this list seems to be a fix for that
-    std::sort(i2cBuses.begin(), i2cBuses.end());
-    BusMap busMap = FindI2CDevices(i2cBuses);
+        auto devDir = fs::path("/dev/");
+        auto matchString = std::string("i2c*");
+        std::vector<fs::path> i2cBuses;
+        pendingCallback = false;
 
-    for (auto &busObj : dbusObjectMap)
-    {
-        objServer.remove_object(busObj.second);
-    }
-
-    dbusObjectMap.clear();
-    UNKNOWN_BUS_OBJECT_COUNT = 0;
-
-    for (auto &devicemap : busMap)
-    {
-        for (auto &device : *devicemap.second)
+        if (!find_files(devDir, matchString, i2cBuses, 0))
         {
-            AddFruObjectToDbus(systemBus, device.second, objServer,
-                               dbusObjectMap, devicemap.first, device.first);
+            std::cerr << "unable to find i2c devices\n";
+            return;
         }
-    }
-    // todo, get this from a more sensable place
-    std::vector<char> baseboardFru;
-    if (readBaseboardFru(baseboardFru))
-    {
-        AddFruObjectToDbus(systemBus, baseboardFru, objServer, dbusObjectMap,
-                           -1, -1);
-    }
+        // scanning muxes in reverse order seems to have adverse effects
+        // sorting this list seems to be a fix for that
+        std::sort(i2cBuses.begin(), i2cBuses.end());
+        BusMap busMap = FindI2CDevices(i2cBuses);
+
+        for (auto &busObj : dbusObjectMap)
+        {
+            objServer.remove_object(busObj.second);
+        }
+
+        dbusObjectMap.clear();
+        UNKNOWN_BUS_OBJECT_COUNT = 0;
+
+        for (auto &devicemap : busMap)
+        {
+            for (auto &device : *devicemap.second)
+            {
+                AddFruObjectToDbus(systemBus, device.second, objServer,
+                                   dbusObjectMap, devicemap.first,
+                                   device.first);
+            }
+        }
+        // todo, get this from a more sensable place
+        std::vector<char> baseboardFru;
+        if (readBaseboardFru(baseboardFru))
+        {
+            AddFruObjectToDbus(systemBus, baseboardFru, objServer,
+                               dbusObjectMap, -1, -1);
+        }
+    } while (pendingCallback);
 }
 
 int main(int argc, char **argv)
@@ -500,27 +510,27 @@
                                std::shared_ptr<dbus::DbusObject>>
         dbusObjectMap;
 
-    rescanBusses(dbusObjectMap, systemBus, objServer);
-
-    auto object = std::make_shared<dbus::DbusObject>(
-        systemBus, "/xyz/openbmc_project/FruDevice");
-    objServer.register_object(object);
     auto iface = std::make_shared<dbus::DbusInterface>(
         "xyz.openbmc_project.FruDeviceManager", systemBus);
-    object->register_interface(iface);
 
     std::atomic_bool threadRunning(false);
+    std::atomic_bool pendingCallback(false);
     std::future<void> future;
 
     iface->register_method("ReScan", [&]() {
-        if (!threadRunning)
+        bool notRunning = false;
+        if (threadRunning.compare_exchange_strong(notRunning, true))
         {
-            threadRunning = true;
             future = std::async(std::launch::async, [&] {
-                rescanBusses(dbusObjectMap, systemBus, objServer);
+                rescanBusses(dbusObjectMap, systemBus, objServer,
+                             pendingCallback);
                 threadRunning = false;
             });
         }
+        else
+        {
+            pendingCallback = true;
+        }
         return std::tuple<>(); // this is a bug in boost-dbus, needs some sort
                                // of return
     });
@@ -541,19 +551,87 @@
             auto findPgood = values.find("pgood");
             if (findPgood != values.end())
             {
-                if (!threadRunning)
+                bool notRunning = false;
+                if (threadRunning.compare_exchange_strong(notRunning, true))
                 {
-                    threadRunning = true;
                     future = std::async(std::launch::async, [&] {
-                        rescanBusses(dbusObjectMap, systemBus, objServer);
+                        rescanBusses(dbusObjectMap, systemBus, objServer,
+                                     pendingCallback);
                         threadRunning = false;
                     });
                 }
+                else
+                {
+                    pendingCallback = true;
+                }
             }
             filter.async_dispatch(eventHandler);
 
         };
     filter.async_dispatch(eventHandler);
+    int fd = inotify_init();
+    int wd = inotify_add_watch(fd, I2C_DEV_LOCATION,
+                               IN_CREATE | IN_MOVED_TO | IN_DELETE);
+    std::array<char, 4096> readBuffer;
+    std::string pendingBuffer;
+    // monitor for new i2c devices
+    boost::asio::posix::stream_descriptor dirWatch(io, fd);
+    std::function<void(const boost::system::error_code, std::size_t)>
+        watchI2cBusses = [&](const boost::system::error_code &ec,
+                             std::size_t bytes_transferred) {
+            if (ec)
+            {
+                std::cout << "Callback Error " << ec << "\n";
+                return;
+            }
+            pendingBuffer += std::string(readBuffer.data(), bytes_transferred);
+            bool devChange = false;
+            while (pendingBuffer.size() > sizeof(inotify_event))
+            {
+                const inotify_event *iEvent =
+                    reinterpret_cast<const inotify_event *>(
+                        pendingBuffer.data());
+                switch (iEvent->mask)
+                {
+                case IN_CREATE:
+                case IN_MOVED_TO:
+                case IN_DELETE:
+                    if (boost::starts_with(std::string(iEvent->name), "i2c"))
+                    {
+                        devChange = true;
+                    }
+                }
+
+                pendingBuffer.erase(0, sizeof(inotify_event) + iEvent->len);
+            }
+            bool notRunning = false;
+            if (devChange &&
+                threadRunning.compare_exchange_strong(notRunning, true))
+            {
+                future = std::async(std::launch::async, [&] {
+                    std::this_thread::sleep_for(std::chrono::seconds(2));
+                    rescanBusses(dbusObjectMap, systemBus, objServer,
+                                 pendingCallback);
+                    threadRunning = false;
+                });
+            }
+            else if (devChange)
+            {
+                pendingCallback = true;
+            }
+            dirWatch.async_read_some(boost::asio::buffer(readBuffer),
+                                     watchI2cBusses);
+        };
+
+    dirWatch.async_read_some(boost::asio::buffer(readBuffer), watchI2cBusses);
+
+    // run the intial scan
+    rescanBusses(dbusObjectMap, systemBus, objServer, pendingCallback);
+
+    auto object = std::make_shared<dbus::DbusObject>(
+        systemBus, "/xyz/openbmc_project/FruDevice");
+    objServer.register_object(object);
+    object->register_interface(iface);
 
     io.run();
     return 0;