Muxes: Add symlink to /dev/i2c* support

It is difficult to tell what mux is what i2c channel
when looking at sysfs or d-bus alone. This creates
/dev/mux/<muxname>/<channelname> symlinks to aid other
applications.

Tested-by:

ls /dev/i2c-mux/Riser_*
/dev/mux/Riser_1_Mux:
PcieSlot1  PcieSlot2  PcieSlot3

/dev/i2c-mux/Riser_2_Mux:
PcieSlot1  PcieSlot2  PcieSlot3

Change-Id: I3485d87ad2546d4bc27092bac91d6add59250736
Signed-off-by: James Feist <james.feist@linux.intel.com>
diff --git a/configurations/1Ux16 Riser.json b/configurations/1Ux16 Riser.json
index 08aa342..9d985e1 100644
--- a/configurations/1Ux16 Riser.json
+++ b/configurations/1Ux16 Riser.json
@@ -36,8 +36,14 @@
             {
                 "Address": "0x72",
                 "Bus": "$bus",
+                "ChannelNames": [
+                    "PcieSlot1",
+                    "PcieSlot2",
+                    "PcieSlot3",
+                    ""
+                ],
                 "Name": "Riser 1 Mux",
-                "Type": "PCA9543Mux"
+                "Type": "PCA9545Mux"
             },
             {
                 "Address": "$address",
@@ -92,8 +98,14 @@
             {
                 "Address": "0x73",
                 "Bus": "$bus",
+                "ChannelNames": [
+                    "PcieSlot1",
+                    "PcieSlot2",
+                    "PcieSlot3",
+                    ""
+                ],
                 "Name": "Riser 2 Mux",
-                "Type": "PCA9543Mux"
+                "Type": "PCA9545Mux"
             },
             {
                 "Address": "$address",
diff --git a/configurations/2Ux8 Riser.json b/configurations/2Ux8 Riser.json
index 49b7a6d..842d26c 100644
--- a/configurations/2Ux8 Riser.json
+++ b/configurations/2Ux8 Riser.json
@@ -36,6 +36,12 @@
             {
                 "Address": "0x72",
                 "Bus": "$bus",
+                "ChannelNames": [
+                    "PcieSlot1",
+                    "PcieSlot2",
+                    "PcieSlot3",
+                    ""
+                ],
                 "Name": "Riser 1 Mux",
                 "Type": "PCA9545Mux"
             },
@@ -92,6 +98,12 @@
             {
                 "Address": "0x73",
                 "Bus": "$bus",
+                "ChannelNames": [
+                    "PcieSlot1",
+                    "PcieSlot2",
+                    "PcieSlot3",
+                    ""
+                ],
                 "Name": "Riser 2 Mux",
                 "Type": "PCA9545Mux"
             },
diff --git a/configurations/A2UL16RISER.json b/configurations/A2UL16RISER.json
index f02cec8..d3850c9 100644
--- a/configurations/A2UL16RISER.json
+++ b/configurations/A2UL16RISER.json
@@ -36,6 +36,12 @@
             {
                 "Address": "0x72",
                 "Bus": "$bus",
+                "ChannelNames": [
+                    "PcieSlot1",
+                    "PcieSlot2",
+                    "PcieSlot3",
+                    ""
+                ],
                 "Name": "Riser 2 Mux",
                 "Type": "PCA9544Mux"
             },
@@ -92,6 +98,12 @@
             {
                 "Address": "0x73",
                 "Bus": "$bus",
+                "ChannelNames": [
+                    "PcieSlot1",
+                    "PcieSlot2",
+                    "PcieSlot3",
+                    ""
+                ],
                 "Name": "Riser 2 Mux",
                 "Type": "PCA9544Mux"
             },
diff --git a/include/devices.hpp b/include/devices.hpp
index 2756731..24cba32 100644
--- a/include/devices.hpp
+++ b/include/devices.hpp
@@ -50,6 +50,9 @@
          {"PCA9545Mux",
           ExportTemplate("pca9545 $Address",
                          "/sys/bus/i2c/devices/i2c-$Bus/new_device")},
+         {"PCA9546Mux",
+          ExportTemplate("pca9546 $Address",
+                         "/sys/bus/i2c/devices/i2c-$Bus/new_device")},
          {"pmbus", ExportTemplate("pmbus $Address",
                                   "/sys/bus/i2c/devices/i2c-$Bus/new_device")},
          {"TMP75", ExportTemplate("tmp75 $Address",
diff --git a/src/Overlay.cpp b/src/Overlay.cpp
index 5596ac3..07b5e60 100644
--- a/src/Overlay.cpp
+++ b/src/Overlay.cpp
@@ -17,6 +17,7 @@
 #include <string>
 #include <iostream>
 #include <regex>
+#include <iomanip>
 #include <boost/process/child.hpp>
 #include <boost/algorithm/string/predicate.hpp>
 #include "filesystem.hpp"
@@ -34,15 +35,13 @@
 constexpr const char *TEMPLATE_CHAR = "$";
 constexpr const char *HEX_FORMAT_STR = "0x";
 constexpr const char *PLATFORM = "aspeed,ast2500";
-constexpr const char *I2C_DEVS_DIR = "/sys/class/i2c-dev";
+constexpr const char *I2C_DEVS_DIR = "/sys/bus/i2c/devices";
+constexpr const char *MUX_SYMLINK_DIR = "/dev/i2c-mux";
 
 // 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,
@@ -106,40 +105,80 @@
     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::filesystem::path> &i2cDevsBefore)
+void linkMux(const std::string &muxName, size_t bus, size_t address,
+             const nlohmann::json::array_t &channelNames)
 {
-    std::vector<std::filesystem::path> i2cDevsAfter;
-    findFiles(std::filesystem::path(I2C_DEVS_DIR),
-              R"(i2c-\d+)", i2cDevsAfter);
-
-    for (const auto &dev : i2cDevsAfter)
+    // create directory first time
+    static bool createDir = false;
+    std::error_code ec;
+    if (!createDir)
     {
-        if (std::find(i2cDevsBefore.begin(), i2cDevsBefore.end(), dev) !=
-            i2cDevsBefore.end())
+        std::filesystem::create_directory(MUX_SYMLINK_DIR, ec);
+        createDir = true;
+    }
+    std::ostringstream hex;
+    hex << std::hex << std::setfill('0') << std::setw(4) << address;
+    const std::string &addressHex = hex.str();
+
+    std::filesystem::path devDir(I2C_DEVS_DIR);
+    devDir /= std::to_string(bus) + "-" + addressHex;
+
+    size_t channel = 0;
+    std::string channelName;
+    std::filesystem::path channelPath = devDir / "channel-0";
+    while (is_symlink(channelPath))
+    {
+        if (channel > channelNames.size())
+        {
+            channelName = std::to_string(channel);
+        }
+        else
+        {
+            const std::string *ptr =
+                channelNames[channel].get_ptr<const std::string *>();
+            if (ptr == nullptr)
+            {
+                channelName = channelNames[channel].dump();
+            }
+            else
+            {
+                channelName = *ptr;
+            }
+        }
+        // if configuration has name empty, don't put it in dev
+        if (channelName.empty())
         {
             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::filesystem::path(devtreeRef);
-        std::string symbolPath = std::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);
+
+        std::filesystem::path bus = std::filesystem::read_symlink(channelPath);
+        const std::string &busName = bus.filename();
+
+        std::string linkDir = MUX_SYMLINK_DIR + std::string("/") + muxName;
+        if (channel == 0)
+        {
+            std::filesystem::create_directory(linkDir, ec);
+        }
+        std::filesystem::create_symlink(
+            "/dev/" + busName, linkDir + std::string("/") + channelName, ec);
+
+        if (ec)
+        {
+            std::cerr << "Failure creating symlink for " << busName << "\n";
+            return;
+        }
+
+        channel++;
+        channelPath = devDir / ("channel-" + std::to_string(channel));
+    }
+    if (channel == 0)
+    {
+        std::cerr << "Mux missing channels " << devDir << "\n";
     }
 }
 
-void exportDevice(const devices::ExportTemplate &exportTemplate,
+void exportDevice(const std::string &type,
+                  const devices::ExportTemplate &exportTemplate,
                   const nlohmann::json &configuration)
 {
 
@@ -148,6 +187,7 @@
     std::string name = "unknown";
     const uint64_t *bus = nullptr;
     const uint64_t *address = nullptr;
+    const nlohmann::json::array_t *channels = nullptr;
 
     for (auto keyPair = configuration.begin(); keyPair != configuration.end();
          keyPair++)
@@ -174,6 +214,11 @@
         {
             address = keyPair.value().get_ptr<const uint64_t *>();
         }
+        else if (keyPair.key() == "ChannelNames")
+        {
+            channels =
+                keyPair.value().get_ptr<const nlohmann::json::array_t *>();
+        }
         boost::replace_all(parameters, TEMPLATE_CHAR + keyPair.key(),
                            subsituteString);
         boost::replace_all(device, TEMPLATE_CHAR + keyPair.key(),
@@ -214,6 +259,10 @@
     }
     deviceFile << parameters;
     deviceFile.close();
+    if (boost::ends_with(type, "Mux") && bus && address && channels)
+    {
+        linkMux(name, *bus, *address, *channels);
+    }
 }
 
 // this is now deprecated
@@ -369,7 +418,7 @@
             auto device = devices::exportTemplates.find(type.c_str());
             if (device != devices::exportTemplates.end())
             {
-                exportDevice(device->second, configuration);
+                exportDevice(type, device->second, configuration);
             }
         }
     }