xref: /openbmc/bmcweb/features/redfish/lib/pcie.hpp (revision bad2c4a9a1095e5e41959bfc5bc56102bbd2a79a)
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* deviceType = nullptr;
289     const std::string* generationInUse = nullptr;
290     const int64_t* lanesInUse = nullptr;
291 
292     const bool success = sdbusplus::unpackPropertiesNoThrow(
293         dbus_utils::UnpackErrorPrinter(), pcieDevProperties, "DeviceType",
294         deviceType, "GenerationInUse", generationInUse, "LanesInUse",
295         lanesInUse);
296 
297     if (!success)
298     {
299         messages::internalError(resp);
300         return;
301     }
302 
303     if (deviceType != nullptr && !deviceType->empty())
304     {
305         resp.jsonValue["PCIeInterface"]["DeviceType"] = *deviceType;
306     }
307 
308     if (generationInUse != nullptr)
309     {
310         std::optional<pcie_device::PCIeTypes> redfishGenerationInUse =
311             redfishPcieGenerationFromDbus(*generationInUse);
312 
313         if (!redfishGenerationInUse)
314         {
315             messages::internalError(resp);
316             return;
317         }
318         if (*redfishGenerationInUse != pcie_device::PCIeTypes::Invalid)
319         {
320             resp.jsonValue["PCIeInterface"]["PCIeType"] =
321                 *redfishGenerationInUse;
322         }
323     }
324 
325     // The default value of LanesInUse is 0, and the field will be
326     // left as off if it is a default value.
327     if (lanesInUse != nullptr && *lanesInUse != 0)
328     {
329         resp.jsonValue["PCIeInterface"]["LanesInUse"] = *lanesInUse;
330     }
331 
332     resp.jsonValue["PCIeFunctions"]["@odata.id"] = crow::utility::urlFromPieces(
333         "redfish", "v1", "Systems", "system", "PCIeDevices", pcieDeviceId,
334         "PCIeFunctions");
335 }
336 
337 inline void getPCIeDeviceProperties(
338     const std::shared_ptr<bmcweb::AsyncResp>& aResp,
339     const std::string& pcieDevicePath, const std::string& service,
340     const std::function<void(
341         const dbus::utility::DBusPropertiesMap& pcieDevProperties)>&& callback)
342 {
343     sdbusplus::asio::getAllProperties(
344         *crow::connections::systemBus, service, pcieDevicePath,
345         "xyz.openbmc_project.Inventory.Item.PCIeDevice",
346         [aResp,
347          callback](const boost::system::error_code& ec,
348                    const dbus::utility::DBusPropertiesMap& pcieDevProperties) {
349         if (ec)
350         {
351             if (ec.value() != EBADR)
352             {
353                 BMCWEB_LOG_ERROR << "DBUS response error for Properties";
354                 messages::internalError(aResp->res);
355             }
356             return;
357         }
358         callback(pcieDevProperties);
359         });
360 }
361 
362 inline void addPCIeDeviceCommonProperties(
363     const std::shared_ptr<bmcweb::AsyncResp>& aResp,
364     const std::string& pcieDeviceId)
365 {
366     aResp->res.addHeader(
367         boost::beast::http::field::link,
368         "</redfish/v1/JsonSchemas/PCIeDevice/PCIeDevice.json>; rel=describedby");
369     aResp->res.jsonValue["@odata.type"] = "#PCIeDevice.v1_9_0.PCIeDevice";
370     aResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces(
371         "redfish", "v1", "Systems", "system", "PCIeDevices", pcieDeviceId);
372     aResp->res.jsonValue["Name"] = "PCIe Device";
373     aResp->res.jsonValue["Id"] = pcieDeviceId;
374 }
375 
376 inline void handlePCIeDeviceGet(App& app, const crow::Request& req,
377                                 const std::shared_ptr<bmcweb::AsyncResp>& aResp,
378                                 const std::string& systemName,
379                                 const std::string& pcieDeviceId)
380 {
381     if (!redfish::setUpRedfishRoute(app, req, aResp))
382     {
383         return;
384     }
385     if (systemName != "system")
386     {
387         messages::resourceNotFound(aResp->res, "ComputerSystem", systemName);
388         return;
389     }
390 
391     getValidPCIeDevicePath(
392         pcieDeviceId, aResp,
393         [aResp, pcieDeviceId](const std::string& pcieDevicePath,
394                               const std::string& service) {
395         addPCIeDeviceCommonProperties(aResp, pcieDeviceId);
396         getPCIeDeviceAsset(aResp, pcieDevicePath, service);
397         getPCIeDeviceProperties(
398             aResp, pcieDevicePath, service,
399             [aResp, pcieDeviceId](
400                 const dbus::utility::DBusPropertiesMap& pcieDevProperties) {
401             addPCIeDeviceProperties(aResp->res, pcieDeviceId,
402                                     pcieDevProperties);
403             });
404         });
405 }
406 
407 inline void requestRoutesSystemPCIeDevice(App& app)
408 {
409     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/PCIeDevices/<str>/")
410         .privileges(redfish::privileges::getPCIeDevice)
411         .methods(boost::beast::http::verb::get)(
412             std::bind_front(handlePCIeDeviceGet, std::ref(app)));
413 }
414 
415 inline void addPCIeFunctionList(
416     crow::Response& res, const std::string& pcieDeviceId,
417     const dbus::utility::DBusPropertiesMap& pcieDevProperties)
418 {
419     nlohmann::json& pcieFunctionList = res.jsonValue["Members"];
420     pcieFunctionList = nlohmann::json::array();
421     static constexpr const int maxPciFunctionNum = 8;
422 
423     for (int functionNum = 0; functionNum < maxPciFunctionNum; functionNum++)
424     {
425         // Check if this function exists by
426         // looking for a device ID
427         std::string devIDProperty = "Function" + std::to_string(functionNum) +
428                                     "DeviceId";
429         const std::string* property = nullptr;
430         for (const auto& propEntry : pcieDevProperties)
431         {
432             if (propEntry.first == devIDProperty)
433             {
434                 property = std::get_if<std::string>(&propEntry.second);
435                 break;
436             }
437         }
438         if (property == nullptr || property->empty())
439         {
440             continue;
441         }
442 
443         nlohmann::json::object_t pcieFunction;
444         pcieFunction["@odata.id"] = crow::utility::urlFromPieces(
445             "redfish", "v1", "Systems", "system", "PCIeDevices", pcieDeviceId,
446             "PCIeFunctions", std::to_string(functionNum));
447         pcieFunctionList.emplace_back(std::move(pcieFunction));
448     }
449     res.jsonValue["PCIeFunctions@odata.count"] = pcieFunctionList.size();
450 }
451 
452 inline void handlePCIeFunctionCollectionGet(
453     App& app, const crow::Request& req,
454     const std::shared_ptr<bmcweb::AsyncResp>& aResp,
455     const std::string& pcieDeviceId)
456 {
457     if (!redfish::setUpRedfishRoute(app, req, aResp))
458     {
459         return;
460     }
461 
462     getValidPCIeDevicePath(
463         pcieDeviceId, aResp,
464         [aResp, pcieDeviceId](const std::string& pcieDevicePath,
465                               const std::string& service) {
466         aResp->res.addHeader(
467             boost::beast::http::field::link,
468             "</redfish/v1/JsonSchemas/PCIeFunctionCollection/PCIeFunctionCollection.json>; rel=describedby");
469         aResp->res.jsonValue["@odata.type"] =
470             "#PCIeFunctionCollection.PCIeFunctionCollection";
471         aResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces(
472             "redfish", "v1", "Systems", "system", "PCIeDevices", pcieDeviceId,
473             "PCIeFunctions");
474         aResp->res.jsonValue["Name"] = "PCIe Function Collection";
475         aResp->res.jsonValue["Description"] =
476             "Collection of PCIe Functions for PCIe Device " + pcieDeviceId;
477         getPCIeDeviceProperties(
478             aResp, pcieDevicePath, service,
479             [aResp, pcieDeviceId](
480                 const dbus::utility::DBusPropertiesMap& pcieDevProperties) {
481             addPCIeFunctionList(aResp->res, pcieDeviceId, pcieDevProperties);
482             });
483         });
484 }
485 
486 inline void requestRoutesSystemPCIeFunctionCollection(App& app)
487 {
488     /**
489      * Functions triggers appropriate requests on DBus
490      */
491     BMCWEB_ROUTE(app,
492                  "/redfish/v1/Systems/system/PCIeDevices/<str>/PCIeFunctions/")
493         .privileges(redfish::privileges::getPCIeFunctionCollection)
494         .methods(boost::beast::http::verb::get)(
495             std::bind_front(handlePCIeFunctionCollectionGet, std::ref(app)));
496 }
497 
498 inline bool validatePCIeFunctionId(
499     const std::string& pcieFunctionId,
500     const dbus::utility::DBusPropertiesMap& pcieDevProperties)
501 {
502     std::string functionName = "Function" + pcieFunctionId;
503     std::string devIDProperty = functionName + "DeviceId";
504 
505     const std::string* devIdProperty = nullptr;
506     for (const auto& property : pcieDevProperties)
507     {
508         if (property.first == devIDProperty)
509         {
510             devIdProperty = std::get_if<std::string>(&property.second);
511             break;
512         }
513     }
514     return (devIdProperty != nullptr && !devIdProperty->empty());
515 }
516 
517 inline void addPCIeFunctionProperties(
518     crow::Response& resp, const std::string& pcieFunctionId,
519     const dbus::utility::DBusPropertiesMap& pcieDevProperties)
520 {
521     std::string functionName = "Function" + pcieFunctionId;
522     if (!validatePCIeFunctionId(pcieFunctionId, pcieDevProperties))
523     {
524         messages::resourceNotFound(resp, "PCIeFunction", pcieFunctionId);
525         return;
526     }
527     for (const auto& property : pcieDevProperties)
528     {
529         const std::string* strProperty =
530             std::get_if<std::string>(&property.second);
531 
532         if (property.first == functionName + "DeviceId")
533         {
534             resp.jsonValue["DeviceId"] = *strProperty;
535         }
536         if (property.first == functionName + "VendorId")
537         {
538             resp.jsonValue["VendorId"] = *strProperty;
539         }
540         // TODO: FunctionType and DeviceClass are Redfish enums. The D-Bus
541         // property strings should be mapped correctly to ensure these
542         // strings are Redfish enum values. For now just check for empty.
543         if (property.first == functionName + "FunctionType")
544         {
545             if (!strProperty->empty())
546             {
547                 resp.jsonValue["FunctionType"] = *strProperty;
548             }
549         }
550         if (property.first == functionName + "DeviceClass")
551         {
552             if (!strProperty->empty())
553             {
554                 resp.jsonValue["DeviceClass"] = *strProperty;
555             }
556         }
557         if (property.first == functionName + "ClassCode")
558         {
559             resp.jsonValue["ClassCode"] = *strProperty;
560         }
561         if (property.first == functionName + "RevisionId")
562         {
563             resp.jsonValue["RevisionId"] = *strProperty;
564         }
565         if (property.first == functionName + "SubsystemId")
566         {
567             resp.jsonValue["SubsystemId"] = *strProperty;
568         }
569         if (property.first == functionName + "SubsystemVendorId")
570         {
571             resp.jsonValue["SubsystemVendorId"] = *strProperty;
572         }
573     }
574 }
575 
576 inline void addPCIeFunctionCommonProperties(crow::Response& resp,
577                                             const std::string& pcieDeviceId,
578                                             const std::string& pcieFunctionId)
579 {
580     resp.addHeader(
581         boost::beast::http::field::link,
582         "</redfish/v1/JsonSchemas/PCIeFunction/PCIeFunction.json>; rel=describedby");
583     resp.jsonValue["@odata.type"] = "#PCIeFunction.v1_2_3.PCIeFunction";
584     resp.jsonValue["@odata.id"] = crow::utility::urlFromPieces(
585         "redfish", "v1", "Systems", "system", "PCIeDevices", pcieDeviceId,
586         "PCIeFunctions", pcieFunctionId);
587     resp.jsonValue["Name"] = "PCIe Function";
588     resp.jsonValue["Id"] = pcieFunctionId;
589     resp.jsonValue["FunctionId"] = std::stoi(pcieFunctionId);
590     resp.jsonValue["Links"]["PCIeDevice"]["@odata.id"] =
591         crow::utility::urlFromPieces("redfish", "v1", "Systems", "system",
592                                      "PCIeDevices", pcieDeviceId);
593 }
594 
595 inline void
596     handlePCIeFunctionGet(App& app, const crow::Request& req,
597                           const std::shared_ptr<bmcweb::AsyncResp>& aResp,
598                           const std::string& pcieDeviceId,
599                           const std::string& pcieFunctionId)
600 {
601     if (!redfish::setUpRedfishRoute(app, req, aResp))
602     {
603         return;
604     }
605 
606     getValidPCIeDevicePath(
607         pcieDeviceId, aResp,
608         [aResp, pcieDeviceId, pcieFunctionId](const std::string& pcieDevicePath,
609                                               const std::string& service) {
610         getPCIeDeviceProperties(
611             aResp, pcieDevicePath, service,
612             [aResp, pcieDeviceId, pcieFunctionId](
613                 const dbus::utility::DBusPropertiesMap& pcieDevProperties) {
614             addPCIeFunctionCommonProperties(aResp->res, pcieDeviceId,
615                                             pcieFunctionId);
616             addPCIeFunctionProperties(aResp->res, pcieFunctionId,
617                                       pcieDevProperties);
618             });
619         });
620 }
621 
622 inline void requestRoutesSystemPCIeFunction(App& app)
623 {
624     BMCWEB_ROUTE(
625         app,
626         "/redfish/v1/Systems/system/PCIeDevices/<str>/PCIeFunctions/<str>/")
627         .privileges(redfish::privileges::getPCIeFunction)
628         .methods(boost::beast::http::verb::get)(
629             std::bind_front(handlePCIeFunctionGet, std::ref(app)));
630 }
631 
632 } // namespace redfish
633