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/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