power-utils: Initially add Updater class

The Updater class is used to do PSU code update, initially add it that
does unbind/bind driver and set PSU present to false/true during the
update.

Tested: Manually verify on Witherspoon that the driver is unbind/bind,
        and the PSU present property is set to false/true during the PSU
        update:
          psutils --update \
          /xyz/openbmc_project/inventory/system/chassis/motherboard/powersupply0 \
          /tmp/images/xxxxxxxx

Signed-off-by: Lei YU <mine260309@gmail.com>
Change-Id: Ic0a9df7687303caeb9a7f21ba00dc33ee76482db
diff --git a/tools/power-utils/test/meson.build b/tools/power-utils/test/meson.build
index 056f4cc..45052ad 100644
--- a/tools/power-utils/test/meson.build
+++ b/tools/power-utils/test/meson.build
@@ -16,3 +16,22 @@
         objects: record_manager,
     )
 )
+
+test(
+    'test_updater',
+    executable(
+        'test_updater',
+        'test_updater.cpp',
+        '../updater.cpp',
+        dependencies: [
+            gtest,
+            phosphor_logging,
+        ],
+        implicit_include_directories: false,
+        include_directories: '../../..',
+        link_with: [
+            libpower,
+        ],
+        objects: record_manager,
+    )
+)
diff --git a/tools/power-utils/test/test_updater.cpp b/tools/power-utils/test/test_updater.cpp
new file mode 100644
index 0000000..2786986
--- /dev/null
+++ b/tools/power-utils/test/test_updater.cpp
@@ -0,0 +1,42 @@
+/**
+ * Copyright © 2019 IBM 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 "../updater.hpp"
+
+#include <gtest/gtest.h>
+
+namespace updater
+{
+namespace internal
+{
+
+std::string getDeviceName(std::string devPath);
+
+} // namespace internal
+} // namespace updater
+
+using namespace updater;
+
+TEST(Updater, getDeviceName)
+{
+    auto ret = internal::getDeviceName("");
+    EXPECT_TRUE(ret.empty());
+
+    ret = internal::getDeviceName("/sys/bus/i2c/devices/3-0069");
+    EXPECT_EQ("3-0069", ret);
+
+    ret = internal::getDeviceName("/sys/bus/i2c/devices/3-0069/");
+    EXPECT_EQ("3-0069", ret);
+}
diff --git a/tools/power-utils/updater.cpp b/tools/power-utils/updater.cpp
index 6b9468c..41aa3eb 100644
--- a/tools/power-utils/updater.cpp
+++ b/tools/power-utils/updater.cpp
@@ -17,15 +17,139 @@
 
 #include "updater.hpp"
 
+#include "utility.hpp"
+
+#include <fstream>
+#include <phosphor-logging/log.hpp>
+
+using namespace phosphor::logging;
+namespace util = phosphor::power::util;
+
 namespace updater
 {
 
+constexpr auto INVENTORY_IFACE = "xyz.openbmc_project.Inventory.Item";
+constexpr auto PRESENT_PROP = "Present";
+
+namespace internal
+{
+
+/* Get the device name from the device path */
+std::string getDeviceName(std::string devPath)
+{
+    if (devPath.back() == '/')
+    {
+        devPath.pop_back();
+    }
+    return fs::path(devPath).stem().string();
+}
+
+std::string getDevicePath(const std::string& psuInventoryPath)
+{
+    auto data = util::loadJSONFromFile(PSU_JSON_PATH);
+
+    if (data == nullptr)
+    {
+        return {};
+    }
+
+    auto devicePath = data["psuDevices"][psuInventoryPath];
+    if (devicePath.empty())
+    {
+        log<level::WARNING>("Unable to find psu devices or path");
+    }
+    return devicePath;
+}
+
+} // namespace internal
+
 bool update(const std::string& psuInventoryPath, const std::string& imageDir)
 {
+    auto devPath = internal::getDevicePath(psuInventoryPath);
+    if (devPath.empty())
+    {
+        return false;
+    }
+
+    // TODO: check if it is ready to update
+    // and return if not ready
+
+    Updater updater(psuInventoryPath, devPath, imageDir);
+    int ret = updater.doUpdate();
+    return ret == 0;
+}
+
+Updater::Updater(const std::string& psuInventoryPath,
+                 const std::string& devPath, const std::string& imageDir) :
+    bus(sdbusplus::bus::new_default()),
+    psuInventoryPath(psuInventoryPath), devPath(devPath),
+    devName(internal::getDeviceName(devPath)), imageDir(imageDir)
+{
+    fs::path p = fs::path(devPath) / "driver";
+    try
+    {
+        driverPath =
+            fs::canonical(p); // Get the path that points to the driver dir
+    }
+    catch (const fs::filesystem_error& e)
+    {
+        log<level::ERR>("Failed to get canonical path",
+                        entry("DEVPATH=%s", devPath.c_str()),
+                        entry("ERROR=%s", e.what()));
+        throw;
+    }
+    bindUnbind(false);
+}
+
+Updater::~Updater()
+{
+    bindUnbind(true);
+}
+
+// During PSU update, it needs to access the PSU i2c device directly, so it
+// needs to unbind the driver during the update, and re-bind after it's done.
+// After unbind, the hwmon sysfs will be gone, and the psu-monitor will report
+// errors. So set the PSU inventory's Present property to false so that
+// psu-monitor will not report any errors.
+void Updater::bindUnbind(bool doBind)
+{
+    if (!doBind)
+    {
+        // Set non-present before unbind the driver
+        setPresent(doBind);
+    }
+    auto p = driverPath;
+    p /= doBind ? "bind" : "unbind";
+    std::ofstream out(p.string());
+    out << devName;
+
+    if (doBind)
+    {
+        // Set to present after bind the driver
+        setPresent(doBind);
+    }
+}
+
+void Updater::setPresent(bool present)
+{
+    try
+    {
+        auto service = util::getService(psuInventoryPath, INVENTORY_IFACE, bus);
+        util::setProperty(INVENTORY_IFACE, PRESENT_PROP, psuInventoryPath,
+                          service, bus, present);
+    }
+    catch (const std::exception& e)
+    {
+        log<level::ERR>("Failed to set present property",
+                        entry("PSU=%s", psuInventoryPath.c_str()),
+                        entry("PRESENT=%d", present));
+    }
+}
+
+int Updater::doUpdate()
+{
     // TODO
-    fprintf(stderr, "psu: %s, image: %s\n", psuInventoryPath.c_str(),
-            imageDir.c_str());
-    return true;
+    return 0;
 }
 
 } // namespace updater
diff --git a/tools/power-utils/updater.hpp b/tools/power-utils/updater.hpp
index 9efa713..0d69c0b 100644
--- a/tools/power-utils/updater.hpp
+++ b/tools/power-utils/updater.hpp
@@ -15,11 +15,15 @@
  */
 #pragma once
 
+#include <filesystem>
+#include <sdbusplus/bus.hpp>
 #include <string>
 
 namespace updater
 {
 
+namespace fs = std::filesystem;
+
 /**
  * Update PSU firmware
  *
@@ -30,4 +34,76 @@
  */
 bool update(const std::string& psuInventoryPath, const std::string& imageDir);
 
+class Updater
+{
+  public:
+    Updater() = delete;
+    Updater(const Updater&) = delete;
+    Updater& operator=(const Updater&) = delete;
+    Updater(Updater&&) = default;
+    Updater& operator=(Updater&&) = default;
+
+    /**
+     * @brief Constructor
+     *
+     * @param psuInventoryPath - The PSU inventory path
+     * @param devPath - The PSU device path
+     * @param imageDir - The update image directory
+     */
+    Updater(const std::string& psuInventoryPath, const std::string& devPath,
+            const std::string& imageDir);
+
+    /** @brief Destructor */
+    ~Updater();
+
+    /** @brief Bind or unbind the driver
+     *
+     * @param doBind - indicate if it's going to bind or unbind the driver
+     */
+    void bindUnbind(bool doBind);
+
+    /** @brief Set the PSU inventory present property
+     *
+     * @param present - The present state to set
+     */
+    void setPresent(bool present);
+
+    /** @brief Do the PSU update
+     *
+     * @return 0 if success, otherwise non-zero
+     */
+    int doUpdate();
+
+  private:
+    /** @brief The sdbusplus DBus bus connection */
+    sdbusplus::bus::bus bus;
+
+    /** @brief The PSU inventory path */
+    std::string psuInventoryPath;
+
+    /** @brief The PSU device path
+     *
+     * Usually it is a device in i2c subsystem, e.g.
+     *   /sys/bus/i2c/devices/3-0068
+     */
+    std::string devPath;
+
+    /** @brief The PSU device name
+     *
+     * Usually it is a i2c device name, e.g.
+     *   3-0068
+     */
+    std::string devName;
+
+    /** @brief The PSU image directory */
+    std::string imageDir;
+
+    /** @brief The PSU device driver's path
+     *
+     * Usually it is the PSU driver, e.g.
+     *   /sys/bus/i2c/drivers/ibm-cffps
+     */
+    fs::path driverPath;
+};
+
 } // namespace updater