xref: /openbmc/bmcweb/redfish-core/lib/pcie.hpp (revision 3a58c5a8)
1 /*
2 // Copyright (c) 2018 Intel Corporation
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //      http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 */
16 
17 #pragma once
18 
19 #include "app.hpp"
20 #include "dbus_utility.hpp"
21 #include "generated/enums/resource.hpp"
22 #include "query.hpp"
23 #include "registries/privilege_registry.hpp"
24 #include "utils/collection.hpp"
25 #include "utils/dbus_utils.hpp"
26 #include "utils/pcie_util.hpp"
27 
28 #include <boost/system/linux_error.hpp>
29 #include <boost/url/format.hpp>
30 #include <sdbusplus/asio/property.hpp>
31 #include <sdbusplus/unpack_properties.hpp>
32 
33 #include <limits>
34 
35 namespace redfish
36 {
37 
38 static constexpr const char* inventoryPath = "/xyz/openbmc_project/inventory";
39 static constexpr std::array<std::string_view, 1> pcieDeviceInterface = {
40     "xyz.openbmc_project.Inventory.Item.PCIeDevice"};
41 static constexpr std::array<std::string_view, 1> pcieSlotInterface = {
42     "xyz.openbmc_project.Inventory.Item.PCIeSlot"};
43 
44 static inline void handlePCIeDevicePath(
45     const std::string& pcieDeviceId,
46     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
47     const dbus::utility::MapperGetSubTreePathsResponse& pcieDevicePaths,
48     const std::function<void(const std::string& pcieDevicePath,
49                              const std::string& service)>& callback)
50 
51 {
52     for (const std::string& pcieDevicePath : pcieDevicePaths)
53     {
54         std::string pciecDeviceName =
55             sdbusplus::message::object_path(pcieDevicePath).filename();
56         if (pciecDeviceName.empty() || pciecDeviceName != pcieDeviceId)
57         {
58             continue;
59         }
60 
61         dbus::utility::getDbusObject(
62             pcieDevicePath, pcieDeviceInterface,
63             [pcieDevicePath, asyncResp,
64              callback](const boost::system::error_code& ec,
65                        const dbus::utility::MapperGetObject& object) {
66                 if (ec || object.empty())
67                 {
68                     BMCWEB_LOG_ERROR("DBUS response error {}", ec);
69                     messages::internalError(asyncResp->res);
70                     return;
71                 }
72                 callback(pcieDevicePath, object.begin()->first);
73             });
74         return;
75     }
76 
77     BMCWEB_LOG_WARNING("PCIe Device not found");
78     messages::resourceNotFound(asyncResp->res, "PCIeDevice", pcieDeviceId);
79 }
80 
81 static inline void getValidPCIeDevicePath(
82     const std::string& pcieDeviceId,
83     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
84     const std::function<void(const std::string& pcieDevicePath,
85                              const std::string& service)>& callback)
86 {
87     dbus::utility::getSubTreePaths(
88         inventoryPath, 0, pcieDeviceInterface,
89         [pcieDeviceId, asyncResp,
90          callback](const boost::system::error_code& ec,
91                    const dbus::utility::MapperGetSubTreePathsResponse&
92                        pcieDevicePaths) {
93             if (ec)
94             {
95                 BMCWEB_LOG_ERROR("D-Bus response error on GetSubTree {}", ec);
96                 messages::internalError(asyncResp->res);
97                 return;
98             }
99             handlePCIeDevicePath(pcieDeviceId, asyncResp, pcieDevicePaths,
100                                  callback);
101             return;
102         });
103 }
104 
105 static inline void handlePCIeDeviceCollectionGet(
106     crow::App& app, const crow::Request& req,
107     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
108     const std::string& systemName)
109 {
110     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
111     {
112         return;
113     }
114     if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
115     {
116         // Option currently returns no systems.  TBD
117         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
118                                    systemName);
119         return;
120     }
121     if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
122     {
123         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
124                                    systemName);
125         return;
126     }
127 
128     asyncResp->res.addHeader(boost::beast::http::field::link,
129                              "</redfish/v1/JsonSchemas/PCIeDeviceCollection/"
130                              "PCIeDeviceCollection.json>; rel=describedby");
131     asyncResp->res.jsonValue["@odata.type"] =
132         "#PCIeDeviceCollection.PCIeDeviceCollection";
133     asyncResp->res.jsonValue["@odata.id"] = std::format(
134         "/redfish/v1/Systems/{}/PCIeDevices", BMCWEB_REDFISH_SYSTEM_URI_NAME);
135     asyncResp->res.jsonValue["Name"] = "PCIe Device Collection";
136     asyncResp->res.jsonValue["Description"] = "Collection of PCIe Devices";
137 
138     pcie_util::getPCIeDeviceList(asyncResp,
139                                  nlohmann::json::json_pointer("/Members"));
140 }
141 
142 inline void requestRoutesSystemPCIeDeviceCollection(App& app)
143 {
144     /**
145      * Functions triggers appropriate requests on DBus
146      */
147     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/PCIeDevices/")
148         .privileges(redfish::privileges::getPCIeDeviceCollection)
149         .methods(boost::beast::http::verb::get)(
150             std::bind_front(handlePCIeDeviceCollectionGet, std::ref(app)));
151 }
152 
153 inline void addPCIeSlotProperties(
154     crow::Response& res, const boost::system::error_code& ec,
155     const dbus::utility::DBusPropertiesMap& pcieSlotProperties)
156 {
157     if (ec)
158     {
159         BMCWEB_LOG_ERROR("DBUS response error for getAllProperties{}",
160                          ec.value());
161         messages::internalError(res);
162         return;
163     }
164     std::string generation;
165     size_t lanes = 0;
166     std::string slotType;
167 
168     bool success = sdbusplus::unpackPropertiesNoThrow(
169         dbus_utils::UnpackErrorPrinter(), pcieSlotProperties, "Generation",
170         generation, "Lanes", lanes, "SlotType", slotType);
171 
172     if (!success)
173     {
174         messages::internalError(res);
175         return;
176     }
177 
178     std::optional<pcie_device::PCIeTypes> pcieType =
179         pcie_util::redfishPcieGenerationFromDbus(generation);
180     if (!pcieType)
181     {
182         BMCWEB_LOG_WARNING("Unknown PCIeType: {}", generation);
183     }
184     else
185     {
186         if (*pcieType == pcie_device::PCIeTypes::Invalid)
187         {
188             BMCWEB_LOG_ERROR("Invalid PCIeType: {}", generation);
189             messages::internalError(res);
190             return;
191         }
192         res.jsonValue["Slot"]["PCIeType"] = *pcieType;
193     }
194 
195     if (lanes != 0)
196     {
197         res.jsonValue["Slot"]["Lanes"] = lanes;
198     }
199 
200     std::optional<pcie_slots::SlotTypes> redfishSlotType =
201         pcie_util::dbusSlotTypeToRf(slotType);
202     if (!redfishSlotType)
203     {
204         BMCWEB_LOG_WARNING("Unknown PCIeSlot Type: {}", slotType);
205     }
206     else
207     {
208         if (*redfishSlotType == pcie_slots::SlotTypes::Invalid)
209         {
210             BMCWEB_LOG_ERROR("Invalid PCIeSlot type: {}", slotType);
211             messages::internalError(res);
212             return;
213         }
214         res.jsonValue["Slot"]["SlotType"] = *redfishSlotType;
215     }
216 }
217 
218 inline void getPCIeDeviceSlotPath(
219     const std::string& pcieDevicePath,
220     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
221     std::function<void(const std::string& pcieDeviceSlot)>&& callback)
222 {
223     std::string associationPath = pcieDevicePath + "/contained_by";
224     dbus::utility::getAssociatedSubTreePaths(
225         associationPath, sdbusplus::message::object_path(inventoryPath), 0,
226         pcieSlotInterface,
227         [callback = std::move(callback), asyncResp, pcieDevicePath](
228             const boost::system::error_code& ec,
229             const dbus::utility::MapperGetSubTreePathsResponse& endpoints) {
230             if (ec)
231             {
232                 if (ec.value() == EBADR)
233                 {
234                     // Missing association is not an error
235                     return;
236                 }
237                 BMCWEB_LOG_ERROR(
238                     "DBUS response error for getAssociatedSubTreePaths {}",
239                     ec.value());
240                 messages::internalError(asyncResp->res);
241                 return;
242             }
243             if (endpoints.size() > 1)
244             {
245                 BMCWEB_LOG_ERROR(
246                     "PCIeDevice is associated with more than one PCIeSlot: {}",
247                     endpoints.size());
248                 messages::internalError(asyncResp->res);
249                 return;
250             }
251             if (endpoints.empty())
252             {
253                 // If the device doesn't have an association, return without
254                 // PCIe Slot properties
255                 BMCWEB_LOG_DEBUG("PCIeDevice is not associated with PCIeSlot");
256                 return;
257             }
258             callback(endpoints[0]);
259         });
260 }
261 
262 inline void afterGetDbusObject(
263     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
264     const std::string& pcieDeviceSlot, const boost::system::error_code& ec,
265     const dbus::utility::MapperGetObject& object)
266 {
267     if (ec || object.empty())
268     {
269         BMCWEB_LOG_ERROR("DBUS response error for getDbusObject {}",
270                          ec.value());
271         messages::internalError(asyncResp->res);
272         return;
273     }
274     sdbusplus::asio::getAllProperties(
275         *crow::connections::systemBus, object.begin()->first, pcieDeviceSlot,
276         "xyz.openbmc_project.Inventory.Item.PCIeSlot",
277         [asyncResp](
278             const boost::system::error_code& ec2,
279             const dbus::utility::DBusPropertiesMap& pcieSlotProperties) {
280             addPCIeSlotProperties(asyncResp->res, ec2, pcieSlotProperties);
281         });
282 }
283 
284 inline void afterGetPCIeDeviceSlotPath(
285     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
286     const std::string& pcieDeviceSlot)
287 {
288     dbus::utility::getDbusObject(
289         pcieDeviceSlot, pcieSlotInterface,
290         [asyncResp,
291          pcieDeviceSlot](const boost::system::error_code& ec,
292                          const dbus::utility::MapperGetObject& object) {
293             afterGetDbusObject(asyncResp, pcieDeviceSlot, ec, object);
294         });
295 }
296 
297 inline void getPCIeDeviceHealth(
298     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
299     const std::string& pcieDevicePath, const std::string& service)
300 {
301     sdbusplus::asio::getProperty<bool>(
302         *crow::connections::systemBus, service, pcieDevicePath,
303         "xyz.openbmc_project.State.Decorator.OperationalStatus", "Functional",
304         [asyncResp](const boost::system::error_code& ec, const bool value) {
305             if (ec)
306             {
307                 if (ec.value() != EBADR)
308                 {
309                     BMCWEB_LOG_ERROR("DBUS response error for Health {}",
310                                      ec.value());
311                     messages::internalError(asyncResp->res);
312                 }
313                 return;
314             }
315 
316             if (!value)
317             {
318                 asyncResp->res.jsonValue["Status"]["Health"] =
319                     resource::Health::Critical;
320             }
321         });
322 }
323 
324 inline void getPCIeDeviceState(
325     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
326     const std::string& pcieDevicePath, const std::string& service)
327 {
328     sdbusplus::asio::getProperty<bool>(
329         *crow::connections::systemBus, service, pcieDevicePath,
330         "xyz.openbmc_project.Inventory.Item", "Present",
331         [asyncResp](const boost::system::error_code& ec, bool value) {
332             if (ec)
333             {
334                 if (ec.value() != EBADR)
335                 {
336                     BMCWEB_LOG_ERROR("DBUS response error for State");
337                     messages::internalError(asyncResp->res);
338                 }
339                 return;
340             }
341 
342             if (!value)
343             {
344                 asyncResp->res.jsonValue["Status"]["State"] =
345                     resource::State::Absent;
346             }
347         });
348 }
349 
350 inline void getPCIeDeviceAsset(
351     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
352     const std::string& pcieDevicePath, const std::string& service)
353 {
354     sdbusplus::asio::getAllProperties(
355         *crow::connections::systemBus, service, pcieDevicePath,
356         "xyz.openbmc_project.Inventory.Decorator.Asset",
357         [pcieDevicePath, asyncResp{asyncResp}](
358             const boost::system::error_code& ec,
359             const dbus::utility::DBusPropertiesMap& assetList) {
360             if (ec)
361             {
362                 if (ec.value() != EBADR)
363                 {
364                     BMCWEB_LOG_ERROR("DBUS response error for Properties{}",
365                                      ec.value());
366                     messages::internalError(asyncResp->res);
367                 }
368                 return;
369             }
370 
371             const std::string* manufacturer = nullptr;
372             const std::string* model = nullptr;
373             const std::string* partNumber = nullptr;
374             const std::string* serialNumber = nullptr;
375             const std::string* sparePartNumber = nullptr;
376 
377             const bool success = sdbusplus::unpackPropertiesNoThrow(
378                 dbus_utils::UnpackErrorPrinter(), assetList, "Manufacturer",
379                 manufacturer, "Model", model, "PartNumber", partNumber,
380                 "SerialNumber", serialNumber, "SparePartNumber",
381                 sparePartNumber);
382 
383             if (!success)
384             {
385                 messages::internalError(asyncResp->res);
386                 return;
387             }
388 
389             if (manufacturer != nullptr)
390             {
391                 asyncResp->res.jsonValue["Manufacturer"] = *manufacturer;
392             }
393             if (model != nullptr)
394             {
395                 asyncResp->res.jsonValue["Model"] = *model;
396             }
397 
398             if (partNumber != nullptr)
399             {
400                 asyncResp->res.jsonValue["PartNumber"] = *partNumber;
401             }
402 
403             if (serialNumber != nullptr)
404             {
405                 asyncResp->res.jsonValue["SerialNumber"] = *serialNumber;
406             }
407 
408             if (sparePartNumber != nullptr && !sparePartNumber->empty())
409             {
410                 asyncResp->res.jsonValue["SparePartNumber"] = *sparePartNumber;
411             }
412         });
413 }
414 
415 inline void addPCIeDeviceProperties(
416     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
417     const std::string& pcieDeviceId,
418     const dbus::utility::DBusPropertiesMap& pcieDevProperties)
419 {
420     const std::string* generationInUse = nullptr;
421     const std::string* generationSupported = nullptr;
422     const size_t* lanesInUse = nullptr;
423     const size_t* maxLanes = nullptr;
424 
425     const bool success = sdbusplus::unpackPropertiesNoThrow(
426         dbus_utils::UnpackErrorPrinter(), pcieDevProperties, "GenerationInUse",
427         generationInUse, "GenerationSupported", generationSupported,
428         "LanesInUse", lanesInUse, "MaxLanes", maxLanes);
429 
430     if (!success)
431     {
432         messages::internalError(asyncResp->res);
433         return;
434     }
435 
436     if (generationInUse != nullptr)
437     {
438         std::optional<pcie_device::PCIeTypes> redfishGenerationInUse =
439             pcie_util::redfishPcieGenerationFromDbus(*generationInUse);
440 
441         if (!redfishGenerationInUse)
442         {
443             BMCWEB_LOG_WARNING("Unknown PCIe Device Generation: {}",
444                                *generationInUse);
445         }
446         else
447         {
448             if (*redfishGenerationInUse == pcie_device::PCIeTypes::Invalid)
449             {
450                 BMCWEB_LOG_ERROR("Invalid PCIe Device Generation: {}",
451                                  *generationInUse);
452                 messages::internalError(asyncResp->res);
453                 return;
454             }
455             asyncResp->res.jsonValue["PCIeInterface"]["PCIeType"] =
456                 *redfishGenerationInUse;
457         }
458     }
459 
460     if (generationSupported != nullptr)
461     {
462         std::optional<pcie_device::PCIeTypes> redfishGenerationSupported =
463             pcie_util::redfishPcieGenerationFromDbus(*generationSupported);
464 
465         if (!redfishGenerationSupported)
466         {
467             BMCWEB_LOG_WARNING("Unknown PCIe Device Generation: {}",
468                                *generationSupported);
469         }
470         else
471         {
472             if (*redfishGenerationSupported == pcie_device::PCIeTypes::Invalid)
473             {
474                 BMCWEB_LOG_ERROR("Invalid PCIe Device Generation: {}",
475                                  *generationSupported);
476                 messages::internalError(asyncResp->res);
477                 return;
478             }
479             asyncResp->res.jsonValue["PCIeInterface"]["MaxPCIeType"] =
480                 *redfishGenerationSupported;
481         }
482     }
483 
484     if (lanesInUse != nullptr)
485     {
486         if (*lanesInUse == std::numeric_limits<size_t>::max())
487         {
488             // The default value of LanesInUse is "maxint", and the field will
489             // be null if it is a default value.
490             asyncResp->res.jsonValue["PCIeInterface"]["LanesInUse"] = nullptr;
491         }
492         else
493         {
494             asyncResp->res.jsonValue["PCIeInterface"]["LanesInUse"] =
495                 *lanesInUse;
496         }
497     }
498     // The default value of MaxLanes is 0, and the field will be
499     // left as off if it is a default value.
500     if (maxLanes != nullptr && *maxLanes != 0)
501     {
502         asyncResp->res.jsonValue["PCIeInterface"]["MaxLanes"] = *maxLanes;
503     }
504 
505     asyncResp->res.jsonValue["PCIeFunctions"]["@odata.id"] =
506         boost::urls::format(
507             "/redfish/v1/Systems/{}/PCIeDevices/{}/PCIeFunctions",
508             BMCWEB_REDFISH_SYSTEM_URI_NAME, pcieDeviceId);
509 }
510 
511 inline void getPCIeDeviceProperties(
512     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
513     const std::string& pcieDevicePath, const std::string& service,
514     const std::function<void(
515         const dbus::utility::DBusPropertiesMap& pcieDevProperties)>&& callback)
516 {
517     sdbusplus::asio::getAllProperties(
518         *crow::connections::systemBus, service, pcieDevicePath,
519         "xyz.openbmc_project.Inventory.Item.PCIeDevice",
520         [asyncResp,
521          callback](const boost::system::error_code& ec,
522                    const dbus::utility::DBusPropertiesMap& pcieDevProperties) {
523             if (ec)
524             {
525                 if (ec.value() != EBADR)
526                 {
527                     BMCWEB_LOG_ERROR("DBUS response error for Properties");
528                     messages::internalError(asyncResp->res);
529                 }
530                 return;
531             }
532             callback(pcieDevProperties);
533         });
534 }
535 
536 inline void addPCIeDeviceCommonProperties(
537     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
538     const std::string& pcieDeviceId)
539 {
540     asyncResp->res.addHeader(
541         boost::beast::http::field::link,
542         "</redfish/v1/JsonSchemas/PCIeDevice/PCIeDevice.json>; rel=describedby");
543     asyncResp->res.jsonValue["@odata.type"] = "#PCIeDevice.v1_9_0.PCIeDevice";
544     asyncResp->res.jsonValue["@odata.id"] =
545         boost::urls::format("/redfish/v1/Systems/{}/PCIeDevices/{}",
546                             BMCWEB_REDFISH_SYSTEM_URI_NAME, pcieDeviceId);
547     asyncResp->res.jsonValue["Name"] = "PCIe Device";
548     asyncResp->res.jsonValue["Id"] = pcieDeviceId;
549     asyncResp->res.jsonValue["Status"]["State"] = resource::State::Enabled;
550     asyncResp->res.jsonValue["Status"]["Health"] = resource::Health::OK;
551 }
552 
553 inline void afterGetValidPcieDevicePath(
554     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
555     const std::string& pcieDeviceId, const std::string& pcieDevicePath,
556     const std::string& service)
557 {
558     addPCIeDeviceCommonProperties(asyncResp, pcieDeviceId);
559     getPCIeDeviceAsset(asyncResp, pcieDevicePath, service);
560     getPCIeDeviceState(asyncResp, pcieDevicePath, service);
561     getPCIeDeviceHealth(asyncResp, pcieDevicePath, service);
562     getPCIeDeviceProperties(
563         asyncResp, pcieDevicePath, service,
564         std::bind_front(addPCIeDeviceProperties, asyncResp, pcieDeviceId));
565     getPCIeDeviceSlotPath(
566         pcieDevicePath, asyncResp,
567         std::bind_front(afterGetPCIeDeviceSlotPath, asyncResp));
568 }
569 
570 inline void handlePCIeDeviceGet(
571     App& app, const crow::Request& req,
572     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
573     const std::string& systemName, const std::string& pcieDeviceId)
574 {
575     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
576     {
577         return;
578     }
579     if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
580     {
581         // Option currently returns no systems.  TBD
582         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
583                                    systemName);
584         return;
585     }
586     if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
587     {
588         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
589                                    systemName);
590         return;
591     }
592 
593     getValidPCIeDevicePath(
594         pcieDeviceId, asyncResp,
595         std::bind_front(afterGetValidPcieDevicePath, asyncResp, pcieDeviceId));
596 }
597 
598 inline void requestRoutesSystemPCIeDevice(App& app)
599 {
600     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/PCIeDevices/<str>/")
601         .privileges(redfish::privileges::getPCIeDevice)
602         .methods(boost::beast::http::verb::get)(
603             std::bind_front(handlePCIeDeviceGet, std::ref(app)));
604 }
605 
606 inline void addPCIeFunctionList(
607     crow::Response& res, const std::string& pcieDeviceId,
608     const dbus::utility::DBusPropertiesMap& pcieDevProperties)
609 {
610     nlohmann::json& pcieFunctionList = res.jsonValue["Members"];
611     pcieFunctionList = nlohmann::json::array();
612     static constexpr const int maxPciFunctionNum = 8;
613 
614     for (int functionNum = 0; functionNum < maxPciFunctionNum; functionNum++)
615     {
616         // Check if this function exists by
617         // looking for a device ID
618         std::string devIDProperty =
619             "Function" + std::to_string(functionNum) + "DeviceId";
620         const std::string* property = nullptr;
621         for (const auto& propEntry : pcieDevProperties)
622         {
623             if (propEntry.first == devIDProperty)
624             {
625                 property = std::get_if<std::string>(&propEntry.second);
626                 break;
627             }
628         }
629         if (property == nullptr || property->empty())
630         {
631             continue;
632         }
633 
634         nlohmann::json::object_t pcieFunction;
635         pcieFunction["@odata.id"] = boost::urls::format(
636             "/redfish/v1/Systems/{}/PCIeDevices/{}/PCIeFunctions/{}",
637             BMCWEB_REDFISH_SYSTEM_URI_NAME, pcieDeviceId,
638             std::to_string(functionNum));
639         pcieFunctionList.emplace_back(std::move(pcieFunction));
640     }
641     res.jsonValue["PCIeFunctions@odata.count"] = pcieFunctionList.size();
642 }
643 
644 inline void handlePCIeFunctionCollectionGet(
645     App& app, const crow::Request& req,
646     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
647     const std::string& systemName, const std::string& pcieDeviceId)
648 {
649     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
650     {
651         return;
652     }
653     if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
654     {
655         // Option currently returns no systems.  TBD
656         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
657                                    systemName);
658         return;
659     }
660 
661     getValidPCIeDevicePath(
662         pcieDeviceId, asyncResp,
663         [asyncResp, pcieDeviceId](const std::string& pcieDevicePath,
664                                   const std::string& service) {
665             asyncResp->res.addHeader(
666                 boost::beast::http::field::link,
667                 "</redfish/v1/JsonSchemas/PCIeFunctionCollection/PCIeFunctionCollection.json>; rel=describedby");
668             asyncResp->res.jsonValue["@odata.type"] =
669                 "#PCIeFunctionCollection.PCIeFunctionCollection";
670             asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
671                 "/redfish/v1/Systems/{}/PCIeDevices/{}/PCIeFunctions",
672                 BMCWEB_REDFISH_SYSTEM_URI_NAME, pcieDeviceId);
673             asyncResp->res.jsonValue["Name"] = "PCIe Function Collection";
674             asyncResp->res.jsonValue["Description"] =
675                 "Collection of PCIe Functions for PCIe Device " + pcieDeviceId;
676             getPCIeDeviceProperties(
677                 asyncResp, pcieDevicePath, service,
678                 [asyncResp, pcieDeviceId](
679                     const dbus::utility::DBusPropertiesMap& pcieDevProperties) {
680                     addPCIeFunctionList(asyncResp->res, pcieDeviceId,
681                                         pcieDevProperties);
682                 });
683         });
684 }
685 
686 inline void requestRoutesSystemPCIeFunctionCollection(App& app)
687 {
688     /**
689      * Functions triggers appropriate requests on DBus
690      */
691     BMCWEB_ROUTE(app,
692                  "/redfish/v1/Systems/<str>/PCIeDevices/<str>/PCIeFunctions/")
693         .privileges(redfish::privileges::getPCIeFunctionCollection)
694         .methods(boost::beast::http::verb::get)(
695             std::bind_front(handlePCIeFunctionCollectionGet, std::ref(app)));
696 }
697 
698 inline bool validatePCIeFunctionId(
699     uint64_t pcieFunctionId,
700     const dbus::utility::DBusPropertiesMap& pcieDevProperties)
701 {
702     std::string functionName = "Function" + std::to_string(pcieFunctionId);
703     std::string devIDProperty = functionName + "DeviceId";
704 
705     const std::string* devIdProperty = nullptr;
706     for (const auto& property : pcieDevProperties)
707     {
708         if (property.first == devIDProperty)
709         {
710             devIdProperty = std::get_if<std::string>(&property.second);
711             break;
712         }
713     }
714     return (devIdProperty != nullptr && !devIdProperty->empty());
715 }
716 
717 inline void addPCIeFunctionProperties(
718     crow::Response& resp, uint64_t pcieFunctionId,
719     const dbus::utility::DBusPropertiesMap& pcieDevProperties)
720 {
721     std::string functionName = "Function" + std::to_string(pcieFunctionId);
722     for (const auto& property : pcieDevProperties)
723     {
724         const std::string* strProperty =
725             std::get_if<std::string>(&property.second);
726         if (strProperty == nullptr)
727         {
728             continue;
729         }
730         if (property.first == functionName + "DeviceId")
731         {
732             resp.jsonValue["DeviceId"] = *strProperty;
733         }
734         if (property.first == functionName + "VendorId")
735         {
736             resp.jsonValue["VendorId"] = *strProperty;
737         }
738         // TODO: FunctionType and DeviceClass are Redfish enums. The D-Bus
739         // property strings should be mapped correctly to ensure these
740         // strings are Redfish enum values. For now just check for empty.
741         if (property.first == functionName + "FunctionType")
742         {
743             if (!strProperty->empty())
744             {
745                 resp.jsonValue["FunctionType"] = *strProperty;
746             }
747         }
748         if (property.first == functionName + "DeviceClass")
749         {
750             if (!strProperty->empty())
751             {
752                 resp.jsonValue["DeviceClass"] = *strProperty;
753             }
754         }
755         if (property.first == functionName + "ClassCode")
756         {
757             resp.jsonValue["ClassCode"] = *strProperty;
758         }
759         if (property.first == functionName + "RevisionId")
760         {
761             resp.jsonValue["RevisionId"] = *strProperty;
762         }
763         if (property.first == functionName + "SubsystemId")
764         {
765             resp.jsonValue["SubsystemId"] = *strProperty;
766         }
767         if (property.first == functionName + "SubsystemVendorId")
768         {
769             resp.jsonValue["SubsystemVendorId"] = *strProperty;
770         }
771     }
772 }
773 
774 inline void addPCIeFunctionCommonProperties(crow::Response& resp,
775                                             const std::string& pcieDeviceId,
776                                             uint64_t pcieFunctionId)
777 {
778     resp.addHeader(
779         boost::beast::http::field::link,
780         "</redfish/v1/JsonSchemas/PCIeFunction/PCIeFunction.json>; rel=describedby");
781     resp.jsonValue["@odata.type"] = "#PCIeFunction.v1_2_3.PCIeFunction";
782     resp.jsonValue["@odata.id"] = boost::urls::format(
783         "/redfish/v1/Systems/{}/PCIeDevices/{}/PCIeFunctions/{}",
784         BMCWEB_REDFISH_SYSTEM_URI_NAME, pcieDeviceId,
785         std::to_string(pcieFunctionId));
786     resp.jsonValue["Name"] = "PCIe Function";
787     resp.jsonValue["Id"] = std::to_string(pcieFunctionId);
788     resp.jsonValue["FunctionId"] = pcieFunctionId;
789     resp.jsonValue["Links"]["PCIeDevice"]["@odata.id"] =
790         boost::urls::format("/redfish/v1/Systems/{}/PCIeDevices/{}",
791                             BMCWEB_REDFISH_SYSTEM_URI_NAME, pcieDeviceId);
792 }
793 
794 inline void handlePCIeFunctionGet(
795     App& app, const crow::Request& req,
796     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
797     const std::string& systemName, const std::string& pcieDeviceId,
798     const std::string& pcieFunctionIdStr)
799 {
800     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
801     {
802         return;
803     }
804     if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
805     {
806         // Option currently returns no systems.  TBD
807         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
808                                    systemName);
809         return;
810     }
811     if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
812     {
813         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
814                                    systemName);
815         return;
816     }
817     std::string_view pcieFunctionIdView = pcieFunctionIdStr;
818 
819     uint64_t pcieFunctionId = 0;
820     std::from_chars_result result = std::from_chars(
821         pcieFunctionIdView.begin(), pcieFunctionIdView.end(), pcieFunctionId);
822     if (result.ec != std::errc{} || result.ptr != pcieFunctionIdView.end())
823     {
824         messages::resourceNotFound(asyncResp->res, "PCIeFunction",
825                                    pcieFunctionIdStr);
826         return;
827     }
828 
829     getValidPCIeDevicePath(
830         pcieDeviceId, asyncResp,
831         [asyncResp, pcieDeviceId, pcieFunctionId](
832             const std::string& pcieDevicePath, const std::string& service) {
833             getPCIeDeviceProperties(
834                 asyncResp, pcieDevicePath, service,
835                 [asyncResp, pcieDeviceId, pcieFunctionId](
836                     const dbus::utility::DBusPropertiesMap& pcieDevProperties) {
837                     addPCIeFunctionCommonProperties(
838                         asyncResp->res, pcieDeviceId, pcieFunctionId);
839                     addPCIeFunctionProperties(asyncResp->res, pcieFunctionId,
840                                               pcieDevProperties);
841                 });
842         });
843 }
844 
845 inline void requestRoutesSystemPCIeFunction(App& app)
846 {
847     BMCWEB_ROUTE(
848         app, "/redfish/v1/Systems/<str>/PCIeDevices/<str>/PCIeFunctions/<str>/")
849         .privileges(redfish::privileges::getPCIeFunction)
850         .methods(boost::beast::http::verb::get)(
851             std::bind_front(handlePCIeFunctionGet, std::ref(app)));
852 }
853 
854 } // namespace redfish
855