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