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