xref: /openbmc/bmcweb/redfish-core/lib/pcie.hpp (revision b84e729d)
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 getPCIeDeviceAsset(const std::shared_ptr<bmcweb::AsyncResp>& aResp,
221                                const std::string& pcieDevicePath,
222                                const std::string& service)
223 {
224     sdbusplus::asio::getAllProperties(
225         *crow::connections::systemBus, service, pcieDevicePath,
226         "xyz.openbmc_project.Inventory.Decorator.Asset",
227         [pcieDevicePath,
228          aResp{aResp}](const boost::system::error_code& ec,
229                        const dbus::utility::DBusPropertiesMap& assetList) {
230         if (ec)
231         {
232             if (ec.value() != EBADR)
233             {
234                 BMCWEB_LOG_ERROR << "DBUS response error for Properties"
235                                  << ec.value();
236                 messages::internalError(aResp->res);
237             }
238             return;
239         }
240 
241         const std::string* manufacturer = nullptr;
242         const std::string* model = nullptr;
243         const std::string* partNumber = nullptr;
244         const std::string* serialNumber = nullptr;
245         const std::string* sparePartNumber = nullptr;
246 
247         const bool success = sdbusplus::unpackPropertiesNoThrow(
248             dbus_utils::UnpackErrorPrinter(), assetList, "Manufacturer",
249             manufacturer, "Model", model, "PartNumber", partNumber,
250             "SerialNumber", serialNumber, "SparePartNumber", sparePartNumber);
251 
252         if (!success)
253         {
254             messages::internalError(aResp->res);
255             return;
256         }
257 
258         if (manufacturer != nullptr)
259         {
260             aResp->res.jsonValue["Manufacturer"] = *manufacturer;
261         }
262         if (model != nullptr)
263         {
264             aResp->res.jsonValue["Model"] = *model;
265         }
266 
267         if (partNumber != nullptr)
268         {
269             aResp->res.jsonValue["PartNumber"] = *partNumber;
270         }
271 
272         if (serialNumber != nullptr)
273         {
274             aResp->res.jsonValue["SerialNumber"] = *serialNumber;
275         }
276 
277         if (sparePartNumber != nullptr && !sparePartNumber->empty())
278         {
279             aResp->res.jsonValue["SparePartNumber"] = *sparePartNumber;
280         }
281         });
282 }
283 
284 inline void addPCIeDeviceProperties(
285     crow::Response& resp, const std::string& pcieDeviceId,
286     const dbus::utility::DBusPropertiesMap& pcieDevProperties)
287 {
288     const std::string* manufacturer = nullptr;
289     const std::string* deviceType = nullptr;
290     const std::string* generationInUse = nullptr;
291     const int64_t* lanesInUse = nullptr;
292 
293     const bool success = sdbusplus::unpackPropertiesNoThrow(
294         dbus_utils::UnpackErrorPrinter(), pcieDevProperties, "DeviceType",
295         deviceType, "GenerationInUse", generationInUse, "LanesInUse",
296         lanesInUse, "Manufacturer", manufacturer);
297 
298     if (!success)
299     {
300         messages::internalError(resp);
301         return;
302     }
303 
304     if (deviceType != nullptr && !deviceType->empty())
305     {
306         resp.jsonValue["PCIeInterface"]["DeviceType"] = *deviceType;
307     }
308 
309     if (generationInUse != nullptr)
310     {
311         std::optional<pcie_device::PCIeTypes> redfishGenerationInUse =
312             redfishPcieGenerationFromDbus(*generationInUse);
313 
314         if (!redfishGenerationInUse)
315         {
316             messages::internalError(resp);
317             return;
318         }
319         if (*redfishGenerationInUse != pcie_device::PCIeTypes::Invalid)
320         {
321             resp.jsonValue["PCIeInterface"]["PCIeType"] =
322                 *redfishGenerationInUse;
323         }
324     }
325 
326     // The default value of LanesInUse is 0, and the field will be
327     // left as off if it is a default value.
328     if (lanesInUse != nullptr && *lanesInUse != 0)
329     {
330         resp.jsonValue["PCIeInterface"]["LanesInUse"] = *lanesInUse;
331     }
332 
333     if (manufacturer != nullptr)
334     {
335         resp.jsonValue["PCIeInterface"]["Manufacturer"] = *manufacturer;
336     }
337 
338     resp.jsonValue["PCIeFunctions"]["@odata.id"] = crow::utility::urlFromPieces(
339         "redfish", "v1", "Systems", "system", "PCIeDevices", pcieDeviceId,
340         "PCIeFunctions");
341 }
342 
343 inline void getPCIeDeviceProperties(
344     const std::shared_ptr<bmcweb::AsyncResp>& aResp,
345     const std::string& pcieDevicePath, const std::string& service,
346     const std::function<void(
347         const dbus::utility::DBusPropertiesMap& pcieDevProperties)>&& callback)
348 {
349     sdbusplus::asio::getAllProperties(
350         *crow::connections::systemBus, service, pcieDevicePath,
351         "xyz.openbmc_project.Inventory.Item.PCIeDevice",
352         [aResp,
353          callback](const boost::system::error_code& ec,
354                    const dbus::utility::DBusPropertiesMap& pcieDevProperties) {
355         if (ec)
356         {
357             if (ec.value() != EBADR)
358             {
359                 BMCWEB_LOG_ERROR << "DBUS response error for Properties";
360                 messages::internalError(aResp->res);
361             }
362             return;
363         }
364         callback(pcieDevProperties);
365         });
366 }
367 
368 inline void addPCIeDeviceCommonProperties(
369     const std::shared_ptr<bmcweb::AsyncResp>& aResp,
370     const std::string& pcieDeviceId)
371 {
372     aResp->res.addHeader(
373         boost::beast::http::field::link,
374         "</redfish/v1/JsonSchemas/PCIeDevice/PCIeDevice.json>; rel=describedby");
375     aResp->res.jsonValue["@odata.type"] = "#PCIeDevice.v1_9_0.PCIeDevice";
376     aResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces(
377         "redfish", "v1", "Systems", "system", "PCIeDevices", pcieDeviceId);
378     aResp->res.jsonValue["Name"] = "PCIe Device";
379     aResp->res.jsonValue["Id"] = pcieDeviceId;
380 }
381 
382 inline void handlePCIeDeviceGet(App& app, const crow::Request& req,
383                                 const std::shared_ptr<bmcweb::AsyncResp>& aResp,
384                                 const std::string& systemName,
385                                 const std::string& pcieDeviceId)
386 {
387     if (!redfish::setUpRedfishRoute(app, req, aResp))
388     {
389         return;
390     }
391     if (systemName != "system")
392     {
393         messages::resourceNotFound(aResp->res, "ComputerSystem", systemName);
394         return;
395     }
396 
397     getValidPCIeDevicePath(
398         pcieDeviceId, aResp,
399         [aResp, pcieDeviceId](const std::string& pcieDevicePath,
400                               const std::string& service) {
401         addPCIeDeviceCommonProperties(aResp, pcieDeviceId);
402         getPCIeDeviceAsset(aResp, pcieDevicePath, service);
403         getPCIeDeviceProperties(
404             aResp, pcieDevicePath, service,
405             [aResp, pcieDeviceId](
406                 const dbus::utility::DBusPropertiesMap& pcieDevProperties) {
407             addPCIeDeviceProperties(aResp->res, pcieDeviceId,
408                                     pcieDevProperties);
409             });
410         });
411 }
412 
413 inline void requestRoutesSystemPCIeDevice(App& app)
414 {
415     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/PCIeDevices/<str>/")
416         .privileges(redfish::privileges::getPCIeDevice)
417         .methods(boost::beast::http::verb::get)(
418             std::bind_front(handlePCIeDeviceGet, std::ref(app)));
419 }
420 
421 inline void addPCIeFunctionList(
422     crow::Response& res, const std::string& pcieDeviceId,
423     const dbus::utility::DBusPropertiesMap& pcieDevProperties)
424 {
425     nlohmann::json& pcieFunctionList = res.jsonValue["Members"];
426     pcieFunctionList = nlohmann::json::array();
427     static constexpr const int maxPciFunctionNum = 8;
428 
429     for (int functionNum = 0; functionNum < maxPciFunctionNum; functionNum++)
430     {
431         // Check if this function exists by
432         // looking for a device ID
433         std::string devIDProperty = "Function" + std::to_string(functionNum) +
434                                     "DeviceId";
435         const std::string* property = nullptr;
436         for (const auto& propEntry : pcieDevProperties)
437         {
438             if (propEntry.first == devIDProperty)
439             {
440                 property = std::get_if<std::string>(&propEntry.second);
441                 break;
442             }
443         }
444         if (property == nullptr || property->empty())
445         {
446             continue;
447         }
448 
449         nlohmann::json::object_t pcieFunction;
450         pcieFunction["@odata.id"] = crow::utility::urlFromPieces(
451             "redfish", "v1", "Systems", "system", "PCIeDevices", pcieDeviceId,
452             "PCIeFunctions", std::to_string(functionNum));
453         pcieFunctionList.emplace_back(std::move(pcieFunction));
454     }
455     res.jsonValue["PCIeFunctions@odata.count"] = pcieFunctionList.size();
456 }
457 
458 inline void handlePCIeFunctionCollectionGet(
459     App& app, const crow::Request& req,
460     const std::shared_ptr<bmcweb::AsyncResp>& aResp,
461     const std::string& pcieDeviceId)
462 {
463     if (!redfish::setUpRedfishRoute(app, req, aResp))
464     {
465         return;
466     }
467 
468     getValidPCIeDevicePath(
469         pcieDeviceId, aResp,
470         [aResp, pcieDeviceId](const std::string& pcieDevicePath,
471                               const std::string& service) {
472         aResp->res.addHeader(
473             boost::beast::http::field::link,
474             "</redfish/v1/JsonSchemas/PCIeFunctionCollection/PCIeFunctionCollection.json>; rel=describedby");
475         aResp->res.jsonValue["@odata.type"] =
476             "#PCIeFunctionCollection.PCIeFunctionCollection";
477         aResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces(
478             "redfish", "v1", "Systems", "system", "PCIeDevices", pcieDeviceId,
479             "PCIeFunctions");
480         aResp->res.jsonValue["Name"] = "PCIe Function Collection";
481         aResp->res.jsonValue["Description"] =
482             "Collection of PCIe Functions for PCIe Device " + pcieDeviceId;
483         getPCIeDeviceProperties(
484             aResp, pcieDevicePath, service,
485             [aResp, pcieDeviceId](
486                 const dbus::utility::DBusPropertiesMap& pcieDevProperties) {
487             addPCIeFunctionList(aResp->res, pcieDeviceId, 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/system/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     const std::string& pcieFunctionId,
506     const dbus::utility::DBusPropertiesMap& pcieDevProperties)
507 {
508     std::string functionName = "Function" + 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, const std::string& pcieFunctionId,
525     const dbus::utility::DBusPropertiesMap& pcieDevProperties)
526 {
527     std::string functionName = "Function" + pcieFunctionId;
528     if (!validatePCIeFunctionId(pcieFunctionId, pcieDevProperties))
529     {
530         messages::resourceNotFound(resp, "PCIeFunction", pcieFunctionId);
531         return;
532     }
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                                             const std::string& 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"] = crow::utility::urlFromPieces(
591         "redfish", "v1", "Systems", "system", "PCIeDevices", pcieDeviceId,
592         "PCIeFunctions", pcieFunctionId);
593     resp.jsonValue["Name"] = "PCIe Function";
594     resp.jsonValue["Id"] = pcieFunctionId;
595     resp.jsonValue["FunctionId"] = std::stoi(pcieFunctionId);
596     resp.jsonValue["Links"]["PCIeDevice"]["@odata.id"] =
597         crow::utility::urlFromPieces("redfish", "v1", "Systems", "system",
598                                      "PCIeDevices", pcieDeviceId);
599 }
600 
601 inline void
602     handlePCIeFunctionGet(App& app, const crow::Request& req,
603                           const std::shared_ptr<bmcweb::AsyncResp>& aResp,
604                           const std::string& pcieDeviceId,
605                           const std::string& pcieFunctionId)
606 {
607     if (!redfish::setUpRedfishRoute(app, req, aResp))
608     {
609         return;
610     }
611 
612     getValidPCIeDevicePath(
613         pcieDeviceId, aResp,
614         [aResp, pcieDeviceId, pcieFunctionId](const std::string& pcieDevicePath,
615                                               const std::string& service) {
616         getPCIeDeviceProperties(
617             aResp, pcieDevicePath, service,
618             [aResp, pcieDeviceId, pcieFunctionId](
619                 const dbus::utility::DBusPropertiesMap& pcieDevProperties) {
620             addPCIeFunctionCommonProperties(aResp->res, pcieDeviceId,
621                                             pcieFunctionId);
622             addPCIeFunctionProperties(aResp->res, pcieFunctionId,
623                                       pcieDevProperties);
624             });
625         });
626 }
627 
628 inline void requestRoutesSystemPCIeFunction(App& app)
629 {
630     BMCWEB_ROUTE(
631         app,
632         "/redfish/v1/Systems/system/PCIeDevices/<str>/PCIeFunctions/<str>/")
633         .privileges(redfish::privileges::getPCIeFunction)
634         .methods(boost::beast::http::verb::get)(
635             std::bind_front(handlePCIeFunctionGet, std::ref(app)));
636 }
637 
638 } // namespace redfish
639