Add overlay logic to Entity Manager

Generate overlays and fixup symbols to map virtual i2c
busses to muxes. Also scan muxes in order as they seem
to get confused if we scan them out of order.

Change-Id: Iec3ed49fca22db8537e4474d9d95cab2da574aef
Signed-off-by: James Feist <james.feist@linux.intel.com>
diff --git a/src/EntityManager.cpp b/src/EntityManager.cpp
index a07c39c..1d5e971 100644
--- a/src/EntityManager.cpp
+++ b/src/EntityManager.cpp
@@ -15,6 +15,7 @@
 */
 
 #include <Utils.hpp>
+#include <Overlay.hpp>
 #include <dbus/properties.hpp>
 #include <nlohmann/json.hpp>
 #include <fstream>
@@ -33,8 +34,9 @@
 constexpr const char *OUTPUT_DIR = "/var/configuration/";
 constexpr const char *CONFIGURATION_DIR = "/usr/share/configurations";
 constexpr const char *TEMPLATE_CHAR = "$";
-constexpr const size_t PROPERTIES_CHANGED_UNTIL_FLUSH = 20;
+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;
 
 namespace fs = std::experimental::filesystem;
 struct cmp_str
@@ -459,7 +461,7 @@
         iface->set_properties(properties);
 
         // flush the queue after adding an amount of properties so we don't hang
-        if (flushCount++ > PROPERTIES_CHANGED_UNTIL_FLUSH)
+        if (flushCount++ > PROPERTIES_CHANGED_UNTIL_FLUSH_COUNT)
         {
             objServer.flush();
             flushCount = 0;
@@ -823,8 +825,8 @@
                                                 keyPair.value()
                                                     .get<std::string>()))
                                         {
+                                            exposedObject["status"] = "okay";
                                             expose[bind] = exposedObject;
-                                            expose[bind]["status"] = "okay";
 
                                             foundBind = true;
                                             break;
@@ -873,7 +875,8 @@
     if (threadRunning.compare_exchange_strong(notRunning, true))
     {
         future = std::async(std::launch::async, [&] {
-            std::this_thread::sleep_for(std::chrono::seconds(5));
+            std::this_thread::sleep_for(
+                std::chrono::seconds(SLEEP_AFTER_PROPERTIES_CHANGE_SECONDS));
             auto oldConfiguration = systemConfiguration;
             DBUS_PROBE_OBJECTS.clear();
             rescan(systemConfiguration);
@@ -891,10 +894,14 @@
                     it++;
                 }
             }
+            // todo: for now, only add new configurations, unload to come later
+            // unloadOverlays();
+            loadOverlays(newConfiguration);
             // only post new items to bus for now
             postToDbus(newConfiguration, objServer);
             // this line to be removed in future
             writeJsonFiles(systemConfiguration);
+
             registerCallbacks(dbusMatches, threadRunning, systemConfiguration,
                               objServer);
             threadRunning = false;
@@ -966,7 +973,10 @@
     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);
diff --git a/src/FruDevice.cpp b/src/FruDevice.cpp
index 83b60e2..4eea9a5 100644
--- a/src/FruDevice.cpp
+++ b/src/FruDevice.cpp
@@ -31,7 +31,6 @@
 
 namespace fs = std::experimental::filesystem;
 static constexpr bool DEBUG = false;
-static constexpr size_t SLEEP_SECONDS_AFTER_PGOOD = 5;
 static size_t UNKNOWN_BUS_OBJECT_COUNT = 0;
 
 const static constexpr char *BASEBOARD_FRU_LOCATION =
@@ -43,6 +42,12 @@
 using DeviceMap = boost::container::flat_map<int, std::vector<char>>;
 using BusMap = boost::container::flat_map<int, std::shared_ptr<DeviceMap>>;
 
+static bool isMuxBus(size_t bus)
+{
+    return is_symlink(std::experimental::filesystem::path(
+        "/sys/bus/i2c/devices/i2c-" + std::to_string(bus) + "/mux_device"));
+}
+
 int get_bus_frus(int file, int first, int last, int bus,
                  std::shared_ptr<DeviceMap> devices)
 {
@@ -173,16 +178,26 @@
         auto &device = busMap[bus];
         device = std::make_shared<DeviceMap>();
 
-        // todo: call with boost asio?
-        futures.emplace_back(
-            std::async(std::launch::async, [file, device, bus] {
-                //  i2cdetect by default uses the range 0x03 to 0x77, as this is
-                //  what we
-                //  have tested with, use this range. Could be changed in
-                //  future.
-                get_bus_frus(file, 0x03, 0x77, bus, device);
-                close(file);
-            }));
+        // don't scan muxed buses async as don't want to confuse the mux
+        if (isMuxBus(bus))
+        {
+            get_bus_frus(file, 0x03, 0x77, bus, device);
+            close(file);
+        }
+        else
+        {
+            // todo: call with boost asio?
+            futures.emplace_back(
+                std::async(std::launch::async, [file, device, bus] {
+                    //  i2cdetect by default uses the range 0x03 to 0x77, as
+                    //  this is
+                    //  what we
+                    //  have tested with, use this range. Could be changed in
+                    //  future.
+                    get_bus_frus(file, 0x03, 0x77, bus, device);
+                    close(file);
+                }));
+        }
     }
     for (auto &fut : futures)
     {
@@ -329,12 +344,6 @@
     return true;
 }
 
-static bool isMuxBus(size_t bus)
-{
-    return std::experimental::filesystem::exists(
-        "/sys/bus/i2c/devices/i2c-" + std::to_string(bus) + "/mux_device");
-}
-
 void AddFruObjectToDbus(
     std::shared_ptr<dbus::connection> dbusConn, std::vector<char> &device,
     dbus::DbusObjectServer &objServer,
@@ -438,6 +447,9 @@
         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);
 
     for (auto &busObj : dbusObjectMap)
@@ -476,7 +488,6 @@
         std::cerr << "unable to find i2c devices\n";
         return 1;
     }
-    BusMap busMap = FindI2CDevices(i2cBuses);
 
     boost::asio::io_service io;
     auto systemBus = std::make_shared<dbus::connection>(io, dbus::bus::system);
@@ -534,8 +545,6 @@
                 {
                     threadRunning = true;
                     future = std::async(std::launch::async, [&] {
-                        std::this_thread::sleep_for(
-                            std::chrono::seconds(SLEEP_SECONDS_AFTER_PGOOD));
                         rescanBusses(dbusObjectMap, systemBus, objServer);
                         threadRunning = false;
                     });
diff --git a/src/Overlay.cpp b/src/Overlay.cpp
new file mode 100644
index 0000000..7df8908
--- /dev/null
+++ b/src/Overlay.cpp
@@ -0,0 +1,292 @@
+/*
+// Copyright (c) 2018 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+
+#include <string>
+#include <iostream>
+#include <regex>
+#include <boost/process/child.hpp>
+#include <experimental/filesystem>
+#include <boost/container/flat_map.hpp>
+#include <boost/container/flat_set.hpp>
+#include <nlohmann/json.hpp>
+#include <Overlay.hpp>
+#include <Utils.hpp>
+
+constexpr const char *DT_OVERLAY = "/usr/bin/dtoverlay";
+constexpr const char *DTC = "/usr/bin/dtc";
+constexpr const char *OUTPUT_DIR = "/tmp/overlays";
+constexpr const char *TEMPLATE_DIR = "/usr/share/overlay_templates";
+constexpr const char *TEMPLATE_CHAR = "$";
+constexpr const char *PLATFORM = "aspeed,ast2500";
+constexpr const char *I2C_DEVS_DIR = "/sys/class/i2c-dev";
+
+// some drivers need to be unbind / bind to load device tree changes
+static const boost::container::flat_map<std::string, std::string> FORCE_PROBES =
+    {{"IntelFanConnector", "/sys/bus/platform/drivers/aspeed_pwm_tacho"}};
+
+static const boost::container::flat_set<std::string> MUX_TYPES = {
+    {"PCA9543Mux"}, {"PCA9545Mux"}};
+
+std::regex ILLEGAL_NAME_REGEX("[^A-Za-z0-9_]");
+
+void createOverlay(const std::string &templatePath,
+                   const nlohmann::json &configuration);
+
+void unloadAllOverlays(void)
+{
+    boost::process::child c(DT_OVERLAY, "-d", OUTPUT_DIR, "-R");
+    c.wait();
+}
+
+// this is hopefully temporary, but some drivers can't detect changes
+// without an unbind and bind so this function must exist for now
+void forceProbe(const std::string &driver)
+{
+    std::ofstream driverUnbind(driver + "/unbind");
+    std::ofstream driverBind(driver + "/bind");
+    if (!driverUnbind.is_open())
+    {
+        std::cerr << "force probe error opening " << driver << "\n";
+        return;
+    }
+    if (!driverBind.is_open())
+    {
+        driverUnbind.close();
+        std::cerr << "force probe error opening " << driver << "\n";
+        return;
+    }
+
+    std::experimental::filesystem::path pathObj(driver);
+    for (auto &p : std::experimental::filesystem::directory_iterator(pathObj))
+    {
+        // symlinks are object names
+        if (is_symlink(p))
+        {
+            std::string driverName = p.path().filename();
+            driverUnbind << driverName;
+            driverBind << driverName;
+            break;
+        }
+    }
+    driverUnbind.close();
+    driverBind.close();
+}
+
+// helper function to make json types into string
+std::string jsonToString(const nlohmann::json &in)
+{
+    if (in.type() == nlohmann::json::value_t::string)
+    {
+        return in.get<std::string>();
+    }
+    else if (in.type() == nlohmann::json::value_t::array)
+    {
+        // remove brackets and comma from array
+        std::string array = in.dump();
+        array = array.substr(1, array.size() - 2);
+        boost::replace_all(array, ",", " ");
+        return array;
+    }
+    return in.dump();
+}
+
+// when muxes create new i2c devices, the symbols are not there to map the new
+// i2c devices to the mux address. this looks up the device tree path and adds
+// the new symbols so the new devices can be referenced via the phandle
+void fixupSymbols(
+    const std::vector<std::experimental::filesystem::path> &i2cDevsBefore)
+{
+    std::vector<std::experimental::filesystem::path> i2cDevsAfter;
+    find_files(std::experimental::filesystem::path(I2C_DEVS_DIR),
+               R"(i2c-\d+)", i2cDevsAfter, 0);
+
+    for (const auto &dev : i2cDevsAfter)
+    {
+        if (std::find(i2cDevsBefore.begin(), i2cDevsBefore.end(), dev) !=
+            i2cDevsBefore.end())
+        {
+            continue;
+        }
+        // removes everything before and including the '-' in /path/i2c-##
+        std::string bus =
+            std::regex_replace(dev.string(), std::regex("^.*-"), "");
+        std::string devtreeRef = dev.string() + "/device/of_node";
+        auto devtreePath = std::experimental::filesystem::path(devtreeRef);
+        std::string symbolPath =
+            std::experimental::filesystem::canonical(devtreePath);
+        symbolPath =
+            symbolPath.substr(sizeof("/sys/firmware/devicetree/base") - 1);
+        nlohmann::json configuration = {{"path", symbolPath},
+                                        {"type", "Symbol"},
+                                        {"bus", std::stoul(bus)},
+                                        {"name", "i2c" + bus}};
+        createOverlay(TEMPLATE_DIR + std::string("/Symbol.template"),
+                      configuration);
+    }
+}
+
+void createOverlay(const std::string &templatePath,
+                   const nlohmann::json &configuration)
+{
+    std::ifstream templateFile(templatePath);
+
+    if (!templateFile.is_open())
+    {
+        std::cerr << "createOverlay error opening " << templatePath << "\n";
+        return;
+    }
+    std::stringstream buff;
+    buff << templateFile.rdbuf();
+    std::string templateStr = buff.str();
+    std::string name = "unknown";
+    std::string type = configuration["type"];
+    for (auto keyPair = configuration.begin(); keyPair != configuration.end();
+         keyPair++)
+    {
+        std::string subsituteString;
+
+        // device tree symbols are in decimal
+        if (keyPair.key() == "bus" &&
+            keyPair.value().type() == nlohmann::json::value_t::string)
+        {
+            unsigned int dec =
+                std::stoul(keyPair.value().get<std::string>(), nullptr, 16);
+            subsituteString = std::to_string(dec);
+        }
+        else if (keyPair.key() == "name" &&
+                 keyPair.value().type() == nlohmann::json::value_t::string)
+        {
+            subsituteString = std::regex_replace(
+                keyPair.value().get<std::string>(), ILLEGAL_NAME_REGEX, "_");
+            name = subsituteString;
+        }
+        else
+        {
+            subsituteString = jsonToString(keyPair.value());
+        }
+        boost::replace_all(templateStr, TEMPLATE_CHAR + keyPair.key(),
+                           subsituteString);
+    }
+    // todo: this is a lame way to fill in platform, but we only
+    // care about ast2500 right now
+    boost::replace_all(templateStr, TEMPLATE_CHAR + std::string("platform"),
+                       PLATFORM);
+    std::string dtsFilename =
+        std::string(OUTPUT_DIR) + "/" + name + "_" + type + ".dts";
+    std::string dtboFilename =
+        std::string(OUTPUT_DIR) + "/" + name + "_" + type + ".dtbo";
+
+    std::ofstream out(dtsFilename);
+    if (!out.is_open())
+    {
+        std::cerr << "createOverlay error opening " << dtsFilename << "\n";
+        return;
+    }
+    out << templateStr;
+    out.close();
+
+    // this is for muxes, we need to store the diff of i2c devices before
+    // and after scanning to load new symbols into device tree so that if we
+    // later add devices to the "virtual" i2c device, we can match the phandle
+    // to the correct mux
+    std::vector<std::experimental::filesystem::path> i2cDevsBefore;
+    auto findMux = MUX_TYPES.find(type);
+    if (findMux != MUX_TYPES.end())
+    {
+        find_files(std::experimental::filesystem::path(I2C_DEVS_DIR),
+                   R"(i2c-\d+)", i2cDevsBefore, 0);
+    }
+
+    // compile dtbo and load overlay
+    boost::process::child c1(DTC, "-@", "-q", "-I", "dts", "-O", "dtb", "-o",
+                             dtboFilename, dtsFilename);
+    c1.wait();
+    if (c1.exit_code())
+    {
+        std::cerr << "DTC error with file " << dtsFilename << "\n";
+        return;
+    }
+    boost::process::child c2(DT_OVERLAY, "-d", OUTPUT_DIR, name + "_" + type);
+    c2.wait();
+    if (c2.exit_code())
+    {
+        std::cerr << "DTOverlay error with file " << dtboFilename << "\n";
+        return;
+    }
+    auto findForceProbe = FORCE_PROBES.find(type);
+    if (findForceProbe != FORCE_PROBES.end())
+    {
+        forceProbe(findForceProbe->second);
+    }
+    if (findMux != MUX_TYPES.end())
+    {
+        fixupSymbols(i2cDevsBefore);
+    }
+}
+
+bool loadOverlays(const nlohmann::json &systemConfiguration)
+{
+
+    std::vector<std::experimental::filesystem::path> paths;
+    if (!find_files(std::experimental::filesystem::path(TEMPLATE_DIR),
+                    R"(.*\.template)", paths, 0))
+    {
+        std::cerr << "Unable to find any tempate files in " << TEMPLATE_DIR
+                  << "\n";
+        return false;
+    }
+
+    std::experimental::filesystem::create_directory(OUTPUT_DIR);
+    for (auto entity = systemConfiguration.begin();
+         entity != systemConfiguration.end(); entity++)
+    {
+        auto findExposes = entity.value().find("exposes");
+        if (findExposes == entity.value().end() ||
+            findExposes->type() != nlohmann::json::value_t::array)
+        {
+            continue;
+        }
+
+        for (auto &configuration : *findExposes)
+        {
+            auto findStatus = configuration.find("status");
+            // status missing is assumed to be 'okay'
+            if (findStatus != configuration.end() && *findStatus == "disabled")
+            {
+                continue;
+            }
+            auto findType = configuration.find("type");
+            if (findType == configuration.end() ||
+                findType->type() != nlohmann::json::value_t::string)
+            {
+                continue;
+            }
+            std::string type = findType.value().get<std::string>();
+            std::string typeFile = type + std::string(".template");
+            for (auto path : paths)
+            {
+                if (path.filename() != typeFile)
+                {
+                    continue;
+                }
+                createOverlay(path.string(), configuration);
+                break;
+            }
+        }
+    }
+
+    return true;
+}
\ No newline at end of file
diff --git a/src/Utils.cpp b/src/Utils.cpp
index e63861e..b5c3787 100644
--- a/src/Utils.cpp
+++ b/src/Utils.cpp
@@ -33,14 +33,13 @@
     for (auto &p : fs::recursive_directory_iterator(dir_path))
     {
         std::string path = p.path().string();
-        if (!is_directory(p))
+        if (std::regex_search(path, match, search))
         {
-            if (std::regex_search(path, match, search))
-                found_paths.emplace_back(p.path());
+            found_paths.emplace_back(p.path());
         }
         // since we're using a recursve iterator, these should only be symlink
         // dirs
-        else if (symlink_depth)
+        else if (is_directory(p) && symlink_depth)
         {
             find_files(p.path(), match_string, found_paths, symlink_depth - 1);
         }