Update to PCIeDevices 1.4 and add PCIeFunctionCollection support

v1.4 of PCIe Devices changed from an array of Links to PCIeFunctions
to a PCIeFunctionCollection.  This change adds support for the
PCIeFunctionCollection and references it from the PCIeDevices.

Tested:
Passed the Redfish Service Validator.

Change-Id: I76f0265c588b52bd02a35bf669ae6edacfb6c2a4
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 5895ee1..bf0f51f 100644
--- a/redfish-core/include/redfish.hpp
+++ b/redfish-core/include/redfish.hpp
@@ -139,6 +139,7 @@
         nodes.emplace_back(
             std::make_unique<TrustStoreCertificateCollection>(app));
         nodes.emplace_back(std::make_unique<TrustStoreCertificate>(app));
+        nodes.emplace_back(std::make_unique<SystemPCIeFunctionCollection>(app));
         nodes.emplace_back(std::make_unique<SystemPCIeFunction>(app));
         nodes.emplace_back(std::make_unique<SystemPCIeDevice>(app));
 
diff --git a/redfish-core/lib/pcie.hpp b/redfish-core/lib/pcie.hpp
index 5b4f5c5..64ccdb2 100644
--- a/redfish-core/lib/pcie.hpp
+++ b/redfish-core/lib/pcie.hpp
@@ -123,9 +123,9 @@
                 }
 
                 asyncResp->res.jsonValue = {
-                    {"@odata.type", "#PCIeDevice.v1_2_0.PCIeDevice"},
+                    {"@odata.type", "#PCIeDevice.v1_4_0.PCIeDevice"},
                     {"@odata.context",
-                     "/redfish/v1/$metadata#PCIeDevice.v1_2_0.PCIeDevice"},
+                     "/redfish/v1/$metadata#PCIeDevice.v1_4_0.PCIeDevice"},
                     {"@odata.id",
                      "/redfish/v1/Systems/system/PCIeDevices/" + device},
                     {"Name", "PCIe Device"},
@@ -147,8 +147,86 @@
                     asyncResp->res.jsonValue["DeviceType"] = *property;
                 }
 
+                asyncResp->res.jsonValue["PCIeFunctions"] = {
+                    {"@odata.id", "/redfish/v1/Systems/system/PCIeDevices/" +
+                                      device + "/PCIeFunctions"}};
+            };
+        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 SystemPCIeFunctionCollection : public Node
+{
+  public:
+    template <typename CrowApp>
+    SystemPCIeFunctionCollection(CrowApp &app) :
+        Node(app, "/redfish/v1/Systems/system/PCIeDevices/<str>/PCIeFunctions/",
+             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:
+    /**
+     * Functions triggers appropriate requests on DBus
+     */
+    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];
+        asyncResp->res.jsonValue = {
+            {"@odata.type", "#PCIeFunctionCollection.PCIeFunctionCollection"},
+            {"@odata.context",
+             "/redfish/v1/"
+             "$metadata#PCIeFunctionCollection.PCIeFunctionCollection"},
+            {"@odata.id", "/redfish/v1/Systems/system/PCIeDevices/" + device +
+                              "/PCIeFunctions"},
+            {"Name", "PCIe Function Collection"},
+            {"Description",
+             "Collection of PCIe Functions for PCIe Device " + device}};
+
+        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: "
+                        << 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;
+                }
+
                 nlohmann::json &pcieFunctionList =
-                    asyncResp->res.jsonValue["Links"]["PCIeFunctions"];
+                    asyncResp->res.jsonValue["Members"];
                 pcieFunctionList = nlohmann::json::array();
                 static constexpr const int maxPciFunctionNum = 8;
                 for (int functionNum = 0; functionNum < maxPciFunctionNum;
@@ -157,10 +235,10 @@
                     // 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())
+                    std::string *property =
+                        sdbusplus::message::variant_ns::get_if<std::string>(
+                            &pcieDevProperties[devIDProperty]);
+                    if (property && !property->empty())
                     {
                         pcieFunctionList.push_back(
                             {{"@odata.id",
@@ -169,7 +247,7 @@
                                   std::to_string(functionNum)}});
                     }
                 }
-                asyncResp->res.jsonValue["Links"]["PCIeFunctions@odata.count"] =
+                asyncResp->res.jsonValue["PCIeFunctions@odata.count"] =
                     pcieFunctionList.size();
             };
         std::string escapedPath = std::string(pciePath) + "/" + device;