Read GPIO config from JSON instead of D-Bus

Read the GPIO information for the buttons from
/etc/default/obmc/gpio/gpio_defs.json instead of
from D-Bus, as the D-Bus location for these were a
legacy thing and are going away.

This requires that the GPIO pin name, like 'J1', is converted
to the GPIO number as used in /sys/class/gpio.

The libgpioplus utility will handle most of this for us, with the
exception of needing to look up the base offset in the
/sys/class/gpiochip*/base file, where the specific gpiochip*
directory is determined as following:

1) Find the 'label' file in a /sys/class/gpiochipX/ directory
   and check for it to match GPIO_BASE_LABEL_NAME, which is
   configurable at build time and defaults to the ASPEED BMC
   value.
2) Use the value in the 'base' file in that directory as the
   base offset.

If a platform doesn't need to use this base offset, it can be
turned off with the LOOKUP_GPIO_BASE dial.

Change-Id: I88d29529ffa8a6d13132ac9059b47f86344837ea
Signed-off-by: Matt Spinler <spinler@us.ibm.com>
diff --git a/src/gpio.cpp b/src/gpio.cpp
index 27fc74d..a398d04 100644
--- a/src/gpio.cpp
+++ b/src/gpio.cpp
@@ -16,23 +16,24 @@
 
 #include "gpio.hpp"
 
+#include "settings.hpp"
+
 #include <fcntl.h>
 #include <unistd.h>
 
 #include <experimental/filesystem>
 #include <fstream>
+#include <gpioplus/utility/aspeed.hpp>
 #include <nlohmann/json.hpp>
-#include <phosphor-logging/elog-errors.hpp>
+#include <optional>
 #include <phosphor-logging/log.hpp>
-#include <xyz/openbmc_project/Common/error.hpp>
+#include <tuple>
 
-const static constexpr char* SYSMGR_SERVICE = "org.openbmc.managers.System";
-const static constexpr char* SYSMGR_OBJ_PATH = "/org/openbmc/managers/System";
-const static constexpr char* SYSMGR_INTERFACE = "org.openbmc.managers.System";
-
+const std::string gpioDev = "/sys/class/gpio";
 static constexpr auto gpioDefs = "/etc/default/obmc/gpio/gpio_defs.json";
 
 using namespace phosphor::logging;
+namespace fs = std::experimental::filesystem;
 
 void closeGpio(int fd)
 {
@@ -42,6 +43,49 @@
     }
 }
 
+uint32_t getGpioBase()
+{
+    // Look for a /sys/class/gpio/gpiochip*/label file
+    // with a value of GPIO_BASE_LABEL_NAME.  Then read
+    // the base value from the 'base' file in that directory.
+#ifdef LOOKUP_GPIO_BASE
+    for (auto& f : fs::directory_iterator(gpioDev))
+    {
+        std::string path{f.path()};
+        if (path.find("gpiochip") == std::string::npos)
+        {
+            continue;
+        }
+
+        std::ifstream labelStream{path + "/label"};
+        std::string label;
+        labelStream >> label;
+
+        if (label == GPIO_BASE_LABEL_NAME)
+        {
+            uint32_t base;
+            std::ifstream baseStream{path + "/base"};
+            baseStream >> base;
+            return base;
+        }
+    }
+
+    log<level::ERR>("Could not find GPIO base");
+    throw std::runtime_error("Could not find GPIO base!");
+#else
+    return 0;
+#endif
+}
+
+uint32_t getGpioNum(const std::string& gpioPin)
+{
+    // gpioplus promises that they will figure out how to easily
+    // support multiple BMC vendors when the time comes.
+    auto offset = gpioplus::utility::aspeed::nameToOffset(gpioPin);
+
+    return getGpioBase() + offset;
+}
+
 bool gpioDefined(const std::string& gpioName)
 {
     try
@@ -68,45 +112,66 @@
     return false;
 }
 
+std::optional<std::tuple<int, std::string>>
+    getGpioConfig(const std::string& gpioName)
+{
+
+    try
+    {
+        std::ifstream gpios{gpioDefs};
+        auto json = nlohmann::json::parse(gpios, nullptr, true);
+        auto defs = json["gpio_definitions"];
+
+        auto gpio =
+            std::find_if(defs.begin(), defs.end(), [&gpioName](const auto g) {
+                return gpioName == g["name"];
+            });
+
+        if (gpio != defs.end())
+        {
+            return std::make_tuple(getGpioNum((*gpio)["pin"]),
+                                   (*gpio)["direction"]);
+        }
+        else
+        {
+            log<level::ERR>("Unable to find GPIO in the definitions",
+                            entry("GPIO_NAME=%s", gpioName.c_str()));
+        }
+    }
+    catch (std::exception& e)
+    {
+        log<level::ERR>("Error parsing GPIO JSON", entry("ERROR=%s", e.what()),
+                        entry("GPIO_NAME=%s", gpioName.c_str()));
+    }
+    return {};
+}
+
 int configGpio(const char* gpioName, int* fd, sdbusplus::bus::bus& bus)
 {
-    auto method = bus.new_method_call(SYSMGR_SERVICE, SYSMGR_OBJ_PATH,
-                                      SYSMGR_INTERFACE, "gpioInit");
-
-    method.append(gpioName);
-
-    auto result = bus.call(method);
-
-    if (result.is_method_error())
+    auto config = getGpioConfig(gpioName);
+    if (!config)
     {
-        log<level::ERR>("bus call error!");
         return -1;
     }
 
-    int32_t gpioNum;
-    std::string gpioDev;
-    std::string gpioDirection;
+    auto [gpioNum, gpioDirection] = *config;
 
-    result.read(gpioDev, gpioNum, gpioDirection);
-
-    std::string devPath;
+    std::string devPath{gpioDev};
 
     std::fstream stream;
 
     stream.exceptions(std::ifstream::failbit | std::ifstream::badbit);
 
-    devPath.clear();
-    devPath = gpioDev + "/gpio" + std::to_string(gpioNum) + "/value";
+    devPath += "/gpio" + std::to_string(gpioNum) + "/value";
 
-    std::experimental::filesystem::path fullPath(devPath);
+    fs::path fullPath(devPath);
 
-    if (std::experimental::filesystem::exists(fullPath))
+    if (fs::exists(fullPath))
     {
         log<level::INFO>("GPIO exported", entry("PATH=%s", devPath.c_str()));
     }
     else
     {
-        devPath.clear();
         devPath = gpioDev + "/export";
 
         stream.open(devPath, std::fstream::out);
@@ -127,7 +192,6 @@
 
     if (gpioDirection == "out")
     {
-        devPath.clear();
         devPath = gpioDev + "/gpio" + std::to_string(gpioNum) + "/value";
 
         uint32_t currentValue;
@@ -166,7 +230,6 @@
     }
     else if (gpioDirection == "in")
     {
-        devPath.clear();
         devPath = gpioDev + "/gpio" + std::to_string(gpioNum) + "/direction";
 
         stream.open(devPath, std::fstream::out);
@@ -187,7 +250,6 @@
 
         // For gpio configured as ‘both’, it is an interrupt pin and trigged on
         // both rising and falling signals
-        devPath.clear();
         devPath = gpioDev + "/gpio" + std::to_string(gpioNum) + "/edge";
 
         stream.open(devPath, std::fstream::out);
@@ -204,7 +266,6 @@
         }
     }
 
-    devPath.clear();
     devPath = gpioDev + "/gpio" + std::to_string(gpioNum) + "/value";
 
     *fd = ::open(devPath.c_str(), O_RDWR | O_NONBLOCK);