PEL: Activate service indicators on new PEL

A service indicator is a device used to add the user during system
service actions.  In the case of PELs, service indicators are always
LEDs.

When a new PEL is created or received from the host, code will check the
service indicator policy to see if any indicators need to be set at that
time based on the PEL callouts.

There is a specific policy named LightPath that is the default, and it
is currently the only supported policy.  This policy has a set of rules
to say which called out location codes need their indicators activated.

After it determines the location codes, it looks up the corresponding
D-Bus inventory paths for them, and then looks up the LED group objects
to assert based on those inventory paths.

If, for any reason, the code can't get a complete list of FRU LEDs to
turn on, then it will turn on the System Attention Indicator LED.  It is
still TBD how that is actually done.

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: I771258a8957fb0944ec1f8787086123e068bc6cc
diff --git a/extensions/openpower-pels/manager.cpp b/extensions/openpower-pels/manager.cpp
index 19aeffd..841a514 100644
--- a/extensions/openpower-pels/manager.cpp
+++ b/extensions/openpower-pels/manager.cpp
@@ -18,6 +18,7 @@
 #include "additional_data.hpp"
 #include "json_utils.hpp"
 #include "pel.hpp"
+#include "service_indicators.hpp"
 
 #include <fmt/format.h>
 #include <sys/inotify.h>
@@ -144,6 +145,10 @@
             {
                 scheduleRepoPrune();
             }
+
+            // Activate any resulting service indicators if necessary
+            auto policy = service_indicators::getPolicy(*_dataIface);
+            policy->activate(*pel);
         }
         catch (std::exception& e)
         {
@@ -340,6 +345,10 @@
         }
         log<level::INFO>(msg.c_str());
     }
+
+    // Activate any resulting service indicators if necessary
+    auto policy = service_indicators::getPolicy(*_dataIface);
+    policy->activate(*pel);
 }
 
 sdbusplus::message::unix_fd Manager::getPEL(uint32_t pelID)
diff --git a/extensions/openpower-pels/openpower-pels.mk b/extensions/openpower-pels/openpower-pels.mk
index 1468935..d2fa114 100644
--- a/extensions/openpower-pels/openpower-pels.mk
+++ b/extensions/openpower-pels/openpower-pels.mk
@@ -41,6 +41,7 @@
 	extensions/openpower-pels/registry.cpp \
 	extensions/openpower-pels/src.cpp \
 	extensions/openpower-pels/section_factory.cpp \
+	extensions/openpower-pels/service_indicators.cpp \
 	extensions/openpower-pels/severity.cpp \
 	extensions/openpower-pels/user_header.cpp
 
diff --git a/extensions/openpower-pels/service_indicators.cpp b/extensions/openpower-pels/service_indicators.cpp
new file mode 100644
index 0000000..97eb3fe
--- /dev/null
+++ b/extensions/openpower-pels/service_indicators.cpp
@@ -0,0 +1,97 @@
+/**
+ * Copyright © 2020 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "service_indicators.hpp"
+
+#include <phosphor-logging/log.hpp>
+
+namespace openpower::pels::service_indicators
+{
+
+using namespace phosphor::logging;
+
+std::unique_ptr<Policy> getPolicy(const DataInterfaceBase& dataIface)
+{
+    // At the moment there is just one type of policy.
+    return std::make_unique<LightPath>(dataIface);
+}
+
+bool LightPath::ignore(const PEL& pel) const
+{
+    // TODO
+    return false;
+}
+
+void LightPath::activate(const PEL& pel)
+{
+    if (ignore(pel))
+    {
+        return;
+    }
+
+    // Now that we've gotten this far, we'll need to turn on
+    // the system attention indicator if we don't find other
+    // indicators to turn on.
+    bool sai = true;
+    auto src = pel.primarySRC();
+    const auto& calloutsObj = (*src)->callouts();
+
+    if (calloutsObj && !calloutsObj->callouts().empty())
+    {
+        const auto& callouts = calloutsObj->callouts();
+
+        // From the callouts, find the location codes whose
+        // LEDs need to be turned on.
+        auto locCodes = getLocationCodes(callouts);
+        if (!locCodes.empty())
+        {
+            // Find the LED groups for those location codes.
+            auto ledPaths = getLEDGroupPaths(locCodes);
+            if (!ledPaths.empty())
+            {
+                // Tell the LED groups to assert their LEDs.
+                assertLEDs(ledPaths);
+                sai = false;
+            }
+        }
+    }
+
+    if (sai)
+    {
+        log<level::INFO>("The System Attention Indicator needs to be turned "
+                         "on, when available");
+    }
+}
+
+std::vector<std::string> LightPath::getLocationCodes(
+    const std::vector<std::unique_ptr<src::Callout>>& callouts) const
+{
+    // TODO
+    return {};
+}
+
+std::vector<std::string> LightPath::getLEDGroupPaths(
+    const std::vector<std::string>& locationCodes) const
+{
+    // TODO
+    return {};
+}
+
+void LightPath::assertLEDs(const std::vector<std::string>& ledGroups) const
+{
+    // TODO
+}
+
+} // namespace openpower::pels::service_indicators
diff --git a/extensions/openpower-pels/service_indicators.hpp b/extensions/openpower-pels/service_indicators.hpp
new file mode 100644
index 0000000..36eacf8
--- /dev/null
+++ b/extensions/openpower-pels/service_indicators.hpp
@@ -0,0 +1,131 @@
+#pragma once
+
+#include "data_interface.hpp"
+#include "pel.hpp"
+
+namespace openpower::pels::service_indicators
+{
+
+/**
+ * @class Policy
+ *
+ * The base class for service indicator policies.
+ */
+class Policy
+{
+  public:
+    Policy() = delete;
+    virtual ~Policy() = default;
+    Policy(const Policy&) = default;
+    Policy& operator=(const Policy&) = default;
+    Policy(Policy&&) = default;
+    Policy& operator=(Policy&&) = default;
+
+    /**
+     * @brief Constructor
+     *
+     * @param[in] dataIface - The DataInterface object
+     */
+    explicit Policy(const DataInterfaceBase& dataIface) : _dataIface(dataIface)
+    {
+    }
+
+    /**
+     * @brief Pure virtual function for activating service indicators
+     *        based on PEL contents.
+     *
+     * @param[in] pel - The PEL
+     */
+    virtual void activate(const PEL& pel) = 0;
+
+  protected:
+    /**
+     * @brief Reference to the DataInterface object
+     */
+    const DataInterfaceBase& _dataIface;
+};
+
+/**
+ * @class LightPath
+ *
+ * This class implements the 'LightPath' IBM policy for
+ * activating LEDs.  It has a set of rules to use to choose
+ * which callouts inside PELs should have their LEDs asserted,
+ * and then activates them by writing the Assert property on
+ * LED group D-Bus objects.
+ */
+class LightPath : public Policy
+{
+  public:
+    LightPath() = delete;
+    virtual ~LightPath() = default;
+    LightPath(const LightPath&) = default;
+    LightPath& operator=(const LightPath&) = default;
+    LightPath(LightPath&&) = default;
+    LightPath& operator=(LightPath&&) = default;
+
+    /**
+     * @brief Constructor
+     *
+     * @param[in] dataIface - The DataInterface object
+     */
+    explicit LightPath(const DataInterfaceBase& dataIface) : Policy(dataIface)
+    {
+    }
+
+    /**
+     * @brief Turns on LEDs for certain FRUs called out in the PEL.
+     *
+     * First it selectively chooses location codes listed in the FRU
+     * callout section that it wants to turn on LEDs for.  Next it
+     * looks up the inventory D-Bus paths for the FRU represented by
+     * those location codes, and then looks for associations to the
+     * LED group objects for those inventory paths.  After it has
+     * the LED group object, it sets the Asserted property on it.
+     *
+     * It only does the above for PELs that were created by the BMC
+     * or hostboot and have the Serviceable action flag set.
+     *
+     * If there are problems looking up any inventory path or LED
+     * group, then it will stop and not activate any LEDs at all.
+     *
+     * @param[in] pel - The PEL
+     */
+    void activate(const PEL& pel) override;
+
+    /**
+     * @brief Description TODO
+     */
+    std::vector<std::string> getLocationCodes(
+        const std::vector<std::unique_ptr<src::Callout>>& callouts) const;
+
+    /**
+     * @brief Description TODO
+     */
+    bool ignore(const PEL& pel) const;
+
+  private:
+    /**
+     * @brief Description TODO
+     */
+    std::vector<std::string>
+        getLEDGroupPaths(const std::vector<std::string>& locationCodes) const;
+
+    /**
+     * @brief Description TODO
+     */
+    void assertLEDs(const std::vector<std::string>& ledGroups) const;
+};
+
+/**
+ * @brief Returns the object for the service indicator policy
+ *        implemented on the system.
+ *
+ * @param[in] dataIface - The DataInterface object
+ *
+ * @return std::unique_ptr<Policy> - The policy object
+ *
+ */
+std::unique_ptr<Policy> getPolicy(const DataInterfaceBase& dataIface);
+
+} // namespace openpower::pels::service_indicators