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