Update hwmon fan target sysfs entries

Override the default FanSpeed.Target set implementation so when a target
value is written to the FanSpeed interface it is also updated in the
related fan target sysfs file. This sets a particular fan to the given
target speed.

Resolves openbmc/openbmc#962
Resolves openbmc/phosphor-hwmon#1

Change-Id: I867811737269b3f42d2a0dc15b37782a74f147b8
Signed-off-by: Matthew Barth <msbarth@us.ibm.com>
diff --git a/Makefile.am b/Makefile.am
index b7057ad..1ac9033 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -21,6 +21,7 @@
 	sensorset.cpp \
 	mainloop.cpp \
 	sysfs.cpp \
-	env.cpp
+	env.cpp \
+	fan_speed.cpp
 
 SUBDIRS = . test
diff --git a/fan_speed.cpp b/fan_speed.cpp
new file mode 100644
index 0000000..74bbea3
--- /dev/null
+++ b/fan_speed.cpp
@@ -0,0 +1,26 @@
+#include "fan_speed.hpp"
+#include "hwmon.hpp"
+#include "sysfs.hpp"
+
+namespace hwmon
+{
+
+uint64_t FanSpeed::target(uint64_t value)
+{
+    auto curValue = FanSpeedObject::target();
+
+    if (curValue != value)
+    {
+        //Write target out to sysfs
+        curValue = writeSysfsWithCallout(value,
+                                         sysfsRoot,
+                                         instance,
+                                         type,
+                                         id,
+                                         entry::target);
+    }
+
+    return FanSpeedObject::target(value);
+}
+
+} // namespace hwmon
diff --git a/fan_speed.hpp b/fan_speed.hpp
new file mode 100644
index 0000000..7c7da3a
--- /dev/null
+++ b/fan_speed.hpp
@@ -0,0 +1,59 @@
+#pragma once
+
+#include "interface.hpp"
+
+namespace hwmon
+{
+
+/**
+ * @class FanSpeed
+ * @brief Target fan speed control implementation
+ * @details Derived FanSpeedObject type that writes the target value to sysfs
+ * which in turn sets the fan speed to that target value
+ */
+class FanSpeed : public FanSpeedObject
+{
+    public:
+
+        /**
+         * @brief Constructs FanSpeed Object
+         *
+         * @param[in] sysfsRoot - The hwmon class root
+         * @param[in] instance - The hwmon instance (ex. hwmon1)
+         * @param[in] id - The hwmon id
+         * @param[in] bus - Dbus bus object
+         * @param[in] objPath - Dbus object path
+         * @param[in] defer - Dbus object registration defer
+         */
+        FanSpeed(const std::string& sysfsRoot,
+                 const std::string& instance,
+                 const std::string& id,
+                 sdbusplus::bus::bus& bus,
+                 const char* objPath,
+                 bool defer) : FanSpeedObject(bus, objPath, defer),
+                    sysfsRoot(sysfsRoot),
+                    instance(instance),
+                    id(id)
+        {
+            // Nothing to do here
+        }
+
+        /**
+         * @brief Set the value of target
+         *
+         * @return Value of target
+         */
+        uint64_t target(uint64_t value) override;
+
+    private:
+        /** @brief hwmon class root */
+        std::string sysfsRoot;
+        /** @brief hwmon instance */
+        std::string instance;
+        /** @brief hwmon type */
+        static constexpr auto type = "fan";
+        /** @brief hwmon id */
+        std::string id;
+};
+
+} // namespace hwmon
diff --git a/mainloop.cpp b/mainloop.cpp
index 7af3096..34970b1 100644
--- a/mainloop.cpp
+++ b/mainloop.cpp
@@ -25,6 +25,7 @@
 #include "env.hpp"
 #include "thresholds.hpp"
 #include "targets.hpp"
+#include "fan_speed.hpp"
 
 // Initialization for Warning Objects
 decltype(Thresholds<WarningObject>::setLo) Thresholds<WarningObject>::setLo =
@@ -54,12 +55,6 @@
 decltype(Thresholds<CriticalObject>::alarmHi) Thresholds<CriticalObject>::alarmHi =
     &CriticalObject::criticalAlarmHigh;
 
-// Initialization for Target objects
-decltype(Targets<FanSpeedObject>::setTarget)
-    Targets<FanSpeedObject>::setTarget = &FanSpeedObject::target;
-decltype(Targets<FanSpeedObject>::getTarget)
-    Targets<FanSpeedObject>::getTarget = &FanSpeedObject::target;
-
 
 using namespace std::literals::chrono_literals;
 
@@ -247,7 +242,7 @@
         addThreshold<CriticalObject>(i.first, sensorValue, info);
         //TODO openbmc/openbmc#1347
         //     Handle application restarts to set/refresh fan speed values
-        addTarget<FanSpeedObject>(i.first, _hwmonRoot, _instance, info);
+        addTarget<hwmon::FanSpeed>(i.first, _hwmonRoot, _instance, info);
 
         // All the interfaces have been created.  Go ahead
         // and emit InterfacesAdded.
diff --git a/sysfs.cpp b/sysfs.cpp
index 75a1376..8b67f3e 100644
--- a/sysfs.cpp
+++ b/sysfs.cpp
@@ -16,10 +16,14 @@
 #include <cstdlib>
 #include <experimental/filesystem>
 #include <memory>
-#include <phosphor-logging/log.hpp>
+#include <phosphor-logging/elog.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <xyz/openbmc_project/Control/Device/error.hpp>
 #include "sysfs.hpp"
 #include "util.hpp"
 
+using namespace phosphor::logging;
+
 std::string findHwmon(const std::string& ofNode)
 {
     namespace fs = std::experimental::filesystem;
@@ -89,4 +93,54 @@
     return value;
 }
 
+uint64_t writeSysfsWithCallout(const uint64_t& value,
+                               const std::string& root,
+                               const std::string& instance,
+                               const std::string& type,
+                               const std::string& id,
+                               const std::string& sensor)
+{
+    namespace fs = std::experimental::filesystem;
+
+    std::string valueStr = std::to_string(value);
+    std::ofstream ofs;
+    fs::path instancePath{root};
+    instancePath /= instance;
+    std::string fullPath = make_sysfs_path(instancePath,
+                                           type, id, sensor);
+
+    ofs.exceptions(std::ofstream::failbit
+                   | std::ofstream::badbit
+                   | std::ofstream::eofbit);
+    try
+    {
+        ofs.open(fullPath);
+        ofs << valueStr;
+    }
+    catch (const std::exception& e)
+    {
+        // errno should still reflect the error from the failing open
+        // or write system calls that got us here.
+        auto rc = errno;
+        instancePath /= "device";
+        using namespace sdbusplus::xyz::openbmc_project::Control::Device::Error;
+        try
+        {
+            elog<WriteFailure>(
+                xyz::openbmc_project::Control::Device::
+                    WriteFailure::CALLOUT_ERRNO(rc),
+                xyz::openbmc_project::Control::Device::
+                    WriteFailure::CALLOUT_DEVICE_PATH(
+                        fs::canonical(instancePath).c_str()));
+        }
+        catch (WriteFailure& elog)
+        {
+            commit(elog.name());
+        }
+        exit(EXIT_FAILURE);
+    }
+
+    return value;
+}
+
 // vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
diff --git a/sysfs.hpp b/sysfs.hpp
index a74baaf..cc36ef4 100644
--- a/sysfs.hpp
+++ b/sysfs.hpp
@@ -44,4 +44,24 @@
                          const std::string& id,
                          const std::string& sensor);
 
+ /** @brief Write a hwmon sysfs value
+  *
+  *  Calls exit(3) with bad status on failure
+  *
+  *  @param[in] value - The value to be written
+  *  @param[in] root - The hwmon class root.
+  *  @param[in] instance - The hwmon instance (ex. hwmon1).
+  *  @param[in] type - The hwmon type (ex. fan).
+  *  @param[in] id - The hwmon id (ex. 1).
+  *  @param[in] sensor - The hwmon sensor (ex. target).
+  *
+  *  @returns - The value written
+  */
+uint64_t writeSysfsWithCallout(const uint64_t& value,
+                               const std::string& root,
+                               const std::string& instance,
+                               const std::string& type,
+                               const std::string& id,
+                               const std::string& sensor);
+
 // vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
diff --git a/targets.hpp b/targets.hpp
index 2b0feb3..381ba75 100644
--- a/targets.hpp
+++ b/targets.hpp
@@ -1,6 +1,7 @@
 #pragma once
 
 #include <experimental/filesystem>
+#include "fan_speed.hpp"
 
 /** @class Targets
  *  @brief Target type traits.
@@ -18,11 +19,9 @@
 
 /**@brief Targets specialization for fan speed. */
 template <>
-struct Targets<FanSpeedObject>
+struct Targets<hwmon::FanSpeed>
 {
     static constexpr InterfaceType type = InterfaceType::FAN_SPEED;
-    static uint64_t (FanSpeedObject::*const setTarget)(uint64_t);
-    static uint64_t (FanSpeedObject::*const getTarget)() const;
 };
 
 /** @brief addTarget
@@ -51,13 +50,18 @@
 
     // Check if target sysfs file exists
     auto targetPath = hwmonRoot + '/' + instance;
-    auto sysfsFile = make_sysfs_path(targetPath,
-                                     sensor.first,
-                                     sensor.second,
-                                     hwmon::entry::target);
-    if (fs::exists(sysfsFile))
+    auto sysfsFullPath = make_sysfs_path(targetPath,
+                                         sensor.first,
+                                         sensor.second,
+                                         hwmon::entry::target);
+    if (fs::exists(sysfsFullPath))
     {
-        auto iface = std::make_shared<T>(bus, objPath.c_str(), deferSignals);
+        auto iface = std::make_shared<T>(hwmonRoot,
+                                         instance,
+                                         sensor.second,
+                                         bus,
+                                         objPath.c_str(),
+                                         deferSignals);
         auto type = Targets<T>::type;
         obj[type] = iface;
     }