Add feature Cold Redundancy

Add an Intel specific feature PSU Cold Redundancy. This is the first patch
which will get PSU information and PSU Event from D-Bus interfaces.
Cold Redundancy design document is in
https://gerrit.openbmc-project.xyz/c/openbmc/docs/+/27637

Signed-off-by: Cheng C Yang <cheng.c.yang@linux.intel.com>
Change-Id: Ic039118e4cebc8b0ff6ba80493180a1d8af0096b
diff --git a/cold-redundancy/cold_redundancy.cpp b/cold-redundancy/cold_redundancy.cpp
new file mode 100644
index 0000000..891c242
--- /dev/null
+++ b/cold-redundancy/cold_redundancy.cpp
@@ -0,0 +1,278 @@
+/*
+// Copyright (c) 2019 Intel 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 "types.hpp"
+
+#include <array>
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/asio/post.hpp>
+#include <boost/container/flat_set.hpp>
+#include <cold_redundancy.hpp>
+#include <filesystem>
+#include <fstream>
+#include <iostream>
+#include <phosphor-logging/elog-errors.hpp>
+#include <regex>
+#include <sdbusplus/asio/connection.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+#include <sdbusplus/asio/sd_event.hpp>
+#include <sdbusplus/bus/match.hpp>
+
+namespace
+{
+constexpr const std::array<const char*, 1> psuInterfaceTypes = {
+    "xyz.openbmc_project.Configuration.pmbus"};
+std::string inventoryPath = std::string(INVENTORY_OBJ_PATH) + "/system";
+const constexpr char* eventPath = "/xyz/openbmc_project/State/Decorator";
+std::vector<std::unique_ptr<PowerSupply>> powerSupplies;
+} // namespace
+
+ColdRedundancy::ColdRedundancy(
+    boost::asio::io_service& io, sdbusplus::asio::object_server& objectServer,
+    std::shared_ptr<sdbusplus::asio::connection>& systemBus) :
+    filterTimer(io),
+    systemBus(systemBus)
+{
+    post(io,
+         [this, &io, &objectServer, &systemBus]() { createPSU(systemBus); });
+    std::function<void(sdbusplus::message::message&)> eventHandler =
+        [this, &io, &objectServer,
+         &systemBus](sdbusplus::message::message& message) {
+            if (message.is_method_error())
+            {
+                std::cerr << "callback method error\n";
+                return;
+            }
+            filterTimer.expires_after(std::chrono::seconds(1));
+            filterTimer.async_wait([this, &io, &objectServer, &systemBus](
+                                       const boost::system::error_code& ec) {
+                if (ec == boost::asio::error::operation_aborted)
+                {
+                    return;
+                }
+                else if (ec)
+                {
+                    std::cerr << "timer error\n";
+                }
+                createPSU(systemBus);
+            });
+        };
+
+    std::function<void(sdbusplus::message::message&)> eventCollect =
+        [&](sdbusplus::message::message& message) {
+            std::string objectName;
+            boost::container::flat_map<std::string, std::variant<bool>> values;
+            std::string path = message.get_path();
+            std::size_t slantingPos = path.find_last_of("/\\");
+            if ((slantingPos == std::string::npos) ||
+                ((slantingPos + 1) >= path.size()))
+            {
+                std::cerr << "Unable to get PSU state name from path\n";
+                return;
+            }
+            std::string statePSUName = path.substr(slantingPos + 1);
+
+            std::size_t hypenPos = statePSUName.find("_");
+            if (hypenPos == std::string::npos)
+            {
+                std::cerr << "Unable to get PSU name from PSU path\n";
+                return;
+            }
+            std::string psuName = statePSUName.substr(0, hypenPos);
+
+            try
+            {
+                message.read(objectName, values);
+            }
+            catch (const sdbusplus::exception::exception& e)
+            {
+                std::cerr << "Failed to read message from PSU Event\n";
+                return;
+            }
+
+            for (auto& psu : powerSupplies)
+            {
+                if (psu->name != psuName)
+                {
+                    continue;
+                }
+
+                std::string psuEventName = "functional";
+                auto findEvent = values.find(psuEventName);
+                if (findEvent != values.end())
+                {
+                    bool* functional = std::get_if<bool>(&(findEvent->second));
+                    if (functional == nullptr)
+                    {
+                        std::cerr << "Unable to get valid functional status\n";
+                        continue;
+                    }
+                    if (*functional)
+                    {
+                        psu->state = CR::PSUState::normal;
+                    }
+                    else
+                    {
+                        psu->state = CR::PSUState::acLost;
+                    }
+                }
+            }
+        };
+
+    using namespace sdbusplus::bus::match::rules;
+    for (const char* type : psuInterfaceTypes)
+    {
+        auto match = std::make_unique<sdbusplus::bus::match::match>(
+            static_cast<sdbusplus::bus::bus&>(*systemBus),
+            type::signal() + member("PropertiesChanged") +
+                path_namespace(inventoryPath) + arg0namespace(type),
+            eventHandler);
+        matches.emplace_back(std::move(match));
+    }
+
+    for (const char* eventType : psuEventInterface)
+    {
+        auto eventMatch = std::make_unique<sdbusplus::bus::match::match>(
+            static_cast<sdbusplus::bus::bus&>(*systemBus),
+            type::signal() + member("PropertiesChanged") +
+                path_namespace(eventPath) + arg0namespace(eventType),
+            eventCollect);
+        matches.emplace_back(std::move(eventMatch));
+    }
+}
+
+static const constexpr int psuDepth = 3;
+// Check PSU information from entity-manager D-Bus interface and use the bus
+// address to create PSU Class for cold redundancy.
+void ColdRedundancy::createPSU(
+    std::shared_ptr<sdbusplus::asio::connection>& conn)
+{
+    numberOfPSU = 0;
+    powerSupplies.clear();
+
+    // call mapper to get matched obj paths
+    conn->async_method_call(
+        [this, &conn](const boost::system::error_code ec,
+                      CR::GetSubTreeType subtree) {
+            if (ec)
+            {
+                std::cerr << "Exception happened when communicating to "
+                             "ObjectMapper\n";
+                return;
+            }
+            for (const auto& object : subtree)
+            {
+                std::string pathName = object.first;
+                for (const auto& serviceIface : object.second)
+                {
+                    std::string serviceName = serviceIface.first;
+                    for (const auto& interface : serviceIface.second)
+                    {
+                        // only get property of matched interface
+                        bool isIfaceMatched = false;
+                        for (const auto& type : psuInterfaceTypes)
+                        {
+                            if (type == interface)
+                            {
+                                isIfaceMatched = true;
+                                break;
+                            }
+                        }
+                        if (!isIfaceMatched)
+                            continue;
+
+                        conn->async_method_call(
+                            [this, &conn,
+                             interface](const boost::system::error_code ec,
+                                        CR::PropertyMapType propMap) {
+                                if (ec)
+                                {
+                                    std::cerr
+                                        << "Exception happened when get all "
+                                           "properties\n";
+                                    return;
+                                }
+
+                                auto configName =
+                                    std::get_if<std::string>(&propMap["Name"]);
+                                if (configName == nullptr)
+                                {
+                                    std::cerr << "error finding necessary "
+                                                 "entry in configuration\n";
+                                    return;
+                                }
+
+                                auto configBus =
+                                    std::get_if<uint64_t>(&propMap["Bus"]);
+                                auto configAddress =
+                                    std::get_if<uint64_t>(&propMap["Address"]);
+
+                                if (configBus == nullptr ||
+                                    configAddress == nullptr)
+                                {
+                                    std::cerr << "error finding necessary "
+                                                 "entry in configuration\n";
+                                    return;
+                                }
+                                for (auto& psu : powerSupplies)
+                                {
+                                    if ((static_cast<uint8_t>(*configBus) ==
+                                         psu->bus) &&
+                                        (static_cast<uint8_t>(*configAddress) ==
+                                         psu->address))
+                                    {
+                                        return;
+                                    }
+                                }
+
+                                uint8_t order = 0;
+
+                                powerSupplies.emplace_back(
+                                    std::make_unique<PowerSupply>(
+                                        *configName,
+                                        static_cast<uint8_t>(*configBus),
+                                        static_cast<uint8_t>(*configAddress),
+                                        order, conn));
+
+                                numberOfPSU++;
+                                std::vector<uint8_t> orders = {};
+                                for (auto& psu : powerSupplies)
+                                {
+                                    orders.push_back(psu->order);
+                                }
+                            },
+                            serviceName.c_str(), pathName.c_str(),
+                            "org.freedesktop.DBus.Properties", "GetAll",
+                            interface);
+                    }
+                }
+            }
+        },
+        "xyz.openbmc_project.ObjectMapper",
+        "/xyz/openbmc_project/object_mapper",
+        "xyz.openbmc_project.ObjectMapper", "GetSubTree",
+        "/xyz/openbmc_project/inventory/system", psuDepth, psuInterfaceTypes);
+}
+
+PowerSupply::PowerSupply(
+    std::string& name, uint8_t bus, uint8_t address, uint8_t order,
+    const std::shared_ptr<sdbusplus::asio::connection>& dbusConnection) :
+    name(name),
+    bus(bus), address(address), order(order)
+{
+    CR::getPSUEvent(dbusConnection, name, state);
+}