Implement Redfish PCIeSlots schema

PCIeSlotCollection, and PCIeSlot schemas are used for determining
and inspecting the PCIe physical topology of a system.  It is used to
determine what a particular physical slots formfactor is.

This commit supports the as documented in Redfish.md.

https://redfish.dmtf.org/schemas/PCIeSlots_v1.xml

Tested: Validator passes (on previous patchset)
1、Get PCIe slots
curl -k -H "X-Auth-Token: $token" -X GET
https://${bmc}/redfish/v1/Chassis/chassis/PCIeSlots
{
  "@odata.id": "/redfish/v1/Chassis/chassis/PCIeSlots",
  "@odata.type": "#PCIeSlots.v1_4_1.PCIeSlots",
  "Id": "PCIeSlots",
  "Name": "PCIe Slot Information",
  "Slots": [
    {
      "HotPluggable": false,
      "Lanes": 16,
      "PCIeType": "Gen1",
      "SlotType": "FullLength"
    },
    {
      "HotPluggable": false,
      "Lanes": 16,
      "PCIeType": "Gen2",
      "SlotType": "OEM"
    }
  ]
}

2、No PCIeSlots
curl -k -H "X-Auth-Token: $token" -X GET
https://${bmc}/redfish/v1/Chassis/chassis/PCIeSlots
{
  "@odata.id": "/redfish/v1/Chassis/chassis/PCIeSlots",
  "@odata.type": "#PCIeSlots.v1_4_1.PCIeSlots",
  "Id": "PCIeSlots",
  "Name": "PCIe Slot Information",
  "Slots": []
}

3、Bad chassis ID return 404
curl -k -H "X-Auth-Token: $token" -X GET
https://${bmc}/redfish/v1/Chassis/badChassisID/PCIeSlots
Returns 404 and ResourceNotFound

Signed-off-by: Chicago Duan <duanzhijia01@inspur.com>
Signed-off-by: Ed Tanous <edtanous@google.com>
Change-Id: I11e1bf94b3865986cbd580293ea906fe96067912
diff --git a/Redfish.md b/Redfish.md
index b7d7f4c..ab4f7db 100644
--- a/Redfish.md
+++ b/Redfish.md
@@ -274,6 +274,15 @@
 - MinNumNeeded
 - MaxNumSupported
 
+#### /redfish/v1/Chassis/{ChassisId}/PCIeSlots/
+- Members
+
+#### /redfish/v1/Chassis/{ChassisId}/PCIeSlots/{SlotName}
+- HotPluggable
+- Lanes
+- PCIeType
+- SlotType
+
 #### /redfish/v1/EventService/
 ##### EventService
 - Actions
diff --git a/redfish-core/include/redfish.hpp b/redfish-core/include/redfish.hpp
index b7dd3b3..55c00f6 100644
--- a/redfish-core/include/redfish.hpp
+++ b/redfish-core/include/redfish.hpp
@@ -31,6 +31,7 @@
 #include "../lib/metric_report_definition.hpp"
 #include "../lib/network_protocol.hpp"
 #include "../lib/pcie.hpp"
+#include "../lib/pcie_slots.hpp"
 #include "../lib/power.hpp"
 #include "../lib/processor.hpp"
 #include "../lib/redfish_sessions.hpp"
@@ -80,6 +81,7 @@
         requestRoutesManagerResetAction(app);
         requestRoutesManagerResetActionInfo(app);
         requestRoutesManagerResetToDefaultsAction(app);
+        requestRoutesPCIeSlots(app);
         requestRoutesChassisCollection(app);
         requestRoutesChassis(app);
         requestRoutesChassisResetAction(app);
diff --git a/redfish-core/lib/chassis.hpp b/redfish-core/lib/chassis.hpp
index 724d5f3..11c04b4 100644
--- a/redfish-core/lib/chassis.hpp
+++ b/redfish-core/lib/chassis.hpp
@@ -397,6 +397,10 @@
                         "/redfish/v1/Chassis/" + chassisId + "/Sensors";
                     asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
 
+                    asyncResp->res.jsonValue["PCIeSlots"]["@odata.id"] =
+                        crow::utility::urlFromPieces("redfish", "v1", "Chassis",
+                                                     chassisId, "PCIeSlots");
+
                     nlohmann::json::array_t computerSystems;
                     nlohmann::json::object_t system;
                     system["@odata.id"] = "/redfish/v1/Systems/system";
diff --git a/redfish-core/lib/pcie.hpp b/redfish-core/lib/pcie.hpp
index b4d6433..8437f50 100644
--- a/redfish-core/lib/pcie.hpp
+++ b/redfish-core/lib/pcie.hpp
@@ -129,9 +129,8 @@
     {
         return "Gen5";
     }
-    if (generationInUse.empty() ||
-        generationInUse ==
-            "xyz.openbmc_project.Inventory.Item.PCIeSlot.Generations.Unknown")
+    if (generationInUse ==
+        "xyz.openbmc_project.Inventory.Item.PCIeSlot.Generations.Unknown")
     {
         return "";
     }
diff --git a/redfish-core/lib/pcie_slots.hpp b/redfish-core/lib/pcie_slots.hpp
new file mode 100644
index 0000000..b340df2
--- /dev/null
+++ b/redfish-core/lib/pcie_slots.hpp
@@ -0,0 +1,290 @@
+#pragma once
+
+#include "error_messages.hpp"
+#include "utility.hpp"
+
+#include <app.hpp>
+#include <pcie.hpp>
+#include <registries/privilege_registry.hpp>
+#include <utils/json_utils.hpp>
+
+namespace redfish
+{
+
+inline std::string dbusSlotTypeToRf(const std::string& slotType)
+{
+    if (slotType ==
+        "xyz.openbmc_project.Inventory.Item.PCIeSlot.SlotTypes.FullLength")
+    {
+        return "FullLength";
+    }
+    if (slotType ==
+        "xyz.openbmc_project.Inventory.Item.PCIeSlot.SlotTypes.HalfLength")
+    {
+        return "HalfLength";
+    }
+    if (slotType ==
+        "xyz.openbmc_project.Inventory.Item.PCIeSlot.SlotTypes.LowProfile")
+    {
+        return "LowProfile";
+    }
+    if (slotType ==
+        "xyz.openbmc_project.Inventory.Item.PCIeSlot.SlotTypes.Mini")
+    {
+        return "Mini";
+    }
+    if (slotType == "xyz.openbmc_project.Inventory.Item.PCIeSlot.SlotTypes.M_2")
+    {
+        return "M2";
+    }
+    if (slotType == "xyz.openbmc_project.Inventory.Item.PCIeSlot.SlotTypes.OEM")
+    {
+        return "OEM";
+    }
+    if (slotType ==
+        "xyz.openbmc_project.Inventory.Item.PCIeSlot.SlotTypes.OCP3Small")
+    {
+        return "OCP3Small";
+    }
+    if (slotType ==
+        "xyz.openbmc_project.Inventory.Item.PCIeSlot.SlotTypes.OCP3Large")
+    {
+        return "OCP3Large";
+    }
+    if (slotType == "xyz.openbmc_project.Inventory.Item.PCIeSlot.SlotTypes.U_2")
+    {
+        return "U2";
+    }
+
+    // Unknown or others
+    return "";
+}
+
+inline void
+    onPcieSlotGetAllDone(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+                         const boost::system::error_code ec,
+                         const dbus::utility::DBusPropertiesMap& propertiesList)
+{
+    if (ec)
+    {
+        BMCWEB_LOG_ERROR << "Can't get PCIeSlot properties!";
+        messages::internalError(asyncResp->res);
+        return;
+    }
+
+    nlohmann::json& slots = asyncResp->res.jsonValue["Slots"];
+
+    nlohmann::json::array_t* slotsPtr =
+        slots.get_ptr<nlohmann::json::array_t*>();
+    if (slotsPtr == nullptr)
+    {
+        BMCWEB_LOG_ERROR << "Slots key isn't an array???";
+        messages::internalError(asyncResp->res);
+        return;
+    }
+
+    nlohmann::json::object_t slot;
+
+    for (const auto& property : propertiesList)
+    {
+        const std::string& propertyName = property.first;
+
+        if (propertyName == "Generation")
+        {
+            const std::string* value =
+                std::get_if<std::string>(&property.second);
+            if (value == nullptr)
+            {
+                messages::internalError(asyncResp->res);
+                return;
+            }
+            std::optional<std::string> pcieType =
+                redfishPcieGenerationFromDbus(*value);
+            if (!pcieType)
+            {
+                messages::internalError(asyncResp->res);
+                return;
+            }
+            slot["PCIeType"] = !pcieType;
+        }
+        else if (propertyName == "Lanes")
+        {
+            const size_t* value = std::get_if<size_t>(&property.second);
+            if (value == nullptr)
+            {
+                messages::internalError(asyncResp->res);
+                return;
+            }
+            slot["Lanes"] = *value;
+        }
+        else if (propertyName == "SlotType")
+        {
+            const std::string* value =
+                std::get_if<std::string>(&property.second);
+            if (value == nullptr)
+            {
+                messages::internalError(asyncResp->res);
+                return;
+            }
+            std::string slotType = dbusSlotTypeToRf(*value);
+            if (!slotType.empty())
+            {
+                messages::internalError(asyncResp->res);
+                return;
+            }
+            slot["SlotType"] = slotType;
+        }
+        else if (propertyName == "HotPluggable")
+        {
+            const bool* value = std::get_if<bool>(&property.second);
+            if (value == nullptr)
+            {
+                messages::internalError(asyncResp->res);
+                return;
+            }
+            slot["HotPluggable"] = *value;
+        }
+    }
+    slots.emplace_back(std::move(slot));
+}
+
+inline void onMapperAssociationDone(
+    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+    const std::string& chassisID, const std::string& pcieSlotPath,
+    const std::string& connectionName, const boost::system::error_code ec,
+    const std::variant<std::vector<std::string>>& endpoints)
+{
+    if (ec)
+    {
+        if (ec.value() == EBADR)
+        {
+            // This PCIeSlot have no chassis association.
+            return;
+        }
+        BMCWEB_LOG_ERROR << "DBUS response error";
+        messages::internalError(asyncResp->res);
+        return;
+    }
+
+    const std::vector<std::string>* pcieSlotChassis =
+        std::get_if<std::vector<std::string>>(&(endpoints));
+
+    if (pcieSlotChassis == nullptr)
+    {
+        BMCWEB_LOG_ERROR << "Error getting PCIe Slot association!";
+        messages::internalError(asyncResp->res);
+        return;
+    }
+
+    if (pcieSlotChassis->size() != 1)
+    {
+        BMCWEB_LOG_ERROR << "PCIe Slot association error! ";
+        messages::internalError(asyncResp->res);
+        return;
+    }
+
+    sdbusplus::message::object_path path((*pcieSlotChassis)[0]);
+    std::string chassisName = path.filename();
+    if (chassisName != chassisID)
+    {
+        // The pcie slot doesn't belong to the chassisID
+        return;
+    }
+
+    crow::connections::systemBus->async_method_call(
+        [asyncResp](const boost::system::error_code ec,
+                    const dbus::utility::DBusPropertiesMap& propertiesList) {
+        onPcieSlotGetAllDone(asyncResp, ec, propertiesList);
+        },
+        connectionName, pcieSlotPath, "org.freedesktop.DBus.Properties",
+        "GetAll", "xyz.openbmc_project.Inventory.Item.PCIeSlot");
+}
+
+inline void
+    onMapperSubtreeDone(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+                        const std::string& chassisID,
+                        const boost::system::error_code ec,
+                        const dbus::utility::MapperGetSubTreeResponse& subtree)
+{
+    if (ec)
+    {
+        BMCWEB_LOG_ERROR << "D-Bus response error on GetSubTree " << ec;
+        messages::internalError(asyncResp->res);
+        return;
+    }
+    if (subtree.empty())
+    {
+        messages::resourceNotFound(asyncResp->res, "#Chassis", chassisID);
+        return;
+    }
+
+    BMCWEB_LOG_DEBUG << "Get properties for PCIeSlots associated to chassis = "
+                     << chassisID;
+
+    asyncResp->res.jsonValue["@odata.type"] = "#PCIeSlots.v1_4_1.PCIeSlots";
+    asyncResp->res.jsonValue["Name"] = "PCIe Slot Information";
+    asyncResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces(
+        "redfish", "v1", "Chassis", chassisID, "PCIeSlots");
+    asyncResp->res.jsonValue["Id"] = "1";
+    asyncResp->res.jsonValue["Slots"] = nlohmann::json::array();
+
+    for (const auto& pathServicePair : subtree)
+    {
+        const std::string& pcieSlotPath = pathServicePair.first;
+        for (const auto& connectionInterfacePair : pathServicePair.second)
+        {
+            const std::string& connectionName = connectionInterfacePair.first;
+            sdbusplus::message::object_path pcieSlotAssociationPath(
+                pcieSlotPath);
+            pcieSlotAssociationPath /= "chassis";
+
+            // The association of this PCIeSlot is used to determine whether
+            // it belongs to this ChassisID
+            crow::connections::systemBus->async_method_call(
+                [asyncResp, chassisID, pcieSlotPath, connectionName](
+                    const boost::system::error_code ec,
+                    const std::variant<std::vector<std::string>>& endpoints) {
+                onMapperAssociationDone(asyncResp, chassisID, pcieSlotPath,
+                                        connectionName, ec, endpoints);
+                },
+                "xyz.openbmc_project.ObjectMapper",
+                std::string{pcieSlotAssociationPath},
+                "org.freedesktop.DBus.Properties", "Get",
+                "xyz.openbmc_project.Association", "endpoints");
+        }
+    }
+}
+
+inline void handlePCIeSlotCollectionGet(
+    crow::App& app, const crow::Request& req,
+    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+    const std::string& chassisID)
+{
+    if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
+    {
+        return;
+    }
+
+    crow::connections::systemBus->async_method_call(
+        [asyncResp,
+         chassisID](const boost::system::error_code ec,
+                    const dbus::utility::MapperGetSubTreeResponse& subtree) {
+        onMapperSubtreeDone(asyncResp, chassisID, ec, subtree);
+        },
+        "xyz.openbmc_project.ObjectMapper",
+        "/xyz/openbmc_project/object_mapper",
+        "xyz.openbmc_project.ObjectMapper", "GetSubTree",
+        "/xyz/openbmc_project/inventory", int32_t(0),
+        std::array<const char*, 1>{
+            "xyz.openbmc_project.Inventory.Item.PCIeSlot"});
+}
+
+inline void requestRoutesPCIeSlots(App& app)
+{
+    BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/PCIeSlots/")
+        .privileges(redfish::privileges::getPCIeSlots)
+        .methods(boost::beast::http::verb::get)(
+            std::bind_front(handlePCIeSlotCollectionGet, std::ref(app)));
+}
+
+} // namespace redfish