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/CMakeLists.txt b/CMakeLists.txt
index 723b300..5115501 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -13,10 +13,12 @@
 set(POWER_DBUS_OBJECT_NAME "xyz/openbmc_project/Chassis/Buttons/Power")
 set(RESET_DBUS_OBJECT_NAME "xyz/openbmc_project/Chassis/Buttons/Reset")
 set(ID_DBUS_OBJECT_NAME "xyz/openbmc_project/Chassis/Buttons/ID")
+set(GPIO_BASE_LABEL_NAME "1e780000.gpio")
 
 add_definitions(-DPOWER_DBUS_OBJECT_NAME="/${POWER_DBUS_OBJECT_NAME}0")
 add_definitions(-DRESET_DBUS_OBJECT_NAME="/${RESET_DBUS_OBJECT_NAME}0")
 add_definitions(-DID_DBUS_OBJECT_NAME="/${ID_DBUS_OBJECT_NAME}0")
+add_definitions(-DGPIO_BASE_LABEL_NAME="${GPIO_BASE_LABEL_NAME}")
 set(SRC_FILES src/power_button.cpp
     src/reset_button.cpp
     src/id_button.cpp
@@ -24,6 +26,13 @@
     src/gpio.cpp
 )
 
+option (
+    LOOKUP_GPIO_BASE
+    "Look up the GPIO base value in /sys/class/gpio. Otherwise use a base of 0." ON
+)
+
+configure_file (settings.hpp.in ${CMAKE_BINARY_DIR}/inc/settings.hpp)
+
 # import sdbusplus
 find_package(PkgConfig REQUIRED)
 pkg_check_modules(SDBUSPLUSPLUS sdbusplus REQUIRED)
diff --git a/settings.hpp.in b/settings.hpp.in
new file mode 100644
index 0000000..e89f312
--- /dev/null
+++ b/settings.hpp.in
@@ -0,0 +1,3 @@
+#pragma once
+
+#cmakedefine LOOKUP_GPIO_BASE
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);