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);
+}
diff --git a/cold-redundancy/cold_redundancy.hpp b/cold-redundancy/cold_redundancy.hpp
new file mode 100644
index 0000000..cfceff2
--- /dev/null
+++ b/cold-redundancy/cold_redundancy.hpp
@@ -0,0 +1,138 @@
+/*
+// 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 <sdbusplus/asio/object_server.hpp>
+#include <util.hpp>
+
+/**
+ * @class ColdRedundancy
+ *
+ */
+class ColdRedundancy
+{
+  public:
+    /**
+     * Constructor
+     *
+     * @param[in] io - boost asio context
+     * @param[in] objectServer - D-Bus object
+     * @param[in] dbusConnection - D-Bus connection
+     */
+    ColdRedundancy(
+        boost::asio::io_service& io,
+        sdbusplus::asio::object_server& objectServer,
+        std::shared_ptr<sdbusplus::asio::connection>& dbusConnection);
+
+    /**
+     * Checking PSU information, adding matches, starting rotation
+     * and creating PSU objects
+     *
+     * @param[in] dbusConnection - D-Bus connection
+     */
+    void
+        createPSU(std::shared_ptr<sdbusplus::asio::connection>& dbusConnection);
+
+  private:
+    /**
+     * @brief Indicates the count of PSUs
+     *
+     * @details Indicates how many PSUs are there on the system.
+     */
+    uint8_t numberOfPSU = 0;
+
+    /**
+     * @brief Indicates the delay timer
+     *
+     * @details Each time this daemon start, need a delay to avoid
+     *          different PSUs calling createPSU function for
+     *          several times at same time
+     */
+    boost::asio::steady_timer filterTimer;
+
+    /**
+     * @brief Indicates the dbus connction
+     */
+    std::shared_ptr<sdbusplus::asio::connection>& systemBus;
+
+    /**
+     * @brief Indicates the D-Bus matches
+     *
+     * @details This matches contain all matches in this daemon such
+     *          as PSU event match, PSU information match. The target
+     *          D-Bus properties change will trigger callback function
+     *          by these matches
+     */
+    std::vector<std::unique_ptr<sdbusplus::bus::match::match>> matches;
+};
+
+/**
+ * @class PowerSupply
+ * Represents a power supply device.
+ */
+class PowerSupply
+{
+  public:
+    /**
+     * Constructor
+     *
+     * @param[in] name - the device name
+     * @param[in] bus - smbus number
+     * @param[in] address - device address on smbus
+     * @param[in] order - ranking order of redundancy
+     * @param[in] dbusConnection - D-Bus connection
+     */
+    PowerSupply(
+        std::string& name, uint8_t bus, uint8_t address, uint8_t order,
+        const std::shared_ptr<sdbusplus::asio::connection>& dbusConnection);
+    ~PowerSupply() = default;
+
+    /**
+     * @brief Indicates the name of the device
+     *
+     * @details The PSU name such as PSU1
+     */
+    std::string name;
+
+    /**
+     * @brief Indicates the smbus number
+     *
+     * @details The smbus number on the system
+     */
+    uint8_t bus;
+
+    /**
+     * @brief Indicates the smbus address of the device
+     *
+     * @details The 7-bit smbus address of the PSU on smbus
+     */
+    uint8_t address;
+
+    /**
+     * @brief Indicates the ranking order
+     *
+     * @details The order indicates the sequence entering standby mode.
+     *          the PSU with lower order will enter standby mode first.
+     */
+    uint8_t order = 0;
+
+    /**
+     * @brief Indicates the status of the PSU
+     *
+     * @details If the PSU has no any problem, the status of it will be
+     *          normal otherwise acLost.
+     */
+    CR::PSUState state = CR::PSUState::normal;
+};
diff --git a/cold-redundancy/meson.build b/cold-redundancy/meson.build
new file mode 100644
index 0000000..1558de6
--- /dev/null
+++ b/cold-redundancy/meson.build
@@ -0,0 +1,18 @@
+cold_redundancy = executable(
+    'cold-redundancy',
+    'redundancy_main.cpp',
+    'cold_redundancy.cpp',
+    'util.cpp',
+    dependencies: [
+        phosphor_dbus_interfaces,
+        phosphor_logging,
+        sdbusplus,
+        systemd,
+        pthread,
+    ],
+    include_directories: [
+        '.',
+        '..',
+    ],
+    install: false,
+)
diff --git a/cold-redundancy/redundancy_main.cpp b/cold-redundancy/redundancy_main.cpp
new file mode 100644
index 0000000..d8572e5
--- /dev/null
+++ b/cold-redundancy/redundancy_main.cpp
@@ -0,0 +1,33 @@
+/*
+// 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 <cold_redundancy.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+
+int main(void)
+{
+    boost::asio::io_service io;
+    auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
+
+    systemBus->request_name("xyz.openbmc_project.PSURedundancy");
+    sdbusplus::asio::object_server objectServer(systemBus);
+
+    ColdRedundancy coldRedundancy(io, objectServer, systemBus);
+
+    io.run();
+
+    return 0;
+}
diff --git a/cold-redundancy/util.cpp b/cold-redundancy/util.cpp
new file mode 100644
index 0000000..fea972e
--- /dev/null
+++ b/cold-redundancy/util.cpp
@@ -0,0 +1,46 @@
+/*
+// 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 "util.hpp"
+
+#include "utility.hpp"
+
+#include <boost/algorithm/string/predicate.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+
+namespace CR
+{
+
+void getPSUEvent(const std::shared_ptr<sdbusplus::asio::connection>& conn,
+                 const std::string& psuName, PSUState& state)
+{
+    state = PSUState::normal;
+    bool result = true;
+    // /State/Decorator/PSUx_OperationalStatus
+    std::string pathStr = psuEventPath + psuName + "_OperationalStatus";
+
+    phosphor::power::util::getProperty<bool>(
+        "xyz.openbmc_project.State.Decorator.OperationalStatus", "functional",
+        pathStr, "xyz.openbmc_project.PSUSensor",
+        static_cast<sdbusplus::bus::bus&>(*conn), result);
+
+    if (!result)
+    {
+        state = PSUState::acLost;
+    }
+}
+
+} // namespace CR
diff --git a/cold-redundancy/util.hpp b/cold-redundancy/util.hpp
new file mode 100644
index 0000000..8ad4840
--- /dev/null
+++ b/cold-redundancy/util.hpp
@@ -0,0 +1,59 @@
+/*
+// 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.
+*/
+
+#pragma once
+#include <boost/container/flat_map.hpp>
+#include <chrono>
+#include <cstdint>
+#include <iostream>
+#include <sdbusplus/asio/connection.hpp>
+
+namespace CR
+{
+
+using BasicVariantType =
+    std::variant<std::vector<std::string>, std::vector<uint64_t>,
+                 std::vector<uint8_t>, std::string, int64_t, uint64_t, double,
+                 int32_t, uint32_t, int16_t, uint16_t, uint8_t, bool>;
+
+using PropertyMapType =
+    boost::container::flat_map<std::string, BasicVariantType>;
+
+using GetSubTreeType = std::vector<
+    std::pair<std::string,
+              std::vector<std::pair<std::string, std::vector<std::string>>>>>;
+
+constexpr std::chrono::microseconds dbusTimeout(5000);
+static std::string psuEventPath = "/xyz/openbmc_project/State/Decorator/";
+
+enum class PSUState
+{
+    normal,
+    acLost
+};
+
+/**
+ * Getting PSU Event from PSU D-Bus interfaces
+ *
+ * @param[in] dbusConnection - D-Bus connection
+ * @param[in] psuName - PSU name such as "PSU1"
+ * @param[out] state - PSU state, normal or acLost
+ */
+void getPSUEvent(
+    const std::shared_ptr<sdbusplus::asio::connection>& dbusConnection,
+    const std::string& psuName, PSUState& state);
+
+} // namespace CR
diff --git a/meson.build b/meson.build
index fdccf3c..210ae7f 100644
--- a/meson.build
+++ b/meson.build
@@ -22,6 +22,7 @@
 sdbusplus = dependency('sdbusplus')
 sdbuspp = find_program('sdbus++')
 sdeventplus = dependency('sdeventplus')
+pthread = dependency('threads')
 
 systemd = dependency('systemd')
 servicedir = systemd.get_pkgconfig_variable('systemdsystemunitdir')
@@ -87,3 +88,4 @@
 subdir('power-supply')
 subdir('tools/power-utils')
 subdir('test')
+subdir('cold-redundancy')
diff --git a/types.hpp b/types.hpp
index d334a25..814c098 100644
--- a/types.hpp
+++ b/types.hpp
@@ -1,5 +1,5 @@
 #pragma once
-
+#include <array>
 /* const expressions shared in this repository */
 
 constexpr auto ASSOCIATION_IFACE = "xyz.openbmc_project.Association";
@@ -22,3 +22,6 @@
 constexpr auto POWER_OBJ_PATH = "/org/openbmc/control/power0";
 
 constexpr auto INPUT_HISTORY = "input_history";
+
+constexpr std::array<const char*, 1> psuEventInterface = {
+    "xyz.openbmc_project.State.Decorator.OperationalStatus"};