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