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