xref: /openbmc/bmcweb/redfish-core/lib/pcie.hpp (revision cf3b484e)
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 
39 static inline void handlePCIeDevicePath(
40     const std::string& pcieDeviceId,
41     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
42     const dbus::utility::MapperGetSubTreePathsResponse& pcieDevicePaths,
43     const std::function<void(const std::string& pcieDevicePath,
44                              const std::string& service)>& callback)
45 
46 {
47     for (const std::string& pcieDevicePath : pcieDevicePaths)
48     {
49         std::string pciecDeviceName =
50             sdbusplus::message::object_path(pcieDevicePath).filename();
51         if (pciecDeviceName.empty() || pciecDeviceName != pcieDeviceId)
52         {
53             continue;
54         }
55 
56         dbus::utility::getDbusObject(
57             pcieDevicePath, {},
58             [pcieDevicePath, asyncResp,
59              callback](const boost::system::error_code& ec,
60                        const dbus::utility::MapperGetObject& object) {
61             if (ec || object.empty())
62             {
63                 BMCWEB_LOG_ERROR << "DBUS response error " << ec;
64                 messages::internalError(asyncResp->res);
65                 return;
66             }
67             callback(pcieDevicePath, object.begin()->first);
68             });
69         return;
70     }
71 
72     BMCWEB_LOG_WARNING << "PCIe Device not found";
73     messages::resourceNotFound(asyncResp->res, "PCIeDevice", pcieDeviceId);
74 }
75 
76 static inline void getValidPCIeDevicePath(
77     const std::string& pcieDeviceId,
78     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
79     const std::function<void(const std::string& pcieDevicePath,
80                              const std::string& service)>& callback)
81 {
82     dbus::utility::getSubTreePaths(
83         inventoryPath, 0, pcieDeviceInterface,
84         [pcieDeviceId, asyncResp,
85          callback](const boost::system::error_code& ec,
86                    const dbus::utility::MapperGetSubTreePathsResponse&
87                        pcieDevicePaths) {
88         if (ec)
89         {
90             BMCWEB_LOG_ERROR << "D-Bus response error on GetSubTree " << ec;
91             messages::internalError(asyncResp->res);
92             return;
93         }
94         handlePCIeDevicePath(pcieDeviceId, asyncResp, pcieDevicePaths,
95                              callback);
96         return;
97         });
98 }
99 
100 static inline void handlePCIeDeviceCollectionGet(
101     crow::App& app, const crow::Request& req,
102     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
103     const std::string& systemName)
104 {
105     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
106     {
107         return;
108     }
109     if constexpr (bmcwebEnableMultiHost)
110     {
111         // Option currently returns no systems.  TBD
112         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
113                                    systemName);
114         return;
115     }
116     if (systemName != "system")
117     {
118         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
119                                    systemName);
120         return;
121     }
122 
123     asyncResp->res.addHeader(boost::beast::http::field::link,
124                              "</redfish/v1/JsonSchemas/PCIeDeviceCollection/"
125                              "PCIeDeviceCollection.json>; rel=describedby");
126     asyncResp->res.jsonValue["@odata.type"] =
127         "#PCIeDeviceCollection.PCIeDeviceCollection";
128     asyncResp->res.jsonValue["@odata.id"] =
129         "/redfish/v1/Systems/system/PCIeDevices";
130     asyncResp->res.jsonValue["Name"] = "PCIe Device Collection";
131     asyncResp->res.jsonValue["Description"] = "Collection of PCIe Devices";
132     asyncResp->res.jsonValue["Members"] = nlohmann::json::array();
133     asyncResp->res.jsonValue["Members@odata.count"] = 0;
134 
135     pcie_util::getPCIeDeviceList(asyncResp, "Members");
136 }
137 
138 inline void requestRoutesSystemPCIeDeviceCollection(App& app)
139 {
140     /**
141      * Functions triggers appropriate requests on DBus
142      */
143     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/PCIeDevices/")
144         .privileges(redfish::privileges::getPCIeDeviceCollection)
145         .methods(boost::beast::http::verb::get)(
146             std::bind_front(handlePCIeDeviceCollectionGet, std::ref(app)));
147 }
148 
149 inline void
150     getPCIeDeviceHealth(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
151                         const std::string& pcieDevicePath,
152                         const std::string& service)
153 {
154     sdbusplus::asio::getProperty<bool>(
155         *crow::connections::systemBus, service, pcieDevicePath,
156         "xyz.openbmc_project.State.Decorator.OperationalStatus", "Functional",
157         [asyncResp](const boost::system::error_code& ec, const bool value) {
158         if (ec)
159         {
160             if (ec.value() != EBADR)
161             {
162                 BMCWEB_LOG_ERROR << "DBUS response error for Health "
163                                  << ec.value();
164                 messages::internalError(asyncResp->res);
165             }
166             return;
167         }
168 
169         if (!value)
170         {
171             asyncResp->res.jsonValue["Status"]["Health"] = "Critical";
172         }
173         });
174 }
175 
176 inline void
177     getPCIeDeviceState(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
178                        const std::string& pcieDevicePath,
179                        const std::string& service)
180 {
181     sdbusplus::asio::getProperty<bool>(
182         *crow::connections::systemBus, service, pcieDevicePath,
183         "xyz.openbmc_project.Inventory.Item", "Present",
184         [asyncResp](const boost::system::error_code& ec, const bool value) {
185         if (ec)
186         {
187             if (ec.value() != EBADR)
188             {
189                 BMCWEB_LOG_ERROR << "DBUS response error for State";
190                 messages::internalError(asyncResp->res);
191             }
192             return;
193         }
194 
195         if (!value)
196         {
197             asyncResp->res.jsonValue["Status"]["State"] = "Absent";
198         }
199         });
200 }
201 
202 inline void
203     getPCIeDeviceAsset(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
204                        const std::string& pcieDevicePath,
205                        const std::string& service)
206 {
207     sdbusplus::asio::getAllProperties(
208         *crow::connections::systemBus, service, pcieDevicePath,
209         "xyz.openbmc_project.Inventory.Decorator.Asset",
210         [pcieDevicePath, asyncResp{asyncResp}](
211             const boost::system::error_code& ec,
212             const dbus::utility::DBusPropertiesMap& assetList) {
213         if (ec)
214         {
215             if (ec.value() != EBADR)
216             {
217                 BMCWEB_LOG_ERROR << "DBUS response error for Properties"
218                                  << ec.value();
219                 messages::internalError(asyncResp->res);
220             }
221             return;
222         }
223 
224         const std::string* manufacturer = nullptr;
225         const std::string* model = nullptr;
226         const std::string* partNumber = nullptr;
227         const std::string* serialNumber = nullptr;
228         const std::string* sparePartNumber = nullptr;
229 
230         const bool success = sdbusplus::unpackPropertiesNoThrow(
231             dbus_utils::UnpackErrorPrinter(), assetList, "Manufacturer",
232             manufacturer, "Model", model, "PartNumber", partNumber,
233             "SerialNumber", serialNumber, "SparePartNumber", sparePartNumber);
234 
235         if (!success)
236         {
237             messages::internalError(asyncResp->res);
238             return;
239         }
240 
241         if (manufacturer != nullptr)
242         {
243             asyncResp->res.jsonValue["Manufacturer"] = *manufacturer;
244         }
245         if (model != nullptr)
246         {
247             asyncResp->res.jsonValue["Model"] = *model;
248         }
249 
250         if (partNumber != nullptr)
251         {
252             asyncResp->res.jsonValue["PartNumber"] = *partNumber;
253         }
254 
255         if (serialNumber != nullptr)
256         {
257             asyncResp->res.jsonValue["SerialNumber"] = *serialNumber;
258         }
259 
260         if (sparePartNumber != nullptr && !sparePartNumber->empty())
261         {
262             asyncResp->res.jsonValue["SparePartNumber"] = *sparePartNumber;
263         }
264         });
265 }
266 
267 inline void addPCIeDeviceProperties(
268     crow::Response& resp, const std::string& pcieDeviceId,
269     const dbus::utility::DBusPropertiesMap& pcieDevProperties)
270 {
271     const std::string* deviceType = nullptr;
272     const std::string* generationInUse = nullptr;
273     const int64_t* lanesInUse = nullptr;
274 
275     const bool success = sdbusplus::unpackPropertiesNoThrow(
276         dbus_utils::UnpackErrorPrinter(), pcieDevProperties, "DeviceType",
277         deviceType, "GenerationInUse", generationInUse, "LanesInUse",
278         lanesInUse);
279 
280     if (!success)
281     {
282         messages::internalError(resp);
283         return;
284     }
285 
286     if (deviceType != nullptr && !deviceType->empty())
287     {
288         resp.jsonValue["PCIeInterface"]["DeviceType"] = *deviceType;
289     }
290 
291     if (generationInUse != nullptr)
292     {
293         std::optional<pcie_device::PCIeTypes> redfishGenerationInUse =
294             pcie_util::redfishPcieGenerationFromDbus(*generationInUse);
295 
296         if (!redfishGenerationInUse)
297         {
298             BMCWEB_LOG_WARNING << "Unknown PCIe Device Generation: "
299                                << *generationInUse;
300         }
301         else
302         {
303             if (*redfishGenerationInUse == pcie_device::PCIeTypes::Invalid)
304             {
305                 BMCWEB_LOG_ERROR << "Invalid PCIe Device Generation: "
306                                  << *generationInUse;
307                 messages::internalError(resp);
308                 return;
309             }
310             resp.jsonValue["PCIeInterface"]["PCIeType"] =
311                 *redfishGenerationInUse;
312         }
313     }
314 
315     // The default value of LanesInUse is 0, and the field will be
316     // left as off if it is a default value.
317     if (lanesInUse != nullptr && *lanesInUse != 0)
318     {
319         resp.jsonValue["PCIeInterface"]["LanesInUse"] = *lanesInUse;
320     }
321 
322     resp.jsonValue["PCIeFunctions"]["@odata.id"] = boost::urls::format(
323         "/redfish/v1/Systems/system/PCIeDevices/{}/PCIeFunctions",
324         pcieDeviceId);
325 }
326 
327 inline void getPCIeDeviceProperties(
328     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
329     const std::string& pcieDevicePath, const std::string& service,
330     const std::function<void(
331         const dbus::utility::DBusPropertiesMap& pcieDevProperties)>&& callback)
332 {
333     sdbusplus::asio::getAllProperties(
334         *crow::connections::systemBus, service, pcieDevicePath,
335         "xyz.openbmc_project.Inventory.Item.PCIeDevice",
336         [asyncResp,
337          callback](const boost::system::error_code& ec,
338                    const dbus::utility::DBusPropertiesMap& pcieDevProperties) {
339         if (ec)
340         {
341             if (ec.value() != EBADR)
342             {
343                 BMCWEB_LOG_ERROR << "DBUS response error for Properties";
344                 messages::internalError(asyncResp->res);
345             }
346             return;
347         }
348         callback(pcieDevProperties);
349         });
350 }
351 
352 inline void addPCIeDeviceCommonProperties(
353     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
354     const std::string& pcieDeviceId)
355 {
356     asyncResp->res.addHeader(
357         boost::beast::http::field::link,
358         "</redfish/v1/JsonSchemas/PCIeDevice/PCIeDevice.json>; rel=describedby");
359     asyncResp->res.jsonValue["@odata.type"] = "#PCIeDevice.v1_9_0.PCIeDevice";
360     asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
361         "/redfish/v1/Systems/system/PCIeDevices/{}", pcieDeviceId);
362     asyncResp->res.jsonValue["Name"] = "PCIe Device";
363     asyncResp->res.jsonValue["Id"] = pcieDeviceId;
364     asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
365     asyncResp->res.jsonValue["Status"]["Health"] = "OK";
366 }
367 
368 inline void
369     handlePCIeDeviceGet(App& app, const crow::Request& req,
370                         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
371                         const std::string& systemName,
372                         const std::string& pcieDeviceId)
373 {
374     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
375     {
376         return;
377     }
378     if constexpr (bmcwebEnableMultiHost)
379     {
380         // Option currently returns no systems.  TBD
381         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
382                                    systemName);
383         return;
384     }
385     if (systemName != "system")
386     {
387         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
388                                    systemName);
389         return;
390     }
391 
392     getValidPCIeDevicePath(
393         pcieDeviceId, asyncResp,
394         [asyncResp, pcieDeviceId](const std::string& pcieDevicePath,
395                                   const std::string& service) {
396         addPCIeDeviceCommonProperties(asyncResp, pcieDeviceId);
397         getPCIeDeviceAsset(asyncResp, pcieDevicePath, service);
398         getPCIeDeviceState(asyncResp, pcieDevicePath, service);
399         getPCIeDeviceHealth(asyncResp, pcieDevicePath, service);
400         getPCIeDeviceProperties(
401             asyncResp, pcieDevicePath, service,
402             [asyncResp, pcieDeviceId](
403                 const dbus::utility::DBusPropertiesMap& pcieDevProperties) {
404             addPCIeDeviceProperties(asyncResp->res, pcieDeviceId,
405                                     pcieDevProperties);
406             });
407         });
408 }
409 
410 inline void requestRoutesSystemPCIeDevice(App& app)
411 {
412     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/PCIeDevices/<str>/")
413         .privileges(redfish::privileges::getPCIeDevice)
414         .methods(boost::beast::http::verb::get)(
415             std::bind_front(handlePCIeDeviceGet, std::ref(app)));
416 }
417 
418 inline void addPCIeFunctionList(
419     crow::Response& res, const std::string& pcieDeviceId,
420     const dbus::utility::DBusPropertiesMap& pcieDevProperties)
421 {
422     nlohmann::json& pcieFunctionList = res.jsonValue["Members"];
423     pcieFunctionList = nlohmann::json::array();
424     static constexpr const int maxPciFunctionNum = 8;
425 
426     for (int functionNum = 0; functionNum < maxPciFunctionNum; functionNum++)
427     {
428         // Check if this function exists by
429         // looking for a device ID
430         std::string devIDProperty = "Function" + std::to_string(functionNum) +
431                                     "DeviceId";
432         const std::string* property = nullptr;
433         for (const auto& propEntry : pcieDevProperties)
434         {
435             if (propEntry.first == devIDProperty)
436             {
437                 property = std::get_if<std::string>(&propEntry.second);
438                 break;
439             }
440         }
441         if (property == nullptr || property->empty())
442         {
443             continue;
444         }
445 
446         nlohmann::json::object_t pcieFunction;
447         pcieFunction["@odata.id"] = boost::urls::format(
448             "/redfish/v1/Systems/system/PCIeDevices/{}/PCIeFunctions/{}",
449             pcieDeviceId, std::to_string(functionNum));
450         pcieFunctionList.emplace_back(std::move(pcieFunction));
451     }
452     res.jsonValue["PCIeFunctions@odata.count"] = pcieFunctionList.size();
453 }
454 
455 inline void handlePCIeFunctionCollectionGet(
456     App& app, const crow::Request& req,
457     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
458     const std::string& systemName, const std::string& pcieDeviceId)
459 {
460     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
461     {
462         return;
463     }
464     if constexpr (bmcwebEnableMultiHost)
465     {
466         // Option currently returns no systems.  TBD
467         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
468                                    systemName);
469         return;
470     }
471 
472     getValidPCIeDevicePath(
473         pcieDeviceId, asyncResp,
474         [asyncResp, pcieDeviceId](const std::string& pcieDevicePath,
475                                   const std::string& service) {
476         asyncResp->res.addHeader(
477             boost::beast::http::field::link,
478             "</redfish/v1/JsonSchemas/PCIeFunctionCollection/PCIeFunctionCollection.json>; rel=describedby");
479         asyncResp->res.jsonValue["@odata.type"] =
480             "#PCIeFunctionCollection.PCIeFunctionCollection";
481         asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
482             "/redfish/v1/Systems/system/PCIeDevices/{}/PCIeFunctions",
483             pcieDeviceId);
484         asyncResp->res.jsonValue["Name"] = "PCIe Function Collection";
485         asyncResp->res.jsonValue["Description"] =
486             "Collection of PCIe Functions for PCIe Device " + pcieDeviceId;
487         getPCIeDeviceProperties(
488             asyncResp, pcieDevicePath, service,
489             [asyncResp, pcieDeviceId](
490                 const dbus::utility::DBusPropertiesMap& pcieDevProperties) {
491             addPCIeFunctionList(asyncResp->res, pcieDeviceId,
492                                 pcieDevProperties);
493             });
494         });
495 }
496 
497 inline void requestRoutesSystemPCIeFunctionCollection(App& app)
498 {
499     /**
500      * Functions triggers appropriate requests on DBus
501      */
502     BMCWEB_ROUTE(app,
503                  "/redfish/v1/Systems/<str>/PCIeDevices/<str>/PCIeFunctions/")
504         .privileges(redfish::privileges::getPCIeFunctionCollection)
505         .methods(boost::beast::http::verb::get)(
506             std::bind_front(handlePCIeFunctionCollectionGet, std::ref(app)));
507 }
508 
509 inline bool validatePCIeFunctionId(
510     uint64_t pcieFunctionId,
511     const dbus::utility::DBusPropertiesMap& pcieDevProperties)
512 {
513     std::string functionName = "Function" + std::to_string(pcieFunctionId);
514     std::string devIDProperty = functionName + "DeviceId";
515 
516     const std::string* devIdProperty = nullptr;
517     for (const auto& property : pcieDevProperties)
518     {
519         if (property.first == devIDProperty)
520         {
521             devIdProperty = std::get_if<std::string>(&property.second);
522             break;
523         }
524     }
525     return (devIdProperty != nullptr && !devIdProperty->empty());
526 }
527 
528 inline void addPCIeFunctionProperties(
529     crow::Response& resp, uint64_t pcieFunctionId,
530     const dbus::utility::DBusPropertiesMap& pcieDevProperties)
531 {
532     std::string functionName = "Function" + std::to_string(pcieFunctionId);
533     for (const auto& property : pcieDevProperties)
534     {
535         const std::string* strProperty =
536             std::get_if<std::string>(&property.second);
537 
538         if (property.first == functionName + "DeviceId")
539         {
540             resp.jsonValue["DeviceId"] = *strProperty;
541         }
542         if (property.first == functionName + "VendorId")
543         {
544             resp.jsonValue["VendorId"] = *strProperty;
545         }
546         // TODO: FunctionType and DeviceClass are Redfish enums. The D-Bus
547         // property strings should be mapped correctly to ensure these
548         // strings are Redfish enum values. For now just check for empty.
549         if (property.first == functionName + "FunctionType")
550         {
551             if (!strProperty->empty())
552             {
553                 resp.jsonValue["FunctionType"] = *strProperty;
554             }
555         }
556         if (property.first == functionName + "DeviceClass")
557         {
558             if (!strProperty->empty())
559             {
560                 resp.jsonValue["DeviceClass"] = *strProperty;
561             }
562         }
563         if (property.first == functionName + "ClassCode")
564         {
565             resp.jsonValue["ClassCode"] = *strProperty;
566         }
567         if (property.first == functionName + "RevisionId")
568         {
569             resp.jsonValue["RevisionId"] = *strProperty;
570         }
571         if (property.first == functionName + "SubsystemId")
572         {
573             resp.jsonValue["SubsystemId"] = *strProperty;
574         }
575         if (property.first == functionName + "SubsystemVendorId")
576         {
577             resp.jsonValue["SubsystemVendorId"] = *strProperty;
578         }
579     }
580 }
581 
582 inline void addPCIeFunctionCommonProperties(crow::Response& resp,
583                                             const std::string& pcieDeviceId,
584                                             uint64_t pcieFunctionId)
585 {
586     resp.addHeader(
587         boost::beast::http::field::link,
588         "</redfish/v1/JsonSchemas/PCIeFunction/PCIeFunction.json>; rel=describedby");
589     resp.jsonValue["@odata.type"] = "#PCIeFunction.v1_2_3.PCIeFunction";
590     resp.jsonValue["@odata.id"] = boost::urls::format(
591         "/redfish/v1/Systems/system/PCIeDevices/{}/PCIeFunctions/{}",
592         pcieDeviceId, std::to_string(pcieFunctionId));
593     resp.jsonValue["Name"] = "PCIe Function";
594     resp.jsonValue["Id"] = std::to_string(pcieFunctionId);
595     resp.jsonValue["FunctionId"] = pcieFunctionId;
596     resp.jsonValue["Links"]["PCIeDevice"]["@odata.id"] = boost::urls::format(
597         "/redfish/v1/Systems/system/PCIeDevices/{}", pcieDeviceId);
598 }
599 
600 inline void
601     handlePCIeFunctionGet(App& app, const crow::Request& req,
602                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
603                           const std::string& systemName,
604                           const std::string& pcieDeviceId,
605                           const std::string& pcieFunctionIdStr)
606 {
607     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
608     {
609         return;
610     }
611     if constexpr (bmcwebEnableMultiHost)
612     {
613         // Option currently returns no systems.  TBD
614         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
615                                    systemName);
616         return;
617     }
618     if (systemName != "system")
619     {
620         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
621                                    systemName);
622         return;
623     }
624 
625     uint64_t pcieFunctionId = 0;
626     std::from_chars_result result = std::from_chars(
627         &*pcieFunctionIdStr.begin(), &*pcieFunctionIdStr.end(), pcieFunctionId);
628     if (result.ec != std::errc{} || result.ptr != &*pcieFunctionIdStr.end())
629     {
630         messages::resourceNotFound(asyncResp->res, "PCIeFunction",
631                                    pcieFunctionIdStr);
632         return;
633     }
634 
635     getValidPCIeDevicePath(pcieDeviceId, asyncResp,
636                            [asyncResp, pcieDeviceId,
637                             pcieFunctionId](const std::string& pcieDevicePath,
638                                             const std::string& service) {
639         getPCIeDeviceProperties(
640             asyncResp, pcieDevicePath, service,
641             [asyncResp, pcieDeviceId, pcieFunctionId](
642                 const dbus::utility::DBusPropertiesMap& pcieDevProperties) {
643             addPCIeFunctionCommonProperties(asyncResp->res, pcieDeviceId,
644                                             pcieFunctionId);
645             addPCIeFunctionProperties(asyncResp->res, pcieFunctionId,
646                                       pcieDevProperties);
647             });
648     });
649 }
650 
651 inline void requestRoutesSystemPCIeFunction(App& app)
652 {
653     BMCWEB_ROUTE(
654         app, "/redfish/v1/Systems/<str>/PCIeDevices/<str>/PCIeFunctions/<str>/")
655         .privileges(redfish::privileges::getPCIeFunction)
656         .methods(boost::beast::http::verb::get)(
657             std::bind_front(handlePCIeFunctionGet, std::ref(app)));
658 }
659 
660 } // namespace redfish
661