pldm: Add support for OCC state changes

When the state of the OCC changes, host sends stateSensorEvent to
indicate going active or not active which is send as a D-Bus signal
by PLDM daemon. openpower-occ-control app listens for this signal
and updates the OCC D-Bus object. The lowest entity instance number
in the PDRs corresponds to first instance of the OCC.

Tested:

1) On a simics session verified that the sensor event is handled for
OCC going active and OCCActive property is set to true.
2) A sensorEvent is generated for OCC going non active and verified
OCCActive property is set to false.
3) Powered off the host and OCCActive property is set to false.

Signed-off-by: Tom Joseph <tomjoseph@in.ibm.com>
Change-Id: I0469cc483184b66acabc5f0cea1613a7768b3393
diff --git a/Makefile.am b/Makefile.am
index aed6412..28c025f 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -65,6 +65,8 @@
 
 openpower_occ_control_LDFLAGS = $(generic_ld_flags)
 
+include pldm.mk
+
 org/open_power/OCC/Device/error.hpp: ${top_srcdir}/org/open_power/OCC/Device.errors.yaml
 	@mkdir -p `dirname $@`
 	$(SDBUSPLUSPLUS) -r $(top_srcdir) error exception-header org.open_power.OCC.Device > $@
diff --git a/configure.ac b/configure.ac
index 6a3f3b0..0137b6f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -71,6 +71,19 @@
         ]
         AC_SUBST([CPPFLAGS], [$cpp_flags])
     )
+
+    AC_ARG_WITH([host-communication-protocol],
+        AS_HELP_STRING([--with-host-communication-protocol], [To specify the host communication protocol])
+    )
+    AM_CONDITIONAL([ENABLE_PLDM], [test "$with_host_communication_protocol" == "pldm"])
+    AS_IF([test "x$with_host_communication_protocol" == "xpldm"],
+        AC_MSG_NOTICE([Enabling PLDM])
+        [
+            cpp_flags="$CPPFLAGS -DPLDM"
+            AX_PKG_CHECK_MODULES([LIBPLDM], [libpldm])
+        ]
+        AC_SUBST([CPPFLAGS], [$cpp_flags])
+    )
 ])
 
 AC_ARG_VAR(OCC_CONTROL_BUSNAME, [The Dbus busname to own])
diff --git a/occ_manager.cpp b/occ_manager.cpp
index d420eea..f59683d 100644
--- a/occ_manager.cpp
+++ b/occ_manager.cpp
@@ -111,5 +111,12 @@
 }
 #endif
 
+#ifdef PLDM
+bool Manager::updateOCCActive(instanceID instance, bool status)
+{
+    return (statusObjects[instance])->occActive(status);
+}
+#endif
+
 } // namespace occ
 } // namespace open_power
diff --git a/occ_manager.hpp b/occ_manager.hpp
index 20002e4..d8282b1 100644
--- a/occ_manager.hpp
+++ b/occ_manager.hpp
@@ -2,6 +2,9 @@
 
 #include "occ_pass_through.hpp"
 #include "occ_status.hpp"
+#ifdef PLDM
+#include "pldm.hpp"
+#endif
 #include "powercap.hpp"
 
 #include <cstring>
@@ -34,7 +37,15 @@
      *  @param[in] bus   - handle to the bus
      *  @param[in] event - Unique ptr reference to sd_event
      */
-    Manager(sdbusplus::bus::bus& bus, EventPtr& event) : bus(bus), event(event)
+    Manager(sdbusplus::bus::bus& bus, EventPtr& event) :
+        bus(bus), event(event)
+#ifdef PLDM
+        ,
+        pldmHandle(std::make_unique<pldm::Interface>(
+            bus, std::bind(std::mem_fn(&Manager::updateOCCActive), this,
+                           std::placeholders::_1, std::placeholders::_2)))
+#endif
+
     {
 #ifdef I2C_OCC
         // I2C OCC status objects are initialized directly
@@ -114,6 +125,23 @@
      */
     void initStatusObjects();
 #endif
+
+#ifdef PLDM
+    /** @brief Callback handler invoked by the PLDM event handler when state of
+     *         the OCC is toggled by the host. The caller passes the instance
+     *         of the OCC and state of the OCC.
+     *
+     *  @param[in] instance - instance of the OCC
+     *  @param[in] status - true when the OCC goes active and false when the OCC
+     *                      goes inactive
+     *
+     *  @return true if setting the state of OCC is successful and false if it
+     *          fails.
+     */
+    bool updateOCCActive(instanceID instance, bool status);
+
+    std::unique_ptr<pldm::Interface> pldmHandle = nullptr;
+#endif
 };
 
 } // namespace occ
diff --git a/pldm.cpp b/pldm.cpp
new file mode 100644
index 0000000..c520340
--- /dev/null
+++ b/pldm.cpp
@@ -0,0 +1,154 @@
+#include "pldm.hpp"

+

+#include <libpldm/entity.h>

+#include <libpldm/platform.h>

+#include <libpldm/state_set.h>

+

+#include <phosphor-logging/log.hpp>

+

+namespace pldm

+{

+

+using sdbusplus::exception::SdBusError;

+using namespace phosphor::logging;

+

+void Interface::fetchOCCSensorInfo(const PdrList& pdrs,

+                                   SensorToOCCInstance& sensorInstanceMap,

+                                   SensorOffset& sensorOffset)

+{

+    bool offsetFound = false;

+    auto pdr =

+        reinterpret_cast<const pldm_state_sensor_pdr*>(pdrs.front().data());

+    auto possibleStatesPtr = pdr->possible_states;

+    for (auto offset = 0; offset < pdr->composite_sensor_count; offset++)

+    {

+        auto possibleStates =

+            reinterpret_cast<const state_sensor_possible_states*>(

+                possibleStatesPtr);

+

+        if (possibleStates->state_set_id ==

+            PLDM_STATE_SET_OPERATIONAL_RUNNING_STATUS)

+        {

+            sensorOffset = offset;

+            offsetFound = true;

+            break;

+        }

+        possibleStatesPtr += sizeof(possibleStates->state_set_id) +

+                             sizeof(possibleStates->possible_states_size) +

+                             possibleStates->possible_states_size;

+    }

+

+    if (!offsetFound)

+    {

+        log<level::ERR>("pldm: OCC state sensor PDR with StateSetId "

+                        "PLDM_STATE_SET_OPERATIONAL_RUNNING_STATUS not found");

+        return;

+    }

+

+    // To order SensorID based on the EntityInstance

+    std::map<EntityInstance, SensorID> entityInstMap{};

+    for (auto& pdr : pdrs)

+    {

+        auto pdrPtr =

+            reinterpret_cast<const pldm_state_sensor_pdr*>(pdr.data());

+        entityInstMap.emplace(

+            static_cast<EntityInstance>(pdrPtr->entity_instance),

+            static_cast<SensorID>(pdrPtr->sensor_id));

+    }

+

+    open_power::occ::instanceID count = start;

+    for (auto const& pair : entityInstMap)

+    {

+        sensorInstanceMap.emplace(pair.second, count);

+        count++;

+    }

+}

+

+void Interface::sensorEvent(sdbusplus::message::message& msg)

+{

+    if (!isOCCSensorCacheValid())

+    {

+        PdrList pdrs{};

+

+        try

+        {

+            auto method = bus.new_method_call(

+                "xyz.openbmc_project.PLDM", "/xyz/openbmc_project/pldm",

+                "xyz.openbmc_project.PLDM.PDR", "FindStateSensorPDR");

+            method.append(tid, (uint16_t)PLDM_ENTITY_PROC_MODULE,

+                          (uint16_t)PLDM_STATE_SET_OPERATIONAL_RUNNING_STATUS);

+

+            auto responseMsg = bus.call(method);

+            responseMsg.read(pdrs);

+        }

+        catch (const SdBusError& e)

+        {

+            log<level::ERR>("pldm: Failed to fetch the OCC state sensor PDRs",

+                            entry("ERROR=%s", e.what()));

+        }

+

+        if (!pdrs.size())

+        {

+            log<level::ERR>("pldm: OCC state sensor PDRs not present");

+            return;

+        }

+

+        fetchOCCSensorInfo(pdrs, sensorToOCCInstance, sensorOffset);

+    }

+

+    TerminusID tid{};

+    SensorID sensorId{};

+    SensorOffset msgSensorOffset{};

+    EventState eventState{};

+    EventState previousEventState{};

+

+    msg.read(tid, sensorId, sensorOffset, eventState, previousEventState);

+

+    auto sensorEntry = sensorToOCCInstance.find(sensorId);

+    if (sensorEntry == sensorToOCCInstance.end() ||

+        (msgSensorOffset != sensorOffset))

+    {

+        // No action for non matching sensorEvents

+        return;

+    }

+

+    bool newState{};

+    if (eventState == static_cast<EventState>(

+                          PLDM_STATE_SET_OPERATIONAL_RUNNING_STATUS_IN_SERVICE))

+    {

+        newState = 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>("pldm: Updated OCCActive state",

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

+    return;

+}

+

+void Interface::hostStateEvent(sdbusplus::message::message& msg)

+{

+    std::map<std::string, std::variant<std::string>> properties{};

+    std::string interface;

+    msg.read(interface, properties);

+    const auto stateEntry = properties.find("CurrentHostState");

+    if (stateEntry != properties.end())

+    {

+        auto stateEntryValue = stateEntry->second;

+        auto propVal = std::get<std::string>(stateEntryValue);

+        if (propVal == "xyz.openbmc_project.State.Host.HostState.Off")

+        {

+            sensorToOCCInstance.clear();

+        }

+    }

+}

+

+} // namespace pldm
\ No newline at end of file
diff --git a/pldm.hpp b/pldm.hpp
new file mode 100644
index 0000000..76d7e44
--- /dev/null
+++ b/pldm.hpp
@@ -0,0 +1,135 @@
+#pragma once

+

+#include "occ_status.hpp"

+

+#include <sdbusplus/bus/match.hpp>

+

+namespace pldm

+{

+

+namespace MatchRules = sdbusplus::bus::match::rules;

+

+using EntityType = uint16_t;

+using EntityInstance = uint16_t;

+using EventState = uint8_t;

+using PdrList = std::vector<std::vector<uint8_t>>;

+using SensorID = uint16_t;

+using SensorOffset = uint8_t;

+using SensorToOCCInstance = std::map<SensorID, open_power::occ::instanceID>;

+using TerminusID = uint8_t;

+

+/** @brief OCC instance starts with 0 for example "occ0" */

+constexpr open_power::occ::instanceID start = 0;

+

+/** @brief Hardcoded TID */

+constexpr TerminusID tid = 0;

+

+/** @class Interface

+ *

+ *  @brief Abstracts the PLDM details related to the OCC

+ */

+class Interface

+{

+  public:

+    Interface() = delete;

+    ~Interface() = default;

+    Interface(const Interface&) = delete;

+    Interface& operator=(const Interface&) = delete;

+    Interface(Interface&&) = delete;

+    Interface& operator=(Interface&&) = delete;

+

+    /** @brief Constructs the PLDM Interface object for OCC functions

+     *

+     *  @param[in] bus      - reference to systemd bus

+     *  @param[in] callBack - callBack handler to invoke when the OCC state

+     *                        changes.

+     */

+    explicit Interface(

+        sdbusplus::bus::bus& bus,

+        std::function<bool(open_power::occ::instanceID, bool)> callBack) :

+        bus(bus),

+        callBack(callBack),

+        pldmEventSignal(

+            bus,

+            MatchRules::type::signal() +

+                MatchRules::member("StateSensorEvent") +

+                MatchRules::path("/xyz/openbmc_project/pldm") +

+                MatchRules::interface("xyz.openbmc_project.PLDM.Event"),

+            std::bind(std::mem_fn(&Interface::sensorEvent), this,

+                      std::placeholders::_1)),

+        hostStateSignal(

+            bus,

+            MatchRules::propertiesChanged("/xyz/openbmc_project/state/host0",

+                                          "xyz.openbmc_project.State.Host"),

+            std::bind(std::mem_fn(&Interface::hostStateEvent), this,

+                      std::placeholders::_1))

+    {

+    }

+

+    /** @brief Fetch the OCC state sensor PDRs and populate the cache with

+     *         sensorId to OCC instance mapping information and the sensor

+     *         offset for Operational Running Status.

+     *

+     *  @param[in] pdrs - OCC state sensor PDRs

+     *  @param[out] sensorInstanceMap - map of sensorID to OCC instance

+     *  @param[out] sensorOffset - sensor offset of interested state set ID

+     */

+    void fetchOCCSensorInfo(const PdrList& pdrs,

+                            SensorToOCCInstance& sensorInstanceMap,

+                            SensorOffset& sensorOffset);

+

+  private:

+    /** @brief reference to the systemd bus*/

+    sdbusplus::bus::bus& bus;

+

+    /** @brief Callback handler to be invoked when the state of the OCC

+     *         changes

+     */

+    std::function<bool(open_power::occ::instanceID, bool)> callBack = nullptr;

+

+    /** @brief Used to subscribe to D-Bus PLDM StateSensorEvent signal and

+     *         processes if the event corresponds to OCC state change.

+     */

+    sdbusplus::bus::match_t pldmEventSignal;

+

+    /** @brief Used to subscribe for host state change signal */

+    sdbusplus::bus::match_t hostStateSignal;

+

+    /** @brief PLDM Sensor ID to OCC Instance mapping

+     */

+    SensorToOCCInstance sensorToOCCInstance;

+

+    /** @brief Sensor offset of state set ID

+     * PLDM_STATE_SET_OPERATIONAL_RUNNING_STATUS in state sensor PDR.

+     */

+    SensorOffset sensorOffset;

+

+    /** @brief When the OCC state changes host sends PlatformEventMessage

+     *         StateSensorEvent, this function processes the D-Bus signal

+     *         with the sensor event information and invokes the callback

+     *         to change the OCC state.

+     *

+     *  @param[in] msg - data associated with the subscribed signal

+     */

+    void sensorEvent(sdbusplus::message::message& msg);

+

+    /** @brief When the host state changes and if the CurrentHostState is

+     *         xyz.openbmc_project.State.Host.HostState.Off then

+     *         the cache of OCC sensors and effecters mapping is cleared.

+     *

+     *  @param[in] msg - data associated with the subscribed signal

+     */

+    void hostStateEvent(sdbusplus::message::message& msg);

+

+    /** @brief Check if the PDR cache for PLDM OCC sensors is valid

+     *

+     *  @return true if cache is populated and false if the cache is not

+     *          populated.

+     */

+    auto isOCCSensorCacheValid()

+    {

+        return (sensorToOCCInstance.empty() ? false : true);

+    }

+};

+

+} // namespace pldm

diff --git a/pldm.mk b/pldm.mk
new file mode 100644
index 0000000..0733508
--- /dev/null
+++ b/pldm.mk
@@ -0,0 +1,12 @@
+if ENABLE_PLDM

+

+noinst_HEADERS += \

+	pldm.hpp

+libocc_control_la_SOURCES += \

+	pldm.cpp

+openpower_occ_control_LDADD += \

+	$(LIBPLDM_LIBS)

+openpower_occ_control_CXXFLAGS += \

+	$(LIBPLDM_CFLAGS)

+

+endif