Add Redfish PCIe information

This adds the capability to get PCIe device information from
D-Bus and display it in the appropriate Redfish PCIeDevice
and PCIeFunction objects.

Tested: Passed the Redfish validator for the new PCIeDevice
and PCIeFunction objects.

Change-Id: I06f3b0e7d283e48d2235b7d34f78f603b22de79f
Signed-off-by: Jason M. Bills <jason.m.bills@linux.intel.com>
diff --git a/redfish-core/include/redfish.hpp b/redfish-core/include/redfish.hpp
index 44cc74c..21d6500 100644
--- a/redfish-core/include/redfish.hpp
+++ b/redfish-core/include/redfish.hpp
@@ -24,6 +24,7 @@
 #include "../lib/managers.hpp"
 #include "../lib/message_registries.hpp"
 #include "../lib/network_protocol.hpp"
+#include "../lib/pcie.hpp"
 #include "../lib/power.hpp"
 #include "../lib/redfish_sessions.hpp"
 #include "../lib/roles.hpp"
@@ -132,6 +133,10 @@
         nodes.emplace_back(std::make_unique<HTTPSCertificate>(app));
         nodes.emplace_back(std::make_unique<LDAPCertificateCollection>(app));
         nodes.emplace_back(std::make_unique<LDAPCertificate>(app));
+
+        nodes.emplace_back(std::make_unique<SystemPCIeFunction>(app));
+        nodes.emplace_back(std::make_unique<SystemPCIeDevice>(app));
+
         for (const auto& node : nodes)
         {
             node->initPrivileges();
diff --git a/redfish-core/lib/pcie.hpp b/redfish-core/lib/pcie.hpp
new file mode 100644
index 0000000..6b8600f
--- /dev/null
+++ b/redfish-core/lib/pcie.hpp
@@ -0,0 +1,345 @@
+/*
+// Copyright (c) 2018 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 "node.hpp"
+
+#include <boost/system/linux_error.hpp>
+
+namespace redfish
+{
+
+static constexpr char const *pcieService = "xyz.openbmc_project.PCIe";
+static constexpr char const *pciePath = "/xyz/openbmc_project/PCIe";
+static constexpr char const *pcieDeviceInterface =
+    "xyz.openbmc_project.PCIe.Device";
+
+static inline void getPCIeDeviceList(std::shared_ptr<AsyncResp> asyncResp)
+{
+    auto getPCIeMapCallback =
+        [asyncResp](const boost::system::error_code ec,
+                    std::vector<std::string> &pcieDevicePaths) {
+            if (ec)
+            {
+                BMCWEB_LOG_DEBUG << "failed to get PCIe device paths ec: "
+                                 << ec.message();
+                messages::internalError(asyncResp->res);
+                return;
+            }
+            nlohmann::json &pcieDeviceList =
+                asyncResp->res.jsonValue["PCIeDevices"];
+            pcieDeviceList = nlohmann::json::array();
+            for (const std::string &pcieDevicePath : pcieDevicePaths)
+            {
+                size_t devStart = pcieDevicePath.rfind("/");
+                if (devStart == std::string::npos)
+                {
+                    continue;
+                }
+
+                std::string devName = pcieDevicePath.substr(devStart + 1);
+                if (devName.empty())
+                {
+                    continue;
+                }
+                pcieDeviceList.push_back(
+                    {{"@odata.id",
+                      "/redfish/v1/Systems/system/PCIeDevices/" + devName}});
+            }
+            asyncResp->res.jsonValue["PCIeDevices@odata.count"] =
+                pcieDeviceList.size();
+        };
+    crow::connections::systemBus->async_method_call(
+        std::move(getPCIeMapCallback), "xyz.openbmc_project.ObjectMapper",
+        "/xyz/openbmc_project/object_mapper",
+        "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths",
+        std::string(pciePath) + "/", 1, std::array<std::string, 0>());
+}
+
+class SystemPCIeDevice : public Node
+{
+  public:
+    SystemPCIeDevice(CrowApp &app) :
+        Node(app, "/redfish/v1/Systems/system/PCIeDevices/<str>/",
+             std::string())
+    {
+        entityPrivileges = {
+            {boost::beast::http::verb::get, {{"Login"}}},
+            {boost::beast::http::verb::head, {{"Login"}}},
+            {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
+            {boost::beast::http::verb::put, {{"ConfigureManager"}}},
+            {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
+            {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
+    }
+
+  private:
+    void doGet(crow::Response &res, const crow::Request &req,
+               const std::vector<std::string> &params) override
+    {
+        std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
+        if (params.size() != 1)
+        {
+            messages::internalError(asyncResp->res);
+            return;
+        }
+        const std::string &device = params[0];
+
+        auto getPCIeDeviceCallback =
+            [asyncResp,
+             device](const boost::system::error_code ec,
+                     boost::container::flat_map<
+                         std::string, sdbusplus::message::variant<std::string>>
+                         &pcieDevProperties) {
+                if (ec)
+                {
+                    BMCWEB_LOG_DEBUG
+                        << "failed to get PCIe Device properties ec: "
+                        << static_cast<int>(ec.value()) << ": " << ec.message();
+                    if (ec.value() ==
+                        boost::system::linux_error::bad_request_descriptor)
+                    {
+                        messages::resourceNotFound(asyncResp->res, "PCIeDevice",
+                                                   device);
+                    }
+                    else
+                    {
+                        messages::internalError(asyncResp->res);
+                    }
+                    return;
+                }
+
+                asyncResp->res.jsonValue = {
+                    {"@odata.type", "#PCIeDevice.v1_2_0.PCIeDevice"},
+                    {"@odata.context",
+                     "/redfish/v1/$metadata#PCIeDevice.v1_2_0.PCIeDevice"},
+                    {"@odata.id",
+                     "/redfish/v1/Systems/system/PCIeDevices/" + device},
+                    {"Name", "PCIe Device"},
+                    {"Id", device}};
+
+                if (std::string *property =
+                        sdbusplus::message::variant_ns::get_if<std::string>(
+                            &pcieDevProperties["Manufacturer"]);
+                    property)
+                {
+                    asyncResp->res.jsonValue["Manufacturer"] = *property;
+                }
+
+                if (std::string *property =
+                        sdbusplus::message::variant_ns::get_if<std::string>(
+                            &pcieDevProperties["DeviceType"]);
+                    property)
+                {
+                    asyncResp->res.jsonValue["DeviceType"] = *property;
+                }
+
+                nlohmann::json &pcieFunctionList =
+                    asyncResp->res.jsonValue["Links"]["PCIeFunctions"];
+                pcieFunctionList = nlohmann::json::array();
+                static constexpr const int maxPciFunctionNum = 8;
+                for (int functionNum = 0; functionNum < maxPciFunctionNum;
+                     functionNum++)
+                {
+                    // Check if this function exists by looking for a device ID
+                    std::string devIDProperty =
+                        "Function" + std::to_string(functionNum) + "DeviceId";
+                    if (std::string *property =
+                            sdbusplus::message::variant_ns::get_if<std::string>(
+                                &pcieDevProperties[devIDProperty]);
+                        property && !property->empty())
+                    {
+                        pcieFunctionList.push_back(
+                            {{"@odata.id",
+                              "/redfish/v1/Systems/system/PCIeDevices/" +
+                                  device + "/PCIeFunctions/" +
+                                  std::to_string(functionNum)}});
+                    }
+                }
+                asyncResp->res.jsonValue["Links"]["PCIeFunctions@odata.count"] =
+                    pcieFunctionList.size();
+            };
+        std::string escapedPath = std::string(pciePath) + "/" + device;
+        dbus::utility::escapePathForDbus(escapedPath);
+        crow::connections::systemBus->async_method_call(
+            std::move(getPCIeDeviceCallback), pcieService, escapedPath,
+            "org.freedesktop.DBus.Properties", "GetAll", pcieDeviceInterface);
+    }
+};
+
+class SystemPCIeFunction : public Node
+{
+  public:
+    SystemPCIeFunction(CrowApp &app) :
+        Node(
+            app,
+            "/redfish/v1/Systems/system/PCIeDevices/<str>/PCIeFunctions/<str>/",
+            std::string(), std::string())
+    {
+        entityPrivileges = {
+            {boost::beast::http::verb::get, {{"Login"}}},
+            {boost::beast::http::verb::head, {{"Login"}}},
+            {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
+            {boost::beast::http::verb::put, {{"ConfigureManager"}}},
+            {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
+            {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
+    }
+
+  private:
+    void doGet(crow::Response &res, const crow::Request &req,
+               const std::vector<std::string> &params) override
+    {
+        std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
+        if (params.size() != 2)
+        {
+            messages::internalError(asyncResp->res);
+            return;
+        }
+        const std::string &device = params[0];
+        const std::string &function = params[1];
+
+        auto getPCIeDeviceCallback =
+            [asyncResp, device, function](
+                const boost::system::error_code ec,
+                boost::container::flat_map<
+                    std::string, sdbusplus::message::variant<std::string>>
+                    &pcieDevProperties) {
+                if (ec)
+                {
+                    BMCWEB_LOG_DEBUG
+                        << "failed to get PCIe Device properties ec: "
+                        << static_cast<int>(ec.value()) << ": " << ec.message();
+                    if (ec.value() ==
+                        boost::system::linux_error::bad_request_descriptor)
+                    {
+                        messages::resourceNotFound(asyncResp->res, "PCIeDevice",
+                                                   device);
+                    }
+                    else
+                    {
+                        messages::internalError(asyncResp->res);
+                    }
+                    return;
+                }
+
+                // Check if this function exists by looking for a device ID
+                std::string devIDProperty = "Function" + function + "DeviceId";
+                if (std::string *property =
+                        sdbusplus::message::variant_ns::get_if<std::string>(
+                            &pcieDevProperties[devIDProperty]);
+                    property && property->empty())
+                {
+                    messages::resourceNotFound(asyncResp->res, "PCIeFunction",
+                                               function);
+                    return;
+                }
+
+                asyncResp->res.jsonValue = {
+                    {"@odata.type", "#PCIeFunction.v1_2_0.PCIeFunction"},
+                    {"@odata.context",
+                     "/redfish/v1/$metadata#PCIeFunction.PCIeFunction"},
+                    {"@odata.id", "/redfish/v1/Systems/system/PCIeDevices/" +
+                                      device + "/PCIeFunctions/" + function},
+                    {"Name", "PCIe Function"},
+                    {"Id", function},
+                    {"FunctionId", std::stoi(function)},
+                    {"Links",
+                     {{"PCIeDevice",
+                       {{"@odata.id",
+                         "/redfish/v1/Systems/system/PCIeDevices/" +
+                             device}}}}}};
+
+                if (std::string *property =
+                        sdbusplus::message::variant_ns::get_if<std::string>(
+                            &pcieDevProperties["Function" + function +
+                                               "DeviceId"]);
+                    property)
+                {
+                    asyncResp->res.jsonValue["DeviceId"] = *property;
+                }
+
+                if (std::string *property =
+                        sdbusplus::message::variant_ns::get_if<std::string>(
+                            &pcieDevProperties["Function" + function +
+                                               "VendorId"]);
+                    property)
+                {
+                    asyncResp->res.jsonValue["VendorId"] = *property;
+                }
+
+                if (std::string *property =
+                        sdbusplus::message::variant_ns::get_if<std::string>(
+                            &pcieDevProperties["Function" + function +
+                                               "FunctionType"]);
+                    property)
+                {
+                    asyncResp->res.jsonValue["FunctionType"] = *property;
+                }
+
+                if (std::string *property =
+                        sdbusplus::message::variant_ns::get_if<std::string>(
+                            &pcieDevProperties["Function" + function +
+                                               "DeviceClass"]);
+                    property)
+                {
+                    asyncResp->res.jsonValue["DeviceClass"] = *property;
+                }
+
+                if (std::string *property =
+                        sdbusplus::message::variant_ns::get_if<std::string>(
+                            &pcieDevProperties["Function" + function +
+                                               "ClassCode"]);
+                    property)
+                {
+                    asyncResp->res.jsonValue["ClassCode"] = *property;
+                }
+
+                if (std::string *property =
+                        sdbusplus::message::variant_ns::get_if<std::string>(
+                            &pcieDevProperties["Function" + function +
+                                               "RevisionId"]);
+                    property)
+                {
+                    asyncResp->res.jsonValue["RevisionId"] = *property;
+                }
+
+                if (std::string *property =
+                        sdbusplus::message::variant_ns::get_if<std::string>(
+                            &pcieDevProperties["Function" + function +
+                                               "SubsystemId"]);
+                    property)
+                {
+                    asyncResp->res.jsonValue["SubsystemId"] = *property;
+                }
+
+                if (std::string *property =
+                        sdbusplus::message::variant_ns::get_if<std::string>(
+                            &pcieDevProperties["Function" + function +
+                                               "SubsystemVendorId"]);
+                    property)
+                {
+                    asyncResp->res.jsonValue["SubsystemVendorId"] = *property;
+                }
+            };
+        std::string escapedPath = std::string(pciePath) + "/" + device;
+        dbus::utility::escapePathForDbus(escapedPath);
+        crow::connections::systemBus->async_method_call(
+            std::move(getPCIeDeviceCallback), pcieService, escapedPath,
+            "org.freedesktop.DBus.Properties", "GetAll", pcieDeviceInterface);
+    }
+};
+
+} // namespace redfish
diff --git a/redfish-core/lib/systems.hpp b/redfish-core/lib/systems.hpp
index e5f47a1..1a94139 100644
--- a/redfish-core/lib/systems.hpp
+++ b/redfish-core/lib/systems.hpp
@@ -16,6 +16,7 @@
 #pragma once
 
 #include "health.hpp"
+#include "pcie.hpp"
 #include "redfish_util.hpp"
 
 #include <boost/container/flat_map.hpp>
@@ -1298,6 +1299,7 @@
         getComputerSystem(asyncResp);
         getHostState(asyncResp);
         getBootProperties(asyncResp);
+        getPCIeDeviceList(asyncResp);
     }
 
     void doPatch(crow::Response &res, const crow::Request &req,