Add PMBus class

This class provides the interfaces to read PMBus devices
via sysfs files.

This commit provides 3 interfaces:
 1) Write an integer (to provide: echo 1 > clear_faults)
 2) Read a file that represents a bit in a register.
 3) Read a file that represents a bit in a register within
    a PMBus page (or in something that acts like a page).

Additional ones may be added in the future.

ReadFailure/WriteFailure exceptions will be thrown on errors.

Change-Id: I7ea166da00ddc558a9f25c07e6ad9a16714cc3b6
Signed-off-by: Matt Spinler <spinler@us.ibm.com>
diff --git a/Makefile.am b/Makefile.am
index 72698b7..20ab498 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -10,6 +10,7 @@
 	$(SDBUSPLUS_CFLAGS)
 
 libpower_la_SOURCES = \
+	pmbus.cpp \
 	timer.cpp \
 	utility.cpp
 
diff --git a/pmbus.cpp b/pmbus.cpp
new file mode 100644
index 0000000..85e7c43
--- /dev/null
+++ b/pmbus.cpp
@@ -0,0 +1,138 @@
+/**
+ * Copyright © 2017 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 <experimental/filesystem>
+#include <fstream>
+#include <phosphor-logging/elog.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+#include <xyz/openbmc_project/Control/Device/error.hpp>
+#include <xyz/openbmc_project/Sensor/Device/error.hpp>
+#include "pmbus.hpp"
+
+namespace witherspoon
+{
+namespace pmbus
+{
+
+using namespace phosphor::logging;
+using namespace sdbusplus::xyz::openbmc_project::Common::Error;
+using namespace sdbusplus::xyz::openbmc_project::Control::Device::Error;
+using namespace sdbusplus::xyz::openbmc_project::Sensor::Device::Error;
+namespace fs = std::experimental::filesystem;
+
+std::string PMBus::insertPageNum(const std::string& templateName,
+                                 size_t page)
+{
+    auto name = templateName;
+
+    //insert the page where the P was
+    auto pos = name.find('P');
+    if (pos != std::string::npos)
+    {
+        name.replace(pos, 1, std::to_string(page));
+    }
+
+    return name;
+}
+
+bool PMBus::readBitInPage(const std::string& name, size_t page)
+{
+    auto pagedBit = insertPageNum(name, page);
+    return readBit(pagedBit);
+}
+
+bool PMBus::readBit(const std::string& name)
+{
+    unsigned long int value = 0;
+    std::ifstream file;
+    fs::path path{basePath};
+
+    path /= name;
+
+    file.exceptions(std::ifstream::failbit |
+                    std::ifstream::badbit |
+                    std::ifstream::eofbit);
+
+    try
+    {
+        char* err = NULL;
+        std::string val{1, '\0'};
+
+        file.open(path);
+        file.read(&val[0], 1);
+
+        value = strtoul(val.c_str(), &err, 10);
+
+        if (*err)
+        {
+            log<level::ERR>("Invalid character in sysfs file",
+                            entry("FILE=%s", path.c_str()),
+                            entry("CONTENTS=%s", val.c_str()));
+
+            //Catch below and handle as a read failure
+            elog<InternalFailure>();
+        }
+    }
+    catch (std::exception& e)
+    {
+        auto rc = errno;
+
+        log<level::ERR>("Failed to read sysfs file",
+                entry("FILENAME=%s", path.c_str()));
+
+        elog<ReadFailure>(xyz::openbmc_project::Sensor::Device::
+                          ReadFailure::CALLOUT_ERRNO(rc),
+                          xyz::openbmc_project::Sensor::Device::
+                          ReadFailure::CALLOUT_DEVICE_PATH(
+                              fs::canonical(basePath).c_str()));
+    }
+
+    return value != 0;
+}
+
+void PMBus::write(const std::string& name, int value)
+{
+    std::ofstream file;
+    fs::path path{basePath};
+
+    path /= name;
+
+    file.exceptions(std::ofstream::failbit |
+                    std::ofstream::badbit |
+                    std::ofstream::eofbit);
+
+    try
+    {
+        file.open(path);
+        file << value;
+    }
+    catch (const std::exception& e)
+    {
+        auto rc = errno;
+
+        log<level::ERR>("Failed to write sysfs file",
+                entry("FILENAME=%s", path.c_str()));
+
+        elog<WriteFailure>(xyz::openbmc_project::Control::Device::
+                           WriteFailure::CALLOUT_ERRNO(rc),
+                           xyz::openbmc_project::Control::Device::
+                           WriteFailure::CALLOUT_DEVICE_PATH(
+                               fs::canonical(basePath).c_str()));
+    }
+}
+
+}
+}
diff --git a/pmbus.hpp b/pmbus.hpp
new file mode 100644
index 0000000..fc269ac
--- /dev/null
+++ b/pmbus.hpp
@@ -0,0 +1,108 @@
+#pragma once
+
+#include <experimental/filesystem>
+#include <string>
+#include <vector>
+
+namespace witherspoon
+{
+namespace pmbus
+{
+
+/**
+ * @class PMBus
+ *
+ * This class is an interface to communicating with PMBus devices
+ * by reading and writing sysfs files.
+ */
+class PMBus
+{
+    public:
+
+        PMBus() = delete;
+        ~PMBus() = default;
+        PMBus(const PMBus&) = default;
+        PMBus& operator=(const PMBus&) = default;
+        PMBus(PMBus&&) = default;
+        PMBus& operator=(PMBus&&) = default;
+
+        /**
+         * Constructor
+         *
+         * @param[in] path - path to the sysfs directory
+         */
+        PMBus(const std::string& path) :
+            basePath(path)
+        {
+        }
+
+        /**
+         * Reads a file in sysfs that represents a single bit,
+         * therefore doing a PMBus read.
+         *
+         * @param[in] name - path concatenated to
+         *                   basePath to read
+         *
+         * @return bool - false if result was 0, else true
+         */
+        bool readBit(const std::string& name);
+
+        /**
+         * Reads a file in sysfs that represents a single bit,
+         * where the page number passed in is substituted
+         * into the name in place of the 'P' character in it.
+         *
+         * @param[in] name - path concatenated to
+         *                   basePath to read
+         * @param[in] page - page number
+         *
+         * @return bool - false if result was 0, else true
+         */
+        bool readBitInPage(const std::string& name,
+                           size_t page);
+
+        /**
+         * Writes an integer value to the file, therefore doing
+         * a PMBus write.
+         *
+         * @param[in] name - path concatenated to
+         *                   basePath to write
+         * @param[in] value - the value to write
+         */
+        void write(const std::string& name, int value);
+
+        /**
+         * Returns the sysfs base path of this device
+         */
+        inline const auto& path() const
+        {
+            return basePath;
+        }
+
+        /**
+         * Replaces the 'P' in the string passed in with
+         * the page number passed in.
+         *
+         * For example:
+         *   insertPageNum("inP_enable", 42)
+         *   returns "in42_enable"
+         *
+         * @param[in] templateName - the name string, with a 'P' in it
+         * @param[in] page - the page number to insert where the P was
+         *
+         * @return string - the new string with the page number in it
+         */
+        static std::string insertPageNum(const std::string& templateName,
+                                         size_t page);
+
+    private:
+
+        /**
+         * The sysfs device path
+         */
+        std::experimental::filesystem::path basePath;
+
+};
+
+}
+}