Implement processor throttle dbus properties

- create processor throttle dbus objects for each OCC (processor)
- update throttle properties based on OCC poll response data
  or safe mode status.

Throttle data will be made available via Redfish

NAME                                       TYPE      SIGNATURE RESULT/VALUE FLAGS
org.freedesktop.DBus.Introspectable        interface -         -            -
.Introspect                                method    -         s            -
org.freedesktop.DBus.Peer                  interface -         -            -
.GetMachineId                              method    -         s            -
.Ping                                      method    -         -            -
org.freedesktop.DBus.Properties            interface -         -            -
.Get                                       method    ss        v            -
.GetAll                                    method    s         a{sv}        -
.Set                                       method    ssv       -            -
.PropertiesChanged                         signal    sa{sv}as  -            -
xyz.openbmc_project.Control.Power.Throttle interface -         -            -
.ThrottleCauses                            property  as        0            emits-change
.Throttled                                 property  b         false        emits-change

Example of throttled processor (due to a power limit):
as 1 "xyz.openbmc_project.Control.Power.Throttle.ThrottleReasons.PowerLimit"

Change-Id: I0af9d82fab9d694427d0adaa45f4a372d25fbc12
Signed-off-by: Chris Cain <cjcain@us.ibm.com>
diff --git a/app.cpp b/app.cpp
index a7e5f63..1d0ece3 100644
--- a/app.cpp
+++ b/app.cpp
@@ -37,6 +37,7 @@
     // Attach the bus to sd_event to service user requests
     bus.attach_event(eventP.get(), SD_EVENT_PRIORITY_NORMAL);
 
+    // Add object manager interfaces (for mapper)
     sdbusplus::server::manager_t objManager(bus, OCC_CONTROL_ROOT);
 #ifdef READ_OCC_SENSORS
     sdbusplus::server::manager_t objManagerXyz(bus, OCC_SENSORS_ROOT);
@@ -45,6 +46,8 @@
     sdbusplus::server::manager_t objManagerXyzControl(
         bus, "/xyz/openbmc_project/control");
 #endif
+    sdbusplus::server::manager_t objManagerXyzInventory(
+        bus, "/xyz/openbmc_project/inventory");
     open_power::occ::Manager mgr(eventP);
 
     // Claim the bus since all the house keeping is done now
diff --git a/occ_device.cpp b/occ_device.cpp
index 0ca4fdc..26aee75 100644
--- a/occ_device.cpp
+++ b/occ_device.cpp
@@ -118,11 +118,15 @@
 void Device::throttleProcTempCallback(int error)
 {
     statusObject.throttleProcTemp(error);
+    // Update the processor throttle on dbus
+    statusObject.updateThrottle(error, THROTTLED_THERMAL);
 }
 
 void Device::throttleProcPowerCallback(int error)
 {
     statusObject.throttleProcPower(error);
+    // Update the processor throttle on dbus
+    statusObject.updateThrottle(error, THROTTLED_POWER);
 }
 
 void Device::throttleMemTempCallback(int error)
diff --git a/occ_manager.cpp b/occ_manager.cpp
index 4833d5a..69d3bc9 100644
--- a/occ_manager.cpp
+++ b/occ_manager.cpp
@@ -114,6 +114,12 @@
                 }
                 statusObjCreated = true;
                 waitingForAllOccActiveSensors = true;
+
+                // Find/update the processor path associated with each OCC
+                for (auto& obj : statusObjects)
+                {
+                    obj->updateProcAssociation();
+                }
             }
         }
 
@@ -555,6 +561,11 @@
 #ifdef POWER10
     pmode->updateDbusSafeMode(safeMode);
 #endif
+    // Update the processor throttle status on dbus
+    for (auto& obj : statusObjects)
+    {
+        obj->updateThrottle(safeMode, THROTTLED_SAFE);
+    }
 }
 
 void Manager::sbeHRESETResult(instanceID instance, bool success)
diff --git a/occ_status.cpp b/occ_status.cpp
index 85b70ee..b89e469 100644
--- a/occ_status.cpp
+++ b/occ_status.cpp
@@ -18,6 +18,9 @@
 
 using namespace phosphor::logging;
 
+using ThrottleObj =
+    sdbusplus::xyz::openbmc_project::Control::Power::server::Throttle;
+
 // Handles updates to occActive property
 bool Status::occActive(bool value)
 {
@@ -28,6 +31,9 @@
                              .c_str());
         if (value)
         {
+            // Clear prior throttle reason (before setting device active)
+            updateThrottle(false, THROTTLED_ALL);
+
             // Set the device active
             device.setActive(true);
 
@@ -77,6 +83,9 @@
 
             // Set the device inactive
             device.setActive(false);
+
+            // Clear throttles (OCC not active after disabling device)
+            updateThrottle(false, THROTTLED_ALL);
         }
     }
     else if (value && !device.active())
@@ -555,5 +564,121 @@
     }
 }
 
+// Update processor throttle status on dbus
+void Status::updateThrottle(const bool isThrottled, const uint8_t newReason)
+{
+    if (!throttleHandle)
+    {
+        return;
+    }
+
+    uint8_t newThrottleCause = throttleCause;
+
+    if (isThrottled) // throttled due to newReason
+    {
+        if ((newReason & throttleCause) == 0)
+        {
+            // set the bit(s) for passed in reason
+            newThrottleCause |= newReason;
+        }
+        // else no change
+    }
+    else // no longer throttled due to newReason
+    {
+        if ((newReason & throttleCause) != 0)
+        {
+            // clear the bit(s) for passed in reason
+            newThrottleCause &= ~newReason;
+        }
+        // else no change
+    }
+
+    if (newThrottleCause != throttleCause)
+    {
+        if (newThrottleCause == THROTTLED_NONE)
+        {
+            log<level::DEBUG>(
+                fmt::format(
+                    "updateThrottle: OCC{} no longer throttled (prior reason: {})",
+                    instance, throttleCause)
+                    .c_str());
+            throttleCause = THROTTLED_NONE;
+            throttleHandle->throttled(false);
+            throttleHandle->throttleCauses({});
+        }
+        else
+        {
+            log<level::DEBUG>(
+                fmt::format(
+                    "updateThrottle: OCC{} is throttled with reason {} (prior reason: {})",
+                    instance, newThrottleCause, throttleCause)
+                    .c_str());
+            throttleCause = newThrottleCause;
+
+            std::vector<ThrottleObj::ThrottleReasons> updatedCauses;
+            if (throttleCause & THROTTLED_POWER)
+            {
+                updatedCauses.push_back(
+                    throttleHandle->ThrottleReasons::PowerLimit);
+            }
+            if (throttleCause & THROTTLED_THERMAL)
+            {
+                updatedCauses.push_back(
+                    throttleHandle->ThrottleReasons::ThermalLimit);
+            }
+            if (throttleCause & THROTTLED_SAFE)
+            {
+                updatedCauses.push_back(
+                    throttleHandle->ThrottleReasons::ManagementDetectedFault);
+            }
+            throttleHandle->throttleCauses(updatedCauses);
+            throttleHandle->throttled(true);
+        }
+    }
+    // else no change to throttle status
+}
+
+// Get processor path associated with this OCC
+void Status::readProcAssociation()
+{
+    std::string managingPath = path + "/power_managing";
+    log<level::DEBUG>(
+        fmt::format("readProcAssociation: getting endpoints for {} ({})",
+                    managingPath, path)
+            .c_str());
+    try
+    {
+        utils::PropertyValue procPathProperty{};
+        procPathProperty = utils::getProperty(
+            managingPath, "xyz.openbmc_project.Association", "endpoints");
+        auto result = std::get<std::vector<std::string>>(procPathProperty);
+        if (result.size() > 0)
+        {
+            procPath = result[0];
+            log<level::INFO>(
+                fmt::format("readProcAssociation: OCC{} has proc={}", instance,
+                            procPath.c_str())
+                    .c_str());
+        }
+        else
+        {
+            log<level::ERR>(
+                fmt::format(
+                    "readProcAssociation: No processor associated with OCC{} / {}",
+                    instance, path)
+                    .c_str());
+        }
+    }
+    catch (const sdbusplus::exception_t& e)
+    {
+        log<level::ERR>(
+            fmt::format(
+                "readProcAssociation: Unable to get proc assocated with {} - {}",
+                path, e.what())
+                .c_str());
+        procPath = {};
+    }
+}
+
 } // namespace occ
 } // namespace open_power
diff --git a/occ_status.hpp b/occ_status.hpp
index ce3df77..a07c272 100644
--- a/occ_status.hpp
+++ b/occ_status.hpp
@@ -17,6 +17,7 @@
 #include <sdeventplus/event.hpp>
 #include <sdeventplus/utility/timer.hpp>
 #endif
+#include <xyz/openbmc_project/Control/Power/Throttle/server.hpp>
 
 #include <functional>
 
@@ -29,6 +30,9 @@
 namespace Base = sdbusplus::org::open_power::OCC::server;
 using Interface = sdbusplus::server::object_t<Base::Status>;
 
+namespace xyzBase = sdbusplus::xyz::openbmc_project::Control::Power::server;
+using ThrottleInterface = sdbusplus::server::object_t<xyzBase::Throttle>;
+
 // IPMID's host control application
 namespace Control = sdbusplus::org::open_power::Control::server;
 
@@ -50,6 +54,12 @@
 // OCC sysfs name prefix
 const std::string sysfsName = "occ-hwmon";
 
+const uint8_t THROTTLED_NONE = 0x00;
+const uint8_t THROTTLED_POWER = 0x01;
+const uint8_t THROTTLED_THERMAL = 0x02;
+const uint8_t THROTTLED_SAFE = 0x04;
+const uint8_t THROTTLED_ALL = 0xFF;
+
 /** @class Status
  *  @brief Implementation of OCC Active Status
  */
@@ -222,7 +232,6 @@
     {
         return pldmSensorStateReceived;
     }
-
 #endif // POWER10
 
     /** @brief Return the HWMON path for this OCC
@@ -231,10 +240,33 @@
      */
     fs::path getHwmonPath();
 
+    /** @brief Update the processor path associated with this OCC
+     */
+    void updateProcAssociation()
+    {
+        readProcAssociation();
+        if (nullptr != throttleHandle)
+        {
+            throttleHandle.reset();
+        }
+        if (!procPath.empty())
+        {
+            throttleHandle = std::make_unique<ThrottleInterface>(
+                utils::getBus(), procPath.c_str());
+        }
+    }
+
+    /** @brief Update the processor throttle status on dbus
+     */
+    void updateThrottle(const bool isThrottled, const uint8_t reason);
+
   private:
     /** @brief OCC dbus object path */
     std::string path;
 
+    /** @brief Processor path associated with this OCC */
+    std::string procPath;
+
     /** @brief Callback handler to be invoked during property change.
      *         This is a handler in Manager class
      */
@@ -360,6 +392,15 @@
 #ifdef PLDM
     std::function<void(instanceID)> resetCallBack = nullptr;
 #endif
+
+    /** @brief Current throttle reason(s) for this processor */
+    uint8_t throttleCause = THROTTLED_NONE;
+
+    /** @brief Throttle interface for the processor associated with this OCC */
+    std::unique_ptr<ThrottleInterface> throttleHandle;
+
+    /** @brief Read the processor path associated with this OCC */
+    void readProcAssociation();
 };
 
 } // namespace occ
diff --git a/utils.hpp b/utils.hpp
index 8cb8333..eb38c03 100644
--- a/utils.hpp
+++ b/utils.hpp
@@ -19,7 +19,8 @@
 constexpr auto DBUS_PROPERTY_IFACE = "org.freedesktop.DBus.Properties";
 
 // The value of the property(type: variant, contains some basic types)
-using PropertyValue = std::variant<uint32_t, bool, double, std::string>;
+using PropertyValue =
+    std::variant<uint32_t, bool, double, std::string, std::vector<std::string>>;
 
 /** @brief Get the bus connection. */
 static auto& getBus()