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