Trigger periodic OCC POLL commands when the OCCs are running

The OCC control app will periodically trigger kernel poll commands
to the OCC when the OCCs are active.
Code change also adds an interface to allow any OCC command to be
sent to an OCC. The pass-through interface was also updated to
use the new command object.

Tested: I did several IPLs on multiple Rainier systems to verify
the changes. I forced OCC resets to ensure polling stopped when
OCCs were disabled and restarted after it came out of reset.

Change-Id: I56970e781a988bb94f17ac38173ace8a68bb5fad
Signed-off-by: Chris Cain <cjcain@us.ibm.com>
diff --git a/Makefile.am b/Makefile.am
index 28c025f..babbe6c 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -8,6 +8,7 @@
 	occ_events.hpp \
 	occ_finder.hpp \
 	occ_presence.hpp \
+	occ_command.hpp \
 	utils.hpp
 
 noinst_LTLIBRARIES = libocc_control.la
@@ -24,13 +25,19 @@
 	occ_device.cpp \
 	occ_errors.cpp \
 	occ_presence.cpp \
+	occ_command.cpp \
 	powercap.cpp \
 	org/open_power/OCC/Device/error.cpp \
 	occ_finder.cpp \
 	i2c_occ.cpp \
 	utils.cpp
 
-openpower_occ_control_LDADD = libocc_control.la -lstdc++fs
+openpower_occ_control_LDADD = \
+	libocc_control.la \
+	-lstdc++fs \
+	$(SDBUSPLUS_LIBS) \
+	$(SDEVENTPLUS_LIBS) \
+	$(FMT_LIBS)
 
 BUILT_SOURCES =  org/open_power/OCC/Device/error.hpp \
                  org/open_power/OCC/Device/error.cpp \
diff --git a/configure.ac b/configure.ac
index 0137b6f..fb24ec6 100644
--- a/configure.ac
+++ b/configure.ac
@@ -49,6 +49,8 @@
     PKG_CHECK_MODULES([SDBUSPLUS], [sdbusplus])
     PKG_CHECK_MODULES([PHOSPHOR_LOGGING], [phosphor-logging])
     PKG_CHECK_MODULES([PHOSPHOR_DBUS_INTERFACES], [phosphor-dbus-interfaces])
+    PKG_CHECK_MODULES([SDEVENTPLUS], [sdeventplus])
+    PKG_CHECK_MODULES([FMT], [fmt])
 
     # Check for sdbusplus
     PKG_CHECK_MODULES([SDBUSPLUS], [sdbusplus])
diff --git a/occ_command.cpp b/occ_command.cpp
new file mode 100644
index 0000000..e9b03a9
--- /dev/null
+++ b/occ_command.cpp
@@ -0,0 +1,276 @@
+#include "config.h"
+
+#include "occ_command.hpp"
+
+#include "elog-errors.hpp"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <fmt/core.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <memory>
+#include <org/open_power/OCC/Device/error.hpp>
+#include <phosphor-logging/elog.hpp>
+#include <phosphor-logging/log.hpp>
+#include <string>
+
+//#define TRACE_PACKETS
+
+namespace open_power
+{
+namespace occ
+{
+
+using namespace phosphor::logging;
+
+// Trace block of data in hex
+void dump_hex(const std::vector<std::uint8_t>& data,
+              const unsigned int data_len)
+{
+    unsigned int dump_length = data.size();
+    if ((data_len > 0) && (data_len < dump_length))
+    {
+        dump_length = data_len;
+    }
+    std::string s;
+    for (uint32_t i = 0; i < dump_length; i++)
+    {
+        if (i % 16 == 0)
+        {
+            s += fmt::format("{:04X}: ", i);
+        }
+        else if (i % 4 == 0)
+        {
+            s += " ";
+        }
+
+        s += fmt::format("{:02X}", data.at(i));
+
+        if ((i % 16 == 15) || (i == (dump_length - 1)))
+        {
+            log<level::INFO>(s.c_str());
+            s.clear();
+        }
+    }
+}
+
+OccCommand::OccCommand(uint8_t instance, sdbusplus::bus::bus& bus,
+                       const char* path) :
+    occInstance(instance),
+    path(path),
+    devicePath(OCC_DEV_PATH + std::to_string((this->path.back() - '0') + 1)),
+    activeStatusSignal(
+        bus, sdbusRule::propertiesChanged(path, "org.open_power.OCC.Status"),
+        std::bind(std::mem_fn(&OccCommand::activeStatusEvent), this,
+                  std::placeholders::_1))
+{
+    log<level::DEBUG>(
+        fmt::format("OccCommand::OccCommand(path={}, devicePath={}", this->path,
+                    devicePath)
+            .c_str());
+}
+
+void OccCommand::openDevice()
+{
+    using namespace sdbusplus::org::open_power::OCC::Device::Error;
+
+    log<level::DEBUG>(
+        fmt::format("OccCommand::openDevice: calling open {}", devicePath)
+            .c_str());
+    fd = open(devicePath.c_str(), O_RDWR | O_NONBLOCK);
+    if (fd < 0)
+    {
+        const int open_errno = errno;
+        log<level::ERR>(
+            fmt::format(
+                "OccCommand::openDevice: open failed (errno={}, path={})",
+                open_errno, devicePath)
+                .c_str());
+        // This would log and terminate since its not handled.
+        elog<OpenFailure>(
+            phosphor::logging::org::open_power::OCC::Device::OpenFailure::
+                CALLOUT_ERRNO(open_errno),
+            phosphor::logging::org::open_power::OCC::Device::OpenFailure::
+                CALLOUT_DEVICE_PATH(devicePath.c_str()));
+    }
+    else
+    {
+        log<level::DEBUG>("OccCommand::openDevice: open success");
+    }
+
+    return;
+}
+
+void OccCommand::closeDevice()
+{
+    if (fd >= 0)
+    {
+        log<level::DEBUG>("OccCommand::closeDevice: calling close()");
+        close(fd);
+        fd = -1;
+    }
+}
+
+CmdStatus OccCommand::send(const std::vector<uint8_t>& command,
+                           std::vector<uint8_t>& response)
+{
+    using namespace sdbusplus::org::open_power::OCC::Device::Error;
+    CmdStatus status = CmdStatus::FAILURE;
+
+    response.clear();
+
+    log<level::DEBUG>("OccCommand::send: calling openDevice()");
+    openDevice();
+
+    if (fd < 0)
+    {
+        // OCC is inactive; empty response
+        return CmdStatus::OPEN_FAILURE;
+    }
+
+#ifdef TRACE_PACKETS
+    uint8_t cmd_type = command[0];
+    log<level::INFO>(
+        fmt::format("OCC{}: Sending 0x{:02X} command (length={}, {})",
+                    occInstance, cmd_type, command.size(), devicePath)
+            .c_str());
+    dump_hex(command);
+#else
+    log<level::DEBUG>("OccCommand::send: calling write()");
+#endif
+    auto rc = write(fd, command.data(), command.size());
+    if ((rc < 0) || (rc != (int)command.size()))
+    {
+        const int write_errno = errno;
+        log<level::ERR>("OccCommand::send: write failed");
+        // This would log and terminate since its not handled.
+        elog<WriteFailure>(
+            phosphor::logging::org::open_power::OCC::Device::WriteFailure::
+                CALLOUT_ERRNO(write_errno),
+            phosphor::logging::org::open_power::OCC::Device::WriteFailure::
+                CALLOUT_DEVICE_PATH(devicePath.c_str()));
+    }
+    else
+    {
+        log<level::DEBUG>("OccCommand::send: write succeeded");
+    }
+
+    // Now read the response. This would be the content of occ-sram
+    while (1)
+    {
+        uint8_t data{};
+        auto len = read(fd, &data, sizeof(data));
+        const int read_errno = errno;
+        if (len > 0)
+        {
+            response.emplace_back(data);
+        }
+        else if (len < 0 && read_errno == EAGAIN)
+        {
+            // We may have data coming still.
+            // This driver does not need a sleep for a retry.
+            continue;
+        }
+        else if (len == 0)
+        {
+            log<level::DEBUG>("OccCommand::send: read completed");
+            // We have read all that we can.
+            status = CmdStatus::SUCCESS;
+            break;
+        }
+        else
+        {
+            log<level::ERR>("OccCommand::send: read failed");
+            // This would log and terminate since its not handled.
+            elog<ReadFailure>(
+                phosphor::logging::org::open_power::OCC::Device::ReadFailure::
+                    CALLOUT_ERRNO(read_errno),
+                phosphor::logging::org::open_power::OCC::Device::ReadFailure::
+                    CALLOUT_DEVICE_PATH(devicePath.c_str()));
+        }
+    }
+
+    if (response.size() > 2)
+    {
+#ifdef TRACE_PACKETS
+        log<level::INFO>(
+            fmt::format(
+                "OCC{}: Received 0x{:02X} response (length={} w/checksum)",
+                occInstance, cmd_type, response.size())
+                .c_str());
+        dump_hex(response, 64);
+#endif
+
+        // Validate checksum (last 2 bytes of response)
+        const unsigned int csumIndex = response.size() - 2;
+        const uint32_t rspChecksum =
+            (response[csumIndex] << 8) + response[csumIndex + 1];
+        uint32_t calcChecksum = 0;
+        for (unsigned int index = 0; index < csumIndex; ++index)
+        {
+            calcChecksum += response[index];
+        }
+        while (calcChecksum > 0xFFFF)
+        {
+            calcChecksum = (calcChecksum & 0xFFFF) + (calcChecksum >> 16);
+        }
+        if (calcChecksum != rspChecksum)
+        {
+            log<level::ERR>(fmt::format("OCC{}: Checksum Mismatch: response "
+                                        "0x{:04X}, calculated 0x{:04X}",
+                                        occInstance, rspChecksum, calcChecksum)
+                                .c_str());
+            dump_hex(response);
+            status = CmdStatus::INVALID_CHECKSUM;
+        }
+        else
+        {
+            // Strip off 2 byte checksum
+            response.pop_back();
+            response.pop_back();
+        }
+    }
+    else
+    {
+        log<level::ERR>(
+            fmt::format("OccCommand::send: Invalid OCC{} response length: {}",
+                        occInstance, response.size())
+                .c_str());
+        status = CmdStatus::FAILURE;
+        dump_hex(response);
+    }
+
+    closeDevice();
+
+    return status;
+}
+
+// Called at OCC Status change signal
+void OccCommand::activeStatusEvent(sdbusplus::message::message& msg)
+{
+    std::string statusInterface;
+    std::map<std::string, std::variant<bool>> msgData;
+    msg.read(statusInterface, msgData);
+
+    auto propertyMap = msgData.find("OccActive");
+    if (propertyMap != msgData.end())
+    {
+        // Extract the OccActive property
+        if (std::get<bool>(propertyMap->second))
+        {
+            occActive = true;
+        }
+        else
+        {
+            occActive = false;
+
+            this->closeDevice();
+        }
+    }
+    return;
+}
+
+} // namespace occ
+} // namespace open_power
diff --git a/occ_command.hpp b/occ_command.hpp
new file mode 100644
index 0000000..8420e3b
--- /dev/null
+++ b/occ_command.hpp
@@ -0,0 +1,114 @@
+#pragma once
+
+#include "occ_errors.hpp"
+
+#include <org/open_power/OCC/PassThrough/server.hpp>
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/server/object.hpp>
+#include <string>
+
+namespace open_power
+{
+namespace occ
+{
+
+// For waiting on signals
+namespace sdbusRule = sdbusplus::bus::match::rules;
+
+enum class CmdStatus
+{
+    SUCCESS,
+    OPEN_FAILURE,
+    FAILURE,
+    INVALID_CHECKSUM
+};
+
+/** @brief Trace block of data in hex with log<level:INFO>
+ *
+ *  @param[in] data - vector containing data to trace
+ *  @param[in] data_len - optional number of bytes to trace
+ *  If 0, entire vector will be traced.
+ */
+void dump_hex(const std::vector<std::uint8_t>& data,
+              const unsigned int data_len = 0);
+
+/** @class OccCommand
+ *  @brief Send commands and process respsonses from the OCC
+ */
+class OccCommand
+{
+  public:
+    OccCommand() = delete;
+    OccCommand(const OccCommand&) = delete;
+    OccCommand& operator=(const OccCommand&) = delete;
+    OccCommand(OccCommand&&) = default;
+    OccCommand& operator=(OccCommand&&) = default;
+
+    /** @brief Ctor to set up which OCC the command will go to
+     *
+     *  @param[in] instance - OCC instance
+     *  @param[in] bus - Bus to attach to
+     *  @param[in] path - Path to attach at
+     */
+    OccCommand(uint8_t instance, sdbusplus::bus::bus& bus, const char* path);
+
+    /** @brief Dtor to clean up and close device */
+    ~OccCommand()
+    {
+        closeDevice();
+    }
+
+    /** @brief Send the command to the OCC and collect the response.
+     * The checksum will be validated and removed from the response.
+     *
+     *  @param[in] command - command to pass-through
+     *  @param[out] response - response
+     *  returns SUCCESS if response was received
+     */
+    CmdStatus send(const std::vector<std::uint8_t>& command,
+                   std::vector<std::uint8_t>& response);
+
+  private:
+    /** @brief Instance number of the target OCC */
+    uint8_t occInstance;
+
+    /** @brief OCC path on the bus */
+    std::string path;
+
+    /** @brief OCC device path
+     *  For now, here is the hard-coded mapping until
+     *  the udev rule is in.
+     *  occ0 --> /dev/occ1
+     *  occ1 --> /dev/occ2
+     *  ...
+     */
+    std::string devicePath;
+
+    /** @brief Indicates whether or not the OCC is currently active */
+    bool occActive = false;
+
+    /** brief file descriptor associated with occ device */
+    int fd = -1;
+
+    /** @brief Subscribe to OCC Status signal
+     *
+     *  Once the OCC status gets to active, only then we will get /dev/occ2
+     *  populated and hence need to wait on that before opening that
+     */
+    sdbusplus::bus::match_t activeStatusSignal;
+
+    /** Opens devicePath and populates file descriptor */
+    void openDevice();
+
+    /** Closed the fd associated with opened device */
+    void closeDevice();
+
+    /** @brief Callback function on OCC Status change signals
+     *
+     *  @param[in]  msg - Data associated with subscribed signal
+     */
+    void activeStatusEvent(sdbusplus::message::message& msg);
+};
+
+} // namespace occ
+} // namespace open_power
diff --git a/occ_errors.cpp b/occ_errors.cpp
index ab7663a..a356378 100644
--- a/occ_errors.cpp
+++ b/occ_errors.cpp
@@ -4,6 +4,7 @@
 
 #include <errno.h>
 #include <fcntl.h>
+#include <fmt/core.h>
 #include <sys/ioctl.h>
 #include <unistd.h>
 
@@ -30,10 +31,14 @@
     using namespace phosphor::logging;
 
     fd = open(file.c_str(), O_RDONLY | O_NONBLOCK);
+    const int open_errno = errno;
     if (fd < 0)
     {
+        log<level::ERR>(
+            fmt::format("Error::openFile: open failed (errno={})", open_errno)
+                .c_str());
         elog<OpenFailure>(phosphor::logging::org::open_power::OCC::Device::
-                              OpenFailure::CALLOUT_ERRNO(errno),
+                              OpenFailure::CALLOUT_ERRNO(open_errno),
                           phosphor::logging::org::open_power::OCC::Device::
                               OpenFailure::CALLOUT_DEVICE_PATH(file.c_str()));
     }
diff --git a/occ_manager.cpp b/occ_manager.cpp
index 8ead2a4..fc9a112 100644
--- a/occ_manager.cpp
+++ b/occ_manager.cpp
@@ -16,6 +16,8 @@
 namespace occ
 {
 
+using namespace phosphor::logging;
+
 void Manager::findAndCreateObjects()
 {
     for (auto id = 0; id < MAX_CPUS; ++id)
@@ -71,7 +73,6 @@
 
 void Manager::statusCallBack(bool status)
 {
-    using namespace phosphor::logging;
     using InternalFailure =
         sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
 
@@ -93,6 +94,24 @@
             obj->addPresenceWatchMaster();
         }
     }
+
+    if ((!_pollTimer->isEnabled()) && (activeCount > 0))
+    {
+        log<level::INFO>(fmt::format("Manager::statusCallBack(): {} OCCs will "
+                                     "be polled every {} seconds",
+                                     activeCount, pollInterval)
+                             .c_str());
+
+        // Send poll and start OCC poll timer
+        pollerTimerExpired();
+    }
+    else if ((_pollTimer->isEnabled()) && (activeCount == 0))
+    {
+        // Stop OCC poll timer
+        log<level::INFO>("Manager::statusCallBack(): OCCs are not running, "
+                         "stopping poll timer");
+        _pollTimer->setEnabled(false);
+    }
 }
 
 #ifdef I2C_OCC
@@ -124,5 +143,31 @@
 }
 #endif
 
+void Manager::pollerTimerExpired()
+{
+    if (activeCount == 0)
+    {
+        // No OCCs running, so poll timer will not be restarted
+        log<level::INFO>("Manager::pollerTimerExpire(): No OCCs running, poll "
+                         "timer not restarted");
+    }
+
+    if (!_pollTimer)
+    {
+        log<level::ERR>(
+            "Manager::pollerTimerExpired() ERROR: Timer not defined");
+        return;
+    }
+
+    for (auto& obj : statusObjects)
+    {
+        // Read sysfs to force kernel to poll OCC
+        obj->readOccState();
+    }
+
+    // Restart OCC poll timer
+    _pollTimer->restartOnce(std::chrono::seconds(pollInterval));
+}
+
 } // namespace occ
 } // namespace open_power
diff --git a/occ_manager.hpp b/occ_manager.hpp
index d8282b1..4db762c 100644
--- a/occ_manager.hpp
+++ b/occ_manager.hpp
@@ -10,6 +10,8 @@
 #include <cstring>
 #include <functional>
 #include <sdbusplus/bus.hpp>
+#include <sdeventplus/event.hpp>
+#include <sdeventplus/utility/timer.hpp>
 #include <vector>
 
 namespace sdbusRule = sdbusplus::bus::match::rules;
@@ -18,6 +20,9 @@
 namespace occ
 {
 
+/** @brief Default time, in seconds, between OCC poll commands */
+constexpr unsigned int defaultPollingInterval = 10;
+
 /** @class Manager
  *  @brief Builds and manages OCC objects
  */
@@ -38,7 +43,12 @@
      *  @param[in] event - Unique ptr reference to sd_event
      */
     Manager(sdbusplus::bus::bus& bus, EventPtr& event) :
-        bus(bus), event(event)
+        bus(bus), event(event), pollInterval(defaultPollingInterval),
+        sdpEvent(sdeventplus::Event::get_default()),
+        _pollTimer(
+            std::make_unique<
+                sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic>>(
+                sdpEvent, std::bind(&Manager::pollerTimerExpired, this)))
 #ifdef PLDM
         ,
         pldmHandle(std::make_unique<pldm::Interface>(
@@ -55,6 +65,7 @@
 #endif
     }
 
+    /** @brief Return the number of bound OCCs */
     inline auto getNumOCCs() const
     {
         return activeCount;
@@ -117,6 +128,20 @@
     /** @brief Number of OCCs that are bound */
     uint8_t activeCount = 0;
 
+    /** @brief Number of seconds between poll commands */
+    uint8_t pollInterval;
+
+    /** @brief Poll timer event */
+    sdeventplus::Event sdpEvent;
+
+    /**
+     * @brief The timer to be used once the OCC goes active.  When it expires,
+     *        a POLL command will be sent to the OCC and then timer restarted.
+     */
+    std::unique_ptr<
+        sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic>>
+        _pollTimer;
+
 #ifdef I2C_OCC
     /** @brief Init Status objects for I2C OCC devices
      *
@@ -142,6 +167,12 @@
 
     std::unique_ptr<pldm::Interface> pldmHandle = nullptr;
 #endif
+
+    /**
+     * @brief Called when poll timer expires and forces a POLL command to the
+     * OCC. The poll timer will then be restarted.
+     * */
+    void pollerTimerExpired();
 };
 
 } // namespace occ
diff --git a/occ_pass_through.cpp b/occ_pass_through.cpp
index cecad37..11ef469 100644
--- a/occ_pass_through.cpp
+++ b/occ_pass_through.cpp
@@ -6,6 +6,7 @@
 
 #include <errno.h>
 #include <fcntl.h>
+#include <fmt/core.h>
 #include <unistd.h>
 
 #include <algorithm>
@@ -14,6 +15,7 @@
 #include <phosphor-logging/elog.hpp>
 #include <phosphor-logging/log.hpp>
 #include <string>
+
 namespace open_power
 {
 namespace occ
@@ -22,115 +24,76 @@
 PassThrough::PassThrough(sdbusplus::bus::bus& bus, const char* path) :
     Iface(bus, path), path(path),
     devicePath(OCC_DEV_PATH + std::to_string((this->path.back() - '0') + 1)),
+    occInstance(this->path.back() - '0'),
     activeStatusSignal(
         bus, sdbusRule::propertiesChanged(path, "org.open_power.OCC.Status"),
         std::bind(std::mem_fn(&PassThrough::activeStatusEvent), this,
-                  std::placeholders::_1))
+                  std::placeholders::_1)),
+    occCmd(occInstance, bus, path)
 {
     // Nothing to do.
 }
 
-void PassThrough::openDevice()
-{
-    using namespace phosphor::logging;
-    using namespace sdbusplus::org::open_power::OCC::Device::Error;
-
-    if (!occActive)
-    {
-        log<level::INFO>("OCC is inactive; cannot perform pass-through");
-        return;
-    }
-
-    fd = open(devicePath.c_str(), O_RDWR | O_NONBLOCK);
-    if (fd < 0)
-    {
-        // This would log and terminate since its not handled.
-        elog<OpenFailure>(
-            phosphor::logging::org::open_power::OCC::Device::OpenFailure::
-                CALLOUT_ERRNO(errno),
-            phosphor::logging::org::open_power::OCC::Device::OpenFailure::
-                CALLOUT_DEVICE_PATH(devicePath.c_str()));
-    }
-    return;
-}
-
-void PassThrough::closeDevice()
-{
-    if (fd >= 0)
-    {
-        close(fd);
-        fd = -1;
-    }
-}
-
 std::vector<int32_t> PassThrough::send(std::vector<int32_t> command)
 {
-    using namespace phosphor::logging;
-    using namespace sdbusplus::org::open_power::OCC::Device::Error;
-
     std::vector<int32_t> response{};
 
-    openDevice();
-
-    if (fd < 0)
-    {
-        // OCC is inactive; empty response
-        return response;
-    }
-
     // OCC only understands [bytes] so need array of bytes. Doing this
     // because rest-server currently treats all int* as 32 bit integer.
-    std::vector<uint8_t> cmdInBytes;
+    std::vector<uint8_t> cmdInBytes, rsp;
     cmdInBytes.resize(command.size());
 
     // Populate uint8_t version of vector.
     std::transform(command.begin(), command.end(), cmdInBytes.begin(),
                    [](decltype(cmdInBytes)::value_type x) { return x; });
 
-    ssize_t size = cmdInBytes.size() * sizeof(decltype(cmdInBytes)::value_type);
-    auto rc = write(fd, cmdInBytes.data(), size);
-    if (rc < 0 || (rc != size))
-    {
-        // This would log and terminate since its not handled.
-        elog<WriteFailure>(
-            phosphor::logging::org::open_power::OCC::Device::WriteFailure::
-                CALLOUT_ERRNO(errno),
-            phosphor::logging::org::open_power::OCC::Device::WriteFailure::
-                CALLOUT_DEVICE_PATH(devicePath.c_str()));
-    }
+    rsp = send(cmdInBytes);
 
-    // Now read the response. This would be the content of occ-sram
-    while (1)
+    response.resize(rsp.size());
+    std::transform(rsp.begin(), rsp.end(), response.begin(),
+                   [](decltype(response)::value_type x) { return x; });
+
+    return response;
+}
+
+std::vector<uint8_t> PassThrough::send(std::vector<uint8_t> command)
+{
+    using namespace phosphor::logging;
+    using namespace sdbusplus::org::open_power::OCC::Device::Error;
+
+    std::vector<uint8_t> response{};
+
+    log<level::DEBUG>(
+        fmt::format("PassThrough::send() Sending 0x{:02X} command to OCC{}",
+                    command.front(), occInstance)
+            .c_str());
+    CmdStatus status = occCmd.send(command, response);
+    if (status == CmdStatus::SUCCESS)
     {
-        uint8_t data{};
-        auto len = read(fd, &data, sizeof(data));
-        if (len > 0)
+        if (response.size() >= 5)
         {
-            response.emplace_back(data);
-        }
-        else if (len < 0 && errno == EAGAIN)
-        {
-            // We may have data coming still.
-            // This driver does not need a sleep for a retry.
-            continue;
-        }
-        else if (len == 0)
-        {
-            // We have read all that we can.
-            break;
+            log<level::DEBUG>(fmt::format("PassThrough::send() "
+                                          "response had {} bytes",
+                                          response.size())
+                                  .c_str());
         }
         else
         {
-            // This would log and terminate since its not handled.
-            elog<ReadFailure>(
-                phosphor::logging::org::open_power::OCC::Device::ReadFailure::
-                    CALLOUT_ERRNO(errno),
-                phosphor::logging::org::open_power::OCC::Device::ReadFailure::
-                    CALLOUT_DEVICE_PATH(devicePath.c_str()));
+            log<level::ERR>("PassThrough::send() Invalid OCC response");
+            dump_hex(response);
         }
     }
-
-    closeDevice();
+    else
+    {
+        if (status == CmdStatus::OPEN_FAILURE)
+        {
+            log<level::WARNING>("PassThrough::send() - OCC not active yet");
+        }
+        else
+        {
+            log<level::ERR>("PassThrough::send() - OCC command failed!");
+        }
+    }
 
     return response;
 }
@@ -153,7 +116,6 @@
         else
         {
             occActive = false;
-            this->closeDevice();
         }
     }
     return;
diff --git a/occ_pass_through.hpp b/occ_pass_through.hpp
index a28eb6a..0c01ae4 100644
--- a/occ_pass_through.hpp
+++ b/occ_pass_through.hpp
@@ -1,6 +1,11 @@
 #pragma once
 
+#include "occ_command.hpp"
+
+#include <fmt/core.h>
+
 #include <org/open_power/OCC/PassThrough/server.hpp>
+#include <phosphor-logging/log.hpp>
 #include <sdbusplus/bus.hpp>
 #include <sdbusplus/server/object.hpp>
 #include <string>
@@ -23,6 +28,7 @@
 {
   public:
     PassThrough() = delete;
+    ~PassThrough() = default;
     PassThrough(const PassThrough&) = delete;
     PassThrough& operator=(const PassThrough&) = delete;
     PassThrough(PassThrough&&) = default;
@@ -34,17 +40,18 @@
      */
     PassThrough(sdbusplus::bus::bus& bus, const char* path);
 
-    ~PassThrough()
-    {
-        closeDevice();
-    }
-
-    /** @brief Pass through command to OCC
+    /** @brief Pass through command to OCC from dbus
      *  @param[in] command - command to pass-through
      *  @returns OCC response as an array
      */
     std::vector<std::int32_t> send(std::vector<std::int32_t> command) override;
 
+    /** @brief Pass through command to OCC from openpower-occ-control
+     *  @param[in] command - command to pass-through
+     *  @returns OCC response as an array
+     */
+    std::vector<std::uint8_t> send(std::vector<std::uint8_t> command);
+
   private:
     /** @brief Pass-through occ path on the bus */
     std::string path;
@@ -58,12 +65,12 @@
      */
     std::string devicePath;
 
+    /** @brief OCC instance number */
+    int occInstance;
+
     /** @brief Indicates whether or not the OCC is currently active */
     bool occActive = false;
 
-    /** brief file descriptor associated with occ device */
-    int fd = -1;
-
     /** @brief Subscribe to OCC Status signal
      *
      *  Once the OCC status gets to active, only then we will get /dev/occ2
@@ -71,11 +78,8 @@
      */
     sdbusplus::bus::match_t activeStatusSignal;
 
-    /** Opens devicePath and populates file descritor */
-    void openDevice();
-
-    /** Closed the fd associated with opened device */
-    void closeDevice();
+    /** @brief Object to send commands to the OCC */
+    OccCommand occCmd;
 
     /** @brief Callback function on OCC Status change signals
      *
diff --git a/occ_status.cpp b/occ_status.cpp
index 502783d..1bf9330 100644
--- a/occ_status.cpp
+++ b/occ_status.cpp
@@ -3,17 +3,23 @@
 #include "occ_sensor.hpp"
 #include "utils.hpp"
 
+#include <fmt/core.h>
+
 #include <phosphor-logging/log.hpp>
 namespace open_power
 {
 namespace occ
 {
+using namespace phosphor::logging;
 
 // Handles updates to occActive property
 bool Status::occActive(bool value)
 {
     if (value != this->occActive())
     {
+        log<level::INFO>(fmt::format("Status::occActive OCC{} changed to {}",
+                                     instance, value)
+                             .c_str());
         if (value)
         {
             // Bind the device
@@ -22,6 +28,9 @@
             // Start watching for errors
             addErrorWatch();
 
+            // Reset last OCC state
+            lastState = 0;
+
             // Call into Manager to let know that we have bound
             if (this->callBack)
             {
@@ -93,13 +102,16 @@
 // Sends message to host control command handler to reset OCC
 void Status::resetOCC()
 {
+    log<level::INFO>(
+        fmt::format(">>Status::resetOCC() - requesting reset for OCC{}",
+                    instance)
+            .c_str());
 #ifdef PLDM
     if (resetCallBack)
     {
         this->resetCallBack(instance);
     }
 #else
-    using namespace phosphor::logging;
     constexpr auto CONTROL_HOST_PATH = "/org/open_power/control/host0";
     constexpr auto CONTROL_HOST_INTF = "org.open_power.Control.Host";
 
@@ -148,5 +160,44 @@
     return;
 }
 
+void Status::readOccState()
+{
+    unsigned int state;
+    const fs::path filename =
+        fs::path(DEV_PATH) /
+        fs::path(sysfsName + "." + std::to_string(instance + 1)) / "occ_state";
+
+    log<level::DEBUG>(
+        fmt::format("Status::readOccState: reading OCC{} state from {}",
+                    instance, filename.c_str())
+            .c_str());
+
+    std::ifstream file(filename, std::ios::in);
+    const int open_errno = errno;
+    if (file)
+    {
+        file >> state;
+        if (state != lastState)
+        {
+            // Trace OCC state changes
+            log<level::INFO>(
+                fmt::format("Status::readOccState: OCC{} state 0x{:02X}",
+                            instance, state)
+                    .c_str());
+            lastState = state;
+        }
+        file.close();
+    }
+    else
+    {
+        // If not able to read, OCC may be offline
+        log<level::DEBUG>(
+            fmt::format("Status::readOccState: open failed (errno={})",
+                        open_errno)
+                .c_str());
+        lastState = 0;
+    }
+}
+
 } // namespace occ
 } // namespace open_power
diff --git a/occ_status.hpp b/occ_status.hpp
index 24dcb0e..abdc3ed 100644
--- a/occ_status.hpp
+++ b/occ_status.hpp
@@ -1,6 +1,7 @@
 #pragma once
 
 #include "i2c_occ.hpp"
+#include "occ_command.hpp"
 #include "occ_device.hpp"
 #include "occ_events.hpp"
 
@@ -94,7 +95,11 @@
                 sdbusRule::argN(0, Control::convertForMessage(
                                        Control::Host::Command::OCCReset)),
             std::bind(std::mem_fn(&Status::hostControlEvent), this,
-                      std::placeholders::_1))
+                      std::placeholders::_1)),
+        occCmd(instance, bus,
+               (fs::path(OCC_CONTROL_ROOT) /
+                (std::string(OCC_NAME) + std::to_string(instance)))
+                   .c_str())
 #ifdef PLDM
         ,
         resetCallBack(resetCallBack)
@@ -142,6 +147,9 @@
         return device.addPresenceWatchMaster();
     }
 
+    /** @brief Read OCC state (will trigger kernel to poll the OCC) */
+    void readOccState();
+
   private:
     /** @brief sdbus handle */
     sdbusplus::bus::bus& bus;
@@ -155,7 +163,10 @@
     std::function<void(bool)> callBack;
 
     /** @brief OCC instance number. Ex, 0,1, etc */
-    int instance;
+    unsigned int instance;
+
+    /** @brief The last state read from the OCC */
+    unsigned int lastState = 0;
 
     /** @brief OCC instance to Sensor definitions mapping */
     static const std::map<instanceID, sensorDefs> sensorMap;
@@ -171,6 +182,9 @@
      **/
     sdbusplus::bus::match_t hostControlSignal;
 
+    /** @brief Command object to send commands to the OCC */
+    OccCommand occCmd;
+
     /** @brief Callback handler when device errors are detected
      *
      *  @param[in]  error - True if an error is reported, false otherwise
diff --git a/pldm.cpp b/pldm.cpp
index e45e544..39437ad 100644
--- a/pldm.cpp
+++ b/pldm.cpp
@@ -2,6 +2,7 @@
 

 #include "file.hpp"

 

+#include <fmt/core.h>

 #include <libpldm/entity.h>

 #include <libpldm/platform.h>

 #include <libpldm/state_set.h>

@@ -114,25 +115,23 @@
         return;

     }

 

-    bool newState{};

     if (eventState == static_cast<EventState>(

                           PLDM_STATE_SET_OPERATIONAL_RUNNING_STATUS_IN_SERVICE))

     {

-        newState = callBack(sensorEntry->second, true);

+        log<level::INFO>(

+            fmt::format("PLDM: OCC{} is RUNNING", sensorEntry->second).c_str());

+        callBack(sensorEntry->second, true);

     }

     else if (eventState ==

              static_cast<EventState>(

                  PLDM_STATE_SET_OPERATIONAL_RUNNING_STATUS_STOPPED))

     {

-        newState = callBack(sensorEntry->second, false);

-    }

-    else

-    {

-        return;

+        log<level::INFO>(

+            fmt::format("PLDM: OCC{} has now STOPPED", sensorEntry->second)

+                .c_str());

+        callBack(sensorEntry->second, false);

     }

 

-    log<level::INFO>("pldm: Updated OCCActive state",

-                     entry("STATE=%s", newState ? "true" : "false"));

     return;

 }

 

@@ -348,4 +347,4 @@
     return;

 }

 

-} // namespace pldm
\ No newline at end of file
+} // namespace pldm

diff --git a/test/Makefile.am b/test/Makefile.am
index 0ded744..1fee251 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -4,7 +4,13 @@
 # Run all 'check' test programs
 TESTS = $(check_PROGRAMS)
 
-utest_LDADD = $(top_builddir)/libocc_control.la -lstdc++fs
+utest_LDADD = \
+	$(top_builddir)/libocc_control.la \
+	-lstdc++fs \
+	-lgmock \
+	$(SDBUSPLUS_LIBS) \
+	$(SDEVENTPLUS_LIBS) \
+	$(FMT_LIBS)
 
 utest_SOURCES = error_files_tests.cpp \
                 utest.cpp \