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/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