PEL: Keep track of if host is running

Use the DataInterface class to keep track of if the host is up and
running by keeping track of the OperatingSystemState property on the
xyz.openbmc_project.State.OperatingSystem.Status interface on the
/xyz/openbmc_project/state/host0 object path.

Added a isHostUp() function as well as a subscribeToHostStateChange()
function to allow a user to pass in a function to be run when the host
changes state.

Property values of 'BootComplete' or 'Standby' mean that the host is
running, anything else is considered not running.

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: I096b2bc4014dff9be85a4bbd74ef27193fd338fb
diff --git a/extensions/openpower-pels/data_interface.cpp b/extensions/openpower-pels/data_interface.cpp
index 913faf5..66442c5 100644
--- a/extensions/openpower-pels/data_interface.cpp
+++ b/extensions/openpower-pels/data_interface.cpp
@@ -15,6 +15,8 @@
  */
 #include "data_interface.hpp"
 
+#include <xyz/openbmc_project/State/OperatingSystem/Status/server.hpp>
+
 namespace openpower
 {
 namespace pels
@@ -29,6 +31,7 @@
 {
 constexpr auto objectMapper = "/xyz/openbmc_project/object_mapper";
 constexpr auto systemInv = "/xyz/openbmc_project/inventory/system";
+constexpr auto hostState = "/xyz/openbmc_project/state/host0";
 } // namespace object_path
 
 namespace interface
@@ -36,11 +39,15 @@
 constexpr auto dbusProperty = "org.freedesktop.DBus.Properties";
 constexpr auto objectMapper = "xyz.openbmc_project.ObjectMapper";
 constexpr auto invAsset = "xyz.openbmc_project.Inventory.Decorator.Asset";
+constexpr auto osStatus = "xyz.openbmc_project.State.OperatingSystem.Status";
 } // namespace interface
 
+using namespace sdbusplus::xyz::openbmc_project::State::OperatingSystem::server;
+
 DataInterface::DataInterface(sdbusplus::bus::bus& bus) : _bus(bus)
 {
     readMTMS();
+    readHostState();
 }
 
 void DataInterface::readMTMS()
@@ -113,6 +120,20 @@
     return properties;
 }
 
+void DataInterface::getProperty(const std::string& service,
+                                const std::string& objectPath,
+                                const std::string& interface,
+                                const std::string& property, DBusValue& value)
+{
+
+    auto method = _bus.new_method_call(service.c_str(), objectPath.c_str(),
+                                       interface::dbusProperty, "Get");
+    method.append(interface, property);
+    auto reply = _bus.call(method);
+
+    reply.read(value);
+}
+
 DBusService DataInterface::getService(const std::string& objectPath,
                                       const std::string& interface)
 {
@@ -134,5 +155,67 @@
 
     return std::string{};
 }
+
+void DataInterface::readHostState()
+{
+    _hostUp = false;
+
+    try
+    {
+        auto service = getService(object_path::hostState, interface::osStatus);
+        if (!service.empty())
+        {
+            DBusValue value;
+            getProperty(service, object_path::hostState, interface::osStatus,
+                        "OperatingSystemState", value);
+
+            auto status =
+                Status::convertOSStatusFromString(std::get<std::string>(value));
+
+            if ((status == Status::OSStatus::BootComplete) ||
+                (status == Status::OSStatus::Standby))
+            {
+                _hostUp = true;
+            }
+        }
+    }
+    catch (std::exception& e)
+    {
+        // Not available yet.
+    }
+
+    // Keep up to date by watching for the propertiesChanged signal.
+    _osStateMatch = std::make_unique<sdbusplus::bus::match_t>(
+        _bus,
+        sdbusplus::bus::match::rules::propertiesChanged(object_path::hostState,
+                                                        interface::osStatus),
+        std::bind(std::mem_fn(&DataInterface::osStatePropChanged), this,
+                  std::placeholders::_1));
+}
+
+void DataInterface::osStatePropChanged(sdbusplus::message::message& msg)
+{
+    DBusInterface interface;
+    DBusPropertyMap properties;
+
+    msg.read(interface, properties);
+
+    auto state = properties.find("OperatingSystemState");
+    if (state != properties.end())
+    {
+        auto status = Status::convertOSStatusFromString(
+            std::get<std::string>(state->second));
+
+        bool newHostState = false;
+        if ((status == Status::OSStatus::BootComplete) ||
+            (status == Status::OSStatus::Standby))
+        {
+            newHostState = true;
+        }
+
+        setHostState(newHostState);
+    }
+}
+
 } // namespace pels
 } // namespace openpower
diff --git a/extensions/openpower-pels/data_interface.hpp b/extensions/openpower-pels/data_interface.hpp
index 14acd70..a3c118e 100644
--- a/extensions/openpower-pels/data_interface.hpp
+++ b/extensions/openpower-pels/data_interface.hpp
@@ -1,5 +1,6 @@
 #pragma once
 
+#include <phosphor-logging/log.hpp>
 #include <sdbusplus/bus.hpp>
 #include <sdbusplus/bus/match.hpp>
 
@@ -52,8 +53,72 @@
         return _machineSerialNumber;
     }
 
+    /**
+     * @brief Says if the host is up and running
+     *
+     * @return bool - If the host is running
+     */
+    virtual bool isHostUp() const
+    {
+        return _hostUp;
+    }
+
+    using HostStateChangeFunc = std::function<void(bool)>;
+
+    /**
+     * @brief Register a callback function that will get
+     *        called on all host on/off transitions.
+     *
+     * The void(bool) function will get passed the new
+     * value of the host state.
+     *
+     * @param[in] name - The subscription name
+     * @param[in] func - The function to run
+     */
+    void subscribeToHostStateChange(const std::string& name,
+                                    HostStateChangeFunc func)
+    {
+        _hostChangeCallbacks[name] = func;
+    }
+
+    /**
+     * @brief Unsubscribe from host state changes.
+     *
+     * @param[in] name - The subscription name
+     */
+    void unsubscribeFromHostStateChange(const std::string& name)
+    {
+        _hostChangeCallbacks.erase(name);
+    }
+
   protected:
     /**
+     * @brief Sets the host on/off state and runs any
+     *        callback functions (if there was a change).
+     */
+    void setHostState(bool newState)
+    {
+        if (_hostUp != newState)
+        {
+            _hostUp = newState;
+
+            for (auto& [name, func] : _hostChangeCallbacks)
+            {
+                try
+                {
+                    func(_hostUp);
+                }
+                catch (std::exception& e)
+                {
+                    using namespace phosphor::logging;
+                    log<level::ERR>("A host state change callback threw "
+                                    "an exception");
+                }
+            }
+        }
+    }
+
+    /**
      * @brief The machine type-model.  Always kept up to date
      */
     std::string _machineTypeModel;
@@ -62,6 +127,17 @@
      * @brief The machine serial number.  Always kept up to date
      */
     std::string _machineSerialNumber;
+
+    /**
+     * @brief The host up status.  Always kept up to date.
+     */
+    bool _hostUp = false;
+
+    /**
+     * @brief The map of host state change subscriber
+     *        names to callback functions.
+     */
+    std::map<std::string, HostStateChangeFunc> _hostChangeCallbacks;
 };
 
 /**
@@ -98,6 +174,19 @@
     void readMTMS();
 
     /**
+     * @brief Reads the host state from D-Bus.
+     *
+     * For host on, looks for the values of 'BootComplete' or 'Standby'
+     * in the OperatingSystemState property on the
+     * 'xyz.openbmc_project.State.OperatingSystem.Status' interface
+     * on the '/xyz/openbmc_project/state/host0' path.
+     *
+     * Also adds a properties changed watch on it so the code can be
+     * kept up to date on changes.
+     */
+    void readHostState();
+
+    /**
      * @brief Finds the D-Bus service name that hosts the
      *        passed in path and interface.
      *
@@ -120,6 +209,19 @@
                                      const std::string& interface);
 
     /**
+     * @brief Wrapper for the 'Get' properties method call
+     *
+     * @param[in] service - The D-Bus service to call it on
+     * @param[in] objectPath - The D-Bus object path
+     * @param[in] interface - The interface to get the property on
+     * @param[in] property - The property name
+     * @param[out] value - Filled in with the property value.
+     */
+    void getProperty(const std::string& service, const std::string& objectPath,
+                     const std::string& interface, const std::string& property,
+                     DBusValue& value);
+
+    /**
      * @brief The properties changed callback for the Asset iface
      *        on the system inventory object.
      *
@@ -128,11 +230,24 @@
     void sysAssetPropChanged(sdbusplus::message::message& msg);
 
     /**
+     * @brief The properties changed callback for the OperatingSystemStatus
+     *        interface on the host state object.
+     *
+     * @param[in] msg - The sdbusplus message of the signal
+     */
+    void osStatePropChanged(sdbusplus::message::message& msg);
+
+    /**
      * @brief The match object for the system path's properties
      */
     std::unique_ptr<sdbusplus::bus::match_t> _sysInventoryPropMatch;
 
     /**
+     * @brief The match object for the operating system status.
+     */
+    std::unique_ptr<sdbusplus::bus::match_t> _osStateMatch;
+
+    /**
      * @brief The sdbusplus bus object for making D-Bus calls.
      */
     sdbusplus::bus::bus& _bus;