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