Add I2C OCC support for P8 systems

P8 system uses I2C OCC and it uses different driver for occ-hwmon.
Add `--enable-i2c-occ` configure option to enable the support.

It searches i2c device names in sysfs to get all occ-hwmon devices and
use the i2c device name to bind/unbind the driver.

The occ control object path for I2C OCC hwmon becomes something like
/org/open_power/control/3_0050, where 3_0050 is the i2c address.

Change-Id: I8b9d8d4429c563528dc88fb2679b265c53d7a2d5
Signed-off-by: Lei YU <mine260309@gmail.com>
diff --git a/Makefile.am b/Makefile.am
index aecbebb..d71d6e5 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -18,6 +18,7 @@
 	powercap.cpp \
 	org/open_power/OCC/Device/error.cpp \
 	occ_finder.cpp \
+	i2c_occ.cpp \
 	utils.cpp
 
 BUILT_SOURCES =  org/open_power/OCC/Device/error.hpp \
diff --git a/configure.ac b/configure.ac
index 254cc77..b121f55 100644
--- a/configure.ac
+++ b/configure.ac
@@ -57,6 +57,17 @@
 AX_CXX_COMPILE_STDCXX_14([noext])
 AX_APPEND_COMPILE_FLAGS([-Wall -Werror], [CXXFLAGS])
 
+AC_ARG_ENABLE([i2c-occ],
+    AS_HELP_STRING([--enable-i2c-occ], [Enable I2C OCC support])
+)
+AS_IF([test "x$enable_i2c_occ" == "xyes"],
+    AC_MSG_NOTICE([Enabling I2C OCC])
+    [
+        cpp_flags="-DI2C_OCC"
+    ]
+    AC_SUBST([CPPFLAGS], [$cpp_flags])
+)
+
 AC_ARG_VAR(OCC_CONTROL_BUSNAME, [The Dbus busname to own])
 AS_IF([test "x$OCC_CONTROL_BUSNAME" == "x"], [OCC_CONTROL_BUSNAME="org.open_power.OCC.Control"])
 AC_DEFINE_UNQUOTED([OCC_CONTROL_BUSNAME], ["$OCC_CONTROL_BUSNAME"], [The DBus busname to own])
@@ -86,12 +97,21 @@
 AC_DEFINE_UNQUOTED([OCC_MASTER_NAME], ["$OCC_MASTER_NAME"], [The OCC master object name])
 
 AC_ARG_VAR(OCC_HWMON_PATH, [The OCC hwmon path])
-AS_IF([test "x$OCC_HWMON_PATH" == "x"], [OCC_HWMON_PATH="/sys/bus/platform/drivers/occ-hwmon/"])
-AC_DEFINE_UNQUOTED([OCC_HWMON_PATH], ["$OCC_HWMON_PATH"], [The OCC hwmon path])
-
 AC_ARG_VAR(DEV_PATH, [The device path])
-AS_IF([test "x$DEV_PATH" == "x"], [DEV_PATH="/sys/bus/platform/devices/"])
+AC_ARG_VAR(I2C_OCC_DEVICE_NAME, [The device name of i2c occ hwmon])
+AS_IF([test "x$enable_i2c_occ" == "xyes"],
+    # If enable_2c_occ is defined, define occ hwmon path for I2C and its driver's name
+    AS_IF([test "x$OCC_HWMON_PATH" == "x"], [OCC_HWMON_PATH="/sys/bus/i2c/drivers/occ-hwmon/"])
+    AS_IF([test "x$DEV_PATH" == "x"], [DEV_PATH="/sys/bus/i2c/devices"])
+    AS_IF([test "x$I2C_OCC_DEVICE_NAME" == "x"], [I2C_OCC_DEVICE_NAME="p8-occ-hwmon"]),
+
+    # Else, define occ hwmon path for FSI
+    AS_IF([test "x$OCC_HWMON_PATH" == "x"], [OCC_HWMON_PATH="/sys/bus/platform/drivers/occ-hwmon/"])
+    AS_IF([test "x$DEV_PATH" == "x"], [DEV_PATH="/sys/bus/platform/devices/"]),
+)
+AC_DEFINE_UNQUOTED([OCC_HWMON_PATH], ["$OCC_HWMON_PATH"], [The OCC hwmon path])
 AC_DEFINE_UNQUOTED([DEV_PATH], ["$DEV_PATH"], [The device path])
+AC_DEFINE_UNQUOTED([I2C_OCC_DEVICE_NAME], ["$I2C_OCC_DEVICE_NAME"], [The device name of i2c occ hwmon])
 
 AC_ARG_VAR(FSI_SCAN_FILE, [The File to write for initiating FSI rescan])
 AS_IF([test "x$FSI_SCAN_FILE" == "x"], [FSI_SCAN_FILE="/sys/devices/platform/gpio-fsi/fsi0/slave@00:00/00:00:00:0a/fsi1/rescan"])
diff --git a/i2c_occ.cpp b/i2c_occ.cpp
new file mode 100644
index 0000000..ffbb844
--- /dev/null
+++ b/i2c_occ.cpp
@@ -0,0 +1,73 @@
+#include <algorithm>
+#include <fstream>
+
+#include "config.h"
+#include "i2c_occ.hpp"
+
+#ifdef I2C_OCC
+
+namespace i2c_occ
+{
+
+namespace fs = std::experimental::filesystem;
+
+// The device name's length, e.g. "p8-occ-hwmon"
+constexpr auto DEVICE_NAME_LENGTH = 12;
+
+// static assert to make sure the i2c occ device name is expected
+static_assert(sizeof(I2C_OCC_DEVICE_NAME) -1 == DEVICE_NAME_LENGTH);
+
+std::string getFileContent(const fs::path& f)
+{
+    std::string ret(DEVICE_NAME_LENGTH, 0);
+    std::ifstream ifs(f.c_str(), std::ios::binary);
+    if (ifs.is_open())
+    {
+        ifs.read(&ret[0], DEVICE_NAME_LENGTH);
+        ret.resize(ifs.gcount());
+    }
+    return ret;
+}
+
+std::vector<std::string> getOccHwmonDevices(const char* path)
+{
+    std::vector<std::string> result{};
+
+    if (fs::is_directory(path))
+    {
+        for (auto & p : fs::directory_iterator(path))
+        {
+            // Check if a device's name is "p8-occ-hwmon"
+            auto f = p / "name";
+            auto str = getFileContent(f);
+            if (str == I2C_OCC_DEVICE_NAME)
+            {
+                result.emplace_back(p.path().filename());
+            }
+        }
+        std::sort(result.begin(), result.end());
+    }
+    return result;
+}
+
+void i2cToDbus(std::string& path)
+{
+    std::replace(path.begin(), path.end(), '-', '_');
+}
+
+void dbusToI2c(std::string& path)
+{
+    std::replace(path.begin(), path.end(), '_', '-');
+}
+
+std::string getI2cDeviceName(const std::string& dbusPath)
+{
+    auto name = fs::path(dbusPath).filename().string();
+    dbusToI2c(name);
+    return name;
+}
+
+} // namespace i2c_occ
+
+#endif
+
diff --git a/i2c_occ.hpp b/i2c_occ.hpp
new file mode 100644
index 0000000..31b6721
--- /dev/null
+++ b/i2c_occ.hpp
@@ -0,0 +1,69 @@
+#pragma once
+
+#include <experimental/filesystem>
+#include <vector>
+
+#ifdef I2C_OCC
+
+namespace i2c_occ
+{
+
+namespace fs = std::experimental::filesystem;
+
+/** @brief Get file content
+ *
+ * Get at most NAME_LENGTH bytes of content from file. If the file is smaller
+ * than NAME_LENGTH bytes, return the valid parts.
+ *
+ * @param[in] f - The path of file
+ *
+ * @return The string of file content
+ */
+std::string getFileContent(const fs::path& f);
+
+/** @brief Find all devices of occ hwmon
+ *
+ * It iterates in path, finds all occ hwmon devices
+ *
+ * E.g. If "path/3-0050/name" exists and its content is "p8-occ-hwmon",
+ * "3-0050" is returned.
+ *
+ * @param[in] path - The path to search
+ *
+ * @return A vector of strings containing the occ hwmon device path
+ */
+std::vector<std::string> getOccHwmonDevices(const char* path);
+
+/** @brief Convert i2c name to DBus path
+ *
+ * It converts '-' to '_' so that it becomes a valid DBus path.
+ * E.g. 3-0050 converts to 3_0050
+ *
+ * @param[in,out] path - The i2c name to convert
+ */
+void i2cToDbus(std::string& name);
+
+/** @brief Convert DBus path to i2c name
+ *
+ * It converts '_' to '_' so that it becomes a valid i2c name
+ * E.g. 3_0050 converts to 3-0050
+ *
+ * @param[in,out] path - The DBus path to convert
+ */
+void dbusToI2c(std::string& path);
+
+/** @brief Get i2c name from full DBus path
+ *
+ * It extract the i2c name from the full DBus path.
+ * E.g. /org/open_power/control/3_0050 returns "3-0050"
+ *
+ * @param[in] dbusPath - The full DBus path
+ *
+ * @return The i2c name
+ */
+std::string getI2cDeviceName(const std::string& dbusPath);
+
+} // namespace i2c_occ
+
+#endif
+
diff --git a/occ_device.hpp b/occ_device.hpp
index 6538597..a2b4e85 100644
--- a/occ_device.hpp
+++ b/occ_device.hpp
@@ -5,6 +5,8 @@
 #include "occ_events.hpp"
 #include "occ_errors.hpp"
 #include "config.h"
+
+
 namespace open_power
 {
 namespace occ
@@ -34,7 +36,11 @@
         Device(EventPtr& event,
                const std::string& name,
                std::function<void()> callBack = nullptr) :
+#ifdef I2C_OCC
+            config(name),
+#else
             config(name + '-' + "dev0"),
+#endif
             errorFile(fs::path(config) / "occ_error"),
             error(event, errorFile, callBack)
         {
diff --git a/occ_manager.hpp b/occ_manager.hpp
index e79ac3c..7bb1042 100644
--- a/occ_manager.hpp
+++ b/occ_manager.hpp
@@ -12,6 +12,7 @@
 #include "occ_status.hpp"
 #include "occ_finder.hpp"
 #include "config.h"
+#include "i2c_occ.hpp"
 
 namespace sdbusRule = sdbusplus::bus::match::rules;
 namespace open_power
@@ -43,6 +44,10 @@
             bus(bus),
             event(event)
         {
+#ifdef I2C_OCC
+            // I2C OCC status objects are initialized directly
+            initStatusObjects();
+#else
             // Check if CPU inventory exists already.
             auto occs = open_power::occ::finder::get(bus);
             if (occs.empty())
@@ -67,6 +72,7 @@
                     createObjects(occ);
                 }
             }
+#endif
         }
 
     private:
@@ -187,6 +193,31 @@
 
         /** @brief Number of OCCs that are bound */
         uint8_t activeCount = 0;
+
+#ifdef I2C_OCC
+        /** @brief Init Status objects for I2C OCC devices
+         *
+         * It iterates in /sys/bus/i2c/devices, finds all occ hwmon devices
+         * and creates status objects.
+         */
+        void initStatusObjects()
+        {
+            // Make sure we have a valid path string
+            static_assert(sizeof(DEV_PATH) != 0);
+
+            auto deviceNames = i2c_occ::getOccHwmonDevices(DEV_PATH);
+            for (auto& name : deviceNames)
+            {
+                i2c_occ::i2cToDbus(name);
+                auto path = fs::path(OCC_CONTROL_ROOT) / name;
+                statusObjects.emplace_back(
+                    std::make_unique<Status>(
+                        bus,
+                        event,
+                        path.c_str()));
+            }
+        }
+#endif
 };
 
 } // namespace occ
diff --git a/occ_status.hpp b/occ_status.hpp
index 2e12806..23b2080 100644
--- a/occ_status.hpp
+++ b/occ_status.hpp
@@ -7,6 +7,8 @@
 #include <org/open_power/Control/Host/server.hpp>
 #include "occ_events.hpp"
 #include "occ_device.hpp"
+#include "i2c_occ.hpp"
+
 namespace open_power
 {
 namespace occ
@@ -59,7 +61,11 @@
               callBack(callBack),
               instance(((this->path.back() - '0'))),
               device(event,
+#ifdef I2C_OCC
+                     i2c_occ::getI2cDeviceName(path),
+#else
                      name + std::to_string(instance + 1),
+#endif
                      std::bind(&Status::deviceErrorHandler, this)),
               hostControlSignal(
                      bus,
diff --git a/test/Makefile.am b/test/Makefile.am
index d644de8..cd879f5 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -20,5 +20,6 @@
               $(top_builddir)/occ_status.o \
               $(top_builddir)/occ_device.o \
               $(top_builddir)/occ_errors.o \
+              $(top_builddir)/i2c_occ.o \
               $(top_builddir)/utils.o \
               $(top_builddir)/org/open_power/OCC/Device/error.o