xref: /openbmc/bmcweb/redfish-core/lib/pcie.hpp (revision a5409991)
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 "query.hpp"
22 #include "registries/privilege_registry.hpp"
23 #include "utils/collection.hpp"
24 #include "utils/dbus_utils.hpp"
25 #include "utils/pcie_util.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 static constexpr std::array<std::string_view, 1> pcieSlotInterface = {
39     "xyz.openbmc_project.Inventory.Item.PCIeSlot"};
40 
41 static inline void handlePCIeDevicePath(
42     const std::string& pcieDeviceId,
43     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
44     const dbus::utility::MapperGetSubTreePathsResponse& pcieDevicePaths,
45     const std::function<void(const std::string& pcieDevicePath,
46                              const std::string& service)>& callback)
47 
48 {
49     for (const std::string& pcieDevicePath : pcieDevicePaths)
50     {
51         std::string pciecDeviceName =
52             sdbusplus::message::object_path(pcieDevicePath).filename();
53         if (pciecDeviceName.empty() || pciecDeviceName != pcieDeviceId)
54         {
55             continue;
56         }
57 
58         dbus::utility::getDbusObject(
59             pcieDevicePath, {},
60             [pcieDevicePath, asyncResp,
61              callback](const boost::system::error_code& ec,
62                        const dbus::utility::MapperGetObject& object) {
63             if (ec || object.empty())
64             {
65                 BMCWEB_LOG_ERROR << "DBUS response error " << ec;
66                 messages::internalError(asyncResp->res);
67                 return;
68             }
69             callback(pcieDevicePath, object.begin()->first);
70             });
71         return;
72     }
73 
74     BMCWEB_LOG_WARNING << "PCIe Device not found";
75     messages::resourceNotFound(asyncResp->res, "PCIeDevice", pcieDeviceId);
76 }
77 
78 static inline void getValidPCIeDevicePath(
79     const std::string& pcieDeviceId,
80     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
81     const std::function<void(const std::string& pcieDevicePath,
82                              const std::string& service)>& callback)
83 {
84     dbus::utility::getSubTreePaths(
85         inventoryPath, 0, pcieDeviceInterface,
86         [pcieDeviceId, asyncResp,
87          callback](const boost::system::error_code& ec,
88                    const dbus::utility::MapperGetSubTreePathsResponse&
89                        pcieDevicePaths) {
90         if (ec)
91         {
92             BMCWEB_LOG_ERROR << "D-Bus response error on GetSubTree " << ec;
93             messages::internalError(asyncResp->res);
94             return;
95         }
96         handlePCIeDevicePath(pcieDeviceId, asyncResp, pcieDevicePaths,
97                              callback);
98         return;
99         });
100 }
101 
102 static inline void handlePCIeDeviceCollectionGet(
103     crow::App& app, const crow::Request& req,
104     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
105     const std::string& systemName)
106 {
107     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
108     {
109         return;
110     }
111     if constexpr (bmcwebEnableMultiHost)
112     {
113         // Option currently returns no systems.  TBD
114         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
115                                    systemName);
116         return;
117     }
118     if (systemName != "system")
119     {
120         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
121                                    systemName);
122         return;
123     }
124 
125     asyncResp->res.addHeader(boost::beast::http::field::link,
126                              "</redfish/v1/JsonSchemas/PCIeDeviceCollection/"
127                              "PCIeDeviceCollection.json>; rel=describedby");
128     asyncResp->res.jsonValue["@odata.type"] =
129         "#PCIeDeviceCollection.PCIeDeviceCollection";
130     asyncResp->res.jsonValue["@odata.id"] =
131         "/redfish/v1/Systems/system/PCIeDevices";
132     asyncResp->res.jsonValue["Name"] = "PCIe Device Collection";
133     asyncResp->res.jsonValue["Description"] = "Collection of PCIe Devices";
134     asyncResp->res.jsonValue["Members"] = nlohmann::json::array();
135     asyncResp->res.jsonValue["Members@odata.count"] = 0;
136 
137     pcie_util::getPCIeDeviceList(asyncResp, "Members");
138 }
139 
140 inline void requestRoutesSystemPCIeDeviceCollection(App& app)
141 {
142     /**
143      * Functions triggers appropriate requests on DBus
144      */
145     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/PCIeDevices/")
146         .privileges(redfish::privileges::getPCIeDeviceCollection)
147         .methods(boost::beast::http::verb::get)(
148             std::bind_front(handlePCIeDeviceCollectionGet, std::ref(app)));
149 }
150 
151 inline void addPCIeSlotProperties(
152     crow::Response& res, const boost::system::error_code& ec,
153     const dbus::utility::DBusPropertiesMap& pcieSlotProperties)
154 {
155     if (ec)
156     {
157         BMCWEB_LOG_ERROR << "DBUS response error for getAllProperties"
158                          << ec.value();
159         messages::internalError(res);
160         return;
161     }
162     std::string generation;
163     size_t lanes = 0;
164     std::string slotType;
165 
166     bool success = sdbusplus::unpackPropertiesNoThrow(
167         dbus_utils::UnpackErrorPrinter(), pcieSlotProperties, "Generation",
168         generation, "Lanes", lanes, "SlotType", slotType);
169 
170     if (!success)
171     {
172         messages::internalError(res);
173         return;
174     }
175 
176     std::optional<pcie_device::PCIeTypes> pcieType =
177         pcie_util::redfishPcieGenerationFromDbus(generation);
178     if (!pcieType)
179     {
180         BMCWEB_LOG_WARNING << "Unknown PCIeType: " << generation;
181     }
182     else
183     {
184         if (*pcieType == pcie_device::PCIeTypes::Invalid)
185         {
186             BMCWEB_LOG_ERROR << "Invalid PCIeType: " << generation;
187             messages::internalError(res);
188             return;
189         }
190         res.jsonValue["Slot"]["PCIeType"] = *pcieType;
191     }
192 
193     res.jsonValue["Slot"]["Lanes"] = lanes;
194 
195     std::optional<pcie_slots::SlotTypes> redfishSlotType =
196         pcie_util::dbusSlotTypeToRf(slotType);
197     if (!redfishSlotType)
198     {
199         BMCWEB_LOG_WARNING << "Unknown PCIeSlot Type: " << slotType;
200     }
201     else
202     {
203         if (*redfishSlotType == pcie_slots::SlotTypes::Invalid)
204         {
205             BMCWEB_LOG_ERROR << "Invalid PCIeSlot type: " << slotType;
206             messages::internalError(res);
207             return;
208         }
209         res.jsonValue["Slot"]["SlotType"] = *redfishSlotType;
210     }
211 }
212 
213 inline void getPCIeDeviceSlotPath(
214     const std::string& pcieDevicePath,
215     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
216     std::function<void(const std::string& pcieDeviceSlot)>&& callback)
217 {
218     std::string associationPath = pcieDevicePath + "/contained_by";
219     dbus::utility::getAssociatedSubTreePaths(
220         associationPath, sdbusplus::message::object_path(inventoryPath), 0,
221         pcieSlotInterface,
222         [callback, asyncResp, pcieDevicePath](
223             const boost::system::error_code& ec,
224             const dbus::utility::MapperGetSubTreePathsResponse& endpoints) {
225         if (ec)
226         {
227             if (ec.value() == EBADR)
228             {
229                 // Missing association is not an error
230                 return;
231             }
232             BMCWEB_LOG_ERROR
233                 << "DBUS response error for getAssociatedSubTreePaths "
234                 << ec.value();
235             messages::internalError(asyncResp->res);
236             return;
237         }
238         if (endpoints.size() > 1)
239         {
240             BMCWEB_LOG_ERROR
241                 << "PCIeDevice is associated with more than one PCIeSlot: "
242                 << endpoints.size();
243             messages::internalError(asyncResp->res);
244             return;
245         }
246         if (endpoints.empty())
247         {
248             // If the device doesn't have an association, return without PCIe
249             // Slot properties
250             BMCWEB_LOG_DEBUG << "PCIeDevice is not associated with PCIeSlot";
251             return;
252         }
253         callback(endpoints[0]);
254         });
255 }
256 
257 inline void
258     afterGetDbusObject(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
259                        const std::string& pcieDeviceSlot,
260                        const boost::system::error_code& ec,
261                        const dbus::utility::MapperGetObject& object)
262 {
263     if (ec || object.empty())
264     {
265         BMCWEB_LOG_ERROR << "DBUS response error for getDbusObject "
266                          << ec.value();
267         messages::internalError(asyncResp->res);
268         return;
269     }
270     sdbusplus::asio::getAllProperties(
271         *crow::connections::systemBus, object.begin()->first, pcieDeviceSlot,
272         "xyz.openbmc_project.Inventory.Item.PCIeSlot",
273         [asyncResp](
274             const boost::system::error_code& ec2,
275             const dbus::utility::DBusPropertiesMap& pcieSlotProperties) {
276         addPCIeSlotProperties(asyncResp->res, ec2, pcieSlotProperties);
277         });
278 }
279 
280 inline void afterGetPCIeDeviceSlotPath(
281     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
282     const std::string& pcieDeviceSlot)
283 {
284     dbus::utility::getDbusObject(
285         pcieDeviceSlot, pcieSlotInterface,
286         [asyncResp,
287          pcieDeviceSlot](const boost::system::error_code& ec,
288                          const dbus::utility::MapperGetObject& object) {
289         afterGetDbusObject(asyncResp, pcieDeviceSlot, ec, object);
290         });
291 }
292 
293 inline void
294     getPCIeDeviceHealth(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
295                         const std::string& pcieDevicePath,
296                         const std::string& service)
297 {
298     sdbusplus::asio::getProperty<bool>(
299         *crow::connections::systemBus, service, pcieDevicePath,
300         "xyz.openbmc_project.State.Decorator.OperationalStatus", "Functional",
301         [asyncResp](const boost::system::error_code& ec, const bool value) {
302         if (ec)
303         {
304             if (ec.value() != EBADR)
305             {
306                 BMCWEB_LOG_ERROR << "DBUS response error for Health "
307                                  << ec.value();
308                 messages::internalError(asyncResp->res);
309             }
310             return;
311         }
312 
313         if (!value)
314         {
315             asyncResp->res.jsonValue["Status"]["Health"] = "Critical";
316         }
317         });
318 }
319 
320 inline void
321     getPCIeDeviceState(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
322                        const std::string& pcieDevicePath,
323                        const std::string& service)
324 {
325     sdbusplus::asio::getProperty<bool>(
326         *crow::connections::systemBus, service, pcieDevicePath,
327         "xyz.openbmc_project.Inventory.Item", "Present",
328         [asyncResp](const boost::system::error_code& ec, bool value) {
329         if (ec)
330         {
331             if (ec.value() != EBADR)
332             {
333                 BMCWEB_LOG_ERROR << "DBUS response error for State";
334                 messages::internalError(asyncResp->res);
335             }
336             return;
337         }
338 
339         if (!value)
340         {
341             asyncResp->res.jsonValue["Status"]["State"] = "Absent";
342         }
343         });
344 }
345 
346 inline void
347     getPCIeDeviceAsset(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
348                        const std::string& pcieDevicePath,
349                        const std::string& service)
350 {
351     sdbusplus::asio::getAllProperties(
352         *crow::connections::systemBus, service, pcieDevicePath,
353         "xyz.openbmc_project.Inventory.Decorator.Asset",
354         [pcieDevicePath, asyncResp{asyncResp}](
355             const boost::system::error_code& ec,
356             const dbus::utility::DBusPropertiesMap& assetList) {
357         if (ec)
358         {
359             if (ec.value() != EBADR)
360             {
361                 BMCWEB_LOG_ERROR << "DBUS response error for Properties"
362                                  << ec.value();
363                 messages::internalError(asyncResp->res);
364             }
365             return;
366         }
367 
368         const std::string* manufacturer = nullptr;
369         const std::string* model = nullptr;
370         const std::string* partNumber = nullptr;
371         const std::string* serialNumber = nullptr;
372         const std::string* sparePartNumber = nullptr;
373 
374         const bool success = sdbusplus::unpackPropertiesNoThrow(
375             dbus_utils::UnpackErrorPrinter(), assetList, "Manufacturer",
376             manufacturer, "Model", model, "PartNumber", partNumber,
377             "SerialNumber", serialNumber, "SparePartNumber", sparePartNumber);
378 
379         if (!success)
380         {
381             messages::internalError(asyncResp->res);
382             return;
383         }
384 
385         if (manufacturer != nullptr)
386         {
387             asyncResp->res.jsonValue["Manufacturer"] = *manufacturer;
388         }
389         if (model != nullptr)
390         {
391             asyncResp->res.jsonValue["Model"] = *model;
392         }
393 
394         if (partNumber != nullptr)
395         {
396             asyncResp->res.jsonValue["PartNumber"] = *partNumber;
397         }
398 
399         if (serialNumber != nullptr)
400         {
401             asyncResp->res.jsonValue["SerialNumber"] = *serialNumber;
402         }
403 
404         if (sparePartNumber != nullptr && !sparePartNumber->empty())
405         {
406             asyncResp->res.jsonValue["SparePartNumber"] = *sparePartNumber;
407         }
408         });
409 }
410 
411 inline void addPCIeDeviceProperties(
412     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
413     const std::string& pcieDeviceId,
414     const dbus::utility::DBusPropertiesMap& pcieDevProperties)
415 {
416     const std::string* deviceType = nullptr;
417     const std::string* generationInUse = nullptr;
418     const int64_t* lanesInUse = nullptr;
419 
420     const bool success = sdbusplus::unpackPropertiesNoThrow(
421         dbus_utils::UnpackErrorPrinter(), pcieDevProperties, "DeviceType",
422         deviceType, "GenerationInUse", generationInUse, "LanesInUse",
423         lanesInUse);
424 
425     if (!success)
426     {
427         messages::internalError(asyncResp->res);
428         return;
429     }
430 
431     if (deviceType != nullptr && !deviceType->empty())
432     {
433         asyncResp->res.jsonValue["PCIeInterface"]["DeviceType"] = *deviceType;
434     }
435 
436     if (generationInUse != nullptr)
437     {
438         std::optional<pcie_device::PCIeTypes> redfishGenerationInUse =
439             pcie_util::redfishPcieGenerationFromDbus(*generationInUse);
440 
441         if (!redfishGenerationInUse)
442         {
443             BMCWEB_LOG_WARNING << "Unknown PCIe Device Generation: "
444                                << *generationInUse;
445         }
446         else
447         {
448             if (*redfishGenerationInUse == pcie_device::PCIeTypes::Invalid)
449             {
450                 BMCWEB_LOG_ERROR << "Invalid PCIe Device Generation: "
451                                  << *generationInUse;
452                 messages::internalError(asyncResp->res);
453                 return;
454             }
455             asyncResp->res.jsonValue["PCIeInterface"]["PCIeType"] =
456                 *redfishGenerationInUse;
457         }
458     }
459 
460     // The default value of LanesInUse is 0, and the field will be
461     // left as off if it is a default value.
462     if (lanesInUse != nullptr && *lanesInUse != 0)
463     {
464         asyncResp->res.jsonValue["PCIeInterface"]["LanesInUse"] = *lanesInUse;
465     }
466 
467     asyncResp->res.jsonValue["PCIeFunctions"]["@odata.id"] =
468         boost::urls::format(
469             "/redfish/v1/Systems/system/PCIeDevices/{}/PCIeFunctions",
470             pcieDeviceId);
471 }
472 
473 inline void getPCIeDeviceProperties(
474     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
475     const std::string& pcieDevicePath, const std::string& service,
476     const std::function<void(
477         const dbus::utility::DBusPropertiesMap& pcieDevProperties)>&& callback)
478 {
479     sdbusplus::asio::getAllProperties(
480         *crow::connections::systemBus, service, pcieDevicePath,
481         "xyz.openbmc_project.Inventory.Item.PCIeDevice",
482         [asyncResp,
483          callback](const boost::system::error_code& ec,
484                    const dbus::utility::DBusPropertiesMap& pcieDevProperties) {
485         if (ec)
486         {
487             if (ec.value() != EBADR)
488             {
489                 BMCWEB_LOG_ERROR << "DBUS response error for Properties";
490                 messages::internalError(asyncResp->res);
491             }
492             return;
493         }
494         callback(pcieDevProperties);
495         });
496 }
497 
498 inline void addPCIeDeviceCommonProperties(
499     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
500     const std::string& pcieDeviceId)
501 {
502     asyncResp->res.addHeader(
503         boost::beast::http::field::link,
504         "</redfish/v1/JsonSchemas/PCIeDevice/PCIeDevice.json>; rel=describedby");
505     asyncResp->res.jsonValue["@odata.type"] = "#PCIeDevice.v1_9_0.PCIeDevice";
506     asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
507         "/redfish/v1/Systems/system/PCIeDevices/{}", pcieDeviceId);
508     asyncResp->res.jsonValue["Name"] = "PCIe Device";
509     asyncResp->res.jsonValue["Id"] = pcieDeviceId;
510     asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
511     asyncResp->res.jsonValue["Status"]["Health"] = "OK";
512 }
513 
514 inline void afterGetValidPcieDevicePath(
515     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
516     const std::string& pcieDeviceId, const std::string& pcieDevicePath,
517     const std::string& service)
518 {
519     addPCIeDeviceCommonProperties(asyncResp, pcieDeviceId);
520     getPCIeDeviceAsset(asyncResp, pcieDevicePath, service);
521     getPCIeDeviceState(asyncResp, pcieDevicePath, service);
522     getPCIeDeviceHealth(asyncResp, pcieDevicePath, service);
523     getPCIeDeviceProperties(
524         asyncResp, pcieDevicePath, service,
525         std::bind_front(addPCIeDeviceProperties, asyncResp, pcieDeviceId));
526     getPCIeDeviceSlotPath(
527         pcieDevicePath, asyncResp,
528         std::bind_front(afterGetPCIeDeviceSlotPath, asyncResp));
529 }
530 
531 inline void
532     handlePCIeDeviceGet(App& app, const crow::Request& req,
533                         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
534                         const std::string& systemName,
535                         const std::string& pcieDeviceId)
536 {
537     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
538     {
539         return;
540     }
541     if constexpr (bmcwebEnableMultiHost)
542     {
543         // Option currently returns no systems.  TBD
544         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
545                                    systemName);
546         return;
547     }
548     if (systemName != "system")
549     {
550         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
551                                    systemName);
552         return;
553     }
554 
555     getValidPCIeDevicePath(
556         pcieDeviceId, asyncResp,
557         std::bind_front(afterGetValidPcieDevicePath, asyncResp, pcieDeviceId));
558 }
559 
560 inline void requestRoutesSystemPCIeDevice(App& app)
561 {
562     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/PCIeDevices/<str>/")
563         .privileges(redfish::privileges::getPCIeDevice)
564         .methods(boost::beast::http::verb::get)(
565             std::bind_front(handlePCIeDeviceGet, std::ref(app)));
566 }
567 
568 inline void addPCIeFunctionList(
569     crow::Response& res, const std::string& pcieDeviceId,
570     const dbus::utility::DBusPropertiesMap& pcieDevProperties)
571 {
572     nlohmann::json& pcieFunctionList = res.jsonValue["Members"];
573     pcieFunctionList = nlohmann::json::array();
574     static constexpr const int maxPciFunctionNum = 8;
575 
576     for (int functionNum = 0; functionNum < maxPciFunctionNum; functionNum++)
577     {
578         // Check if this function exists by
579         // looking for a device ID
580         std::string devIDProperty = "Function" + std::to_string(functionNum) +
581                                     "DeviceId";
582         const std::string* property = nullptr;
583         for (const auto& propEntry : pcieDevProperties)
584         {
585             if (propEntry.first == devIDProperty)
586             {
587                 property = std::get_if<std::string>(&propEntry.second);
588                 break;
589             }
590         }
591         if (property == nullptr || property->empty())
592         {
593             continue;
594         }
595 
596         nlohmann::json::object_t pcieFunction;
597         pcieFunction["@odata.id"] = boost::urls::format(
598             "/redfish/v1/Systems/system/PCIeDevices/{}/PCIeFunctions/{}",
599             pcieDeviceId, std::to_string(functionNum));
600         pcieFunctionList.emplace_back(std::move(pcieFunction));
601     }
602     res.jsonValue["PCIeFunctions@odata.count"] = pcieFunctionList.size();
603 }
604 
605 inline void handlePCIeFunctionCollectionGet(
606     App& app, const crow::Request& req,
607     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
608     const std::string& systemName, const std::string& pcieDeviceId)
609 {
610     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
611     {
612         return;
613     }
614     if constexpr (bmcwebEnableMultiHost)
615     {
616         // Option currently returns no systems.  TBD
617         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
618                                    systemName);
619         return;
620     }
621 
622     getValidPCIeDevicePath(
623         pcieDeviceId, asyncResp,
624         [asyncResp, pcieDeviceId](const std::string& pcieDevicePath,
625                                   const std::string& service) {
626         asyncResp->res.addHeader(
627             boost::beast::http::field::link,
628             "</redfish/v1/JsonSchemas/PCIeFunctionCollection/PCIeFunctionCollection.json>; rel=describedby");
629         asyncResp->res.jsonValue["@odata.type"] =
630             "#PCIeFunctionCollection.PCIeFunctionCollection";
631         asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
632             "/redfish/v1/Systems/system/PCIeDevices/{}/PCIeFunctions",
633             pcieDeviceId);
634         asyncResp->res.jsonValue["Name"] = "PCIe Function Collection";
635         asyncResp->res.jsonValue["Description"] =
636             "Collection of PCIe Functions for PCIe Device " + pcieDeviceId;
637         getPCIeDeviceProperties(
638             asyncResp, pcieDevicePath, service,
639             [asyncResp, pcieDeviceId](
640                 const dbus::utility::DBusPropertiesMap& pcieDevProperties) {
641             addPCIeFunctionList(asyncResp->res, pcieDeviceId,
642                                 pcieDevProperties);
643             });
644         });
645 }
646 
647 inline void requestRoutesSystemPCIeFunctionCollection(App& app)
648 {
649     /**
650      * Functions triggers appropriate requests on DBus
651      */
652     BMCWEB_ROUTE(app,
653                  "/redfish/v1/Systems/<str>/PCIeDevices/<str>/PCIeFunctions/")
654         .privileges(redfish::privileges::getPCIeFunctionCollection)
655         .methods(boost::beast::http::verb::get)(
656             std::bind_front(handlePCIeFunctionCollectionGet, std::ref(app)));
657 }
658 
659 inline bool validatePCIeFunctionId(
660     uint64_t pcieFunctionId,
661     const dbus::utility::DBusPropertiesMap& pcieDevProperties)
662 {
663     std::string functionName = "Function" + std::to_string(pcieFunctionId);
664     std::string devIDProperty = functionName + "DeviceId";
665 
666     const std::string* devIdProperty = nullptr;
667     for (const auto& property : pcieDevProperties)
668     {
669         if (property.first == devIDProperty)
670         {
671             devIdProperty = std::get_if<std::string>(&property.second);
672             break;
673         }
674     }
675     return (devIdProperty != nullptr && !devIdProperty->empty());
676 }
677 
678 inline void addPCIeFunctionProperties(
679     crow::Response& resp, uint64_t pcieFunctionId,
680     const dbus::utility::DBusPropertiesMap& pcieDevProperties)
681 {
682     std::string functionName = "Function" + std::to_string(pcieFunctionId);
683     for (const auto& property : pcieDevProperties)
684     {
685         const std::string* strProperty =
686             std::get_if<std::string>(&property.second);
687 
688         if (property.first == functionName + "DeviceId")
689         {
690             resp.jsonValue["DeviceId"] = *strProperty;
691         }
692         if (property.first == functionName + "VendorId")
693         {
694             resp.jsonValue["VendorId"] = *strProperty;
695         }
696         // TODO: FunctionType and DeviceClass are Redfish enums. The D-Bus
697         // property strings should be mapped correctly to ensure these
698         // strings are Redfish enum values. For now just check for empty.
699         if (property.first == functionName + "FunctionType")
700         {
701             if (!strProperty->empty())
702             {
703                 resp.jsonValue["FunctionType"] = *strProperty;
704             }
705         }
706         if (property.first == functionName + "DeviceClass")
707         {
708             if (!strProperty->empty())
709             {
710                 resp.jsonValue["DeviceClass"] = *strProperty;
711             }
712         }
713         if (property.first == functionName + "ClassCode")
714         {
715             resp.jsonValue["ClassCode"] = *strProperty;
716         }
717         if (property.first == functionName + "RevisionId")
718         {
719             resp.jsonValue["RevisionId"] = *strProperty;
720         }
721         if (property.first == functionName + "SubsystemId")
722         {
723             resp.jsonValue["SubsystemId"] = *strProperty;
724         }
725         if (property.first == functionName + "SubsystemVendorId")
726         {
727             resp.jsonValue["SubsystemVendorId"] = *strProperty;
728         }
729     }
730 }
731 
732 inline void addPCIeFunctionCommonProperties(crow::Response& resp,
733                                             const std::string& pcieDeviceId,
734                                             uint64_t pcieFunctionId)
735 {
736     resp.addHeader(
737         boost::beast::http::field::link,
738         "</redfish/v1/JsonSchemas/PCIeFunction/PCIeFunction.json>; rel=describedby");
739     resp.jsonValue["@odata.type"] = "#PCIeFunction.v1_2_3.PCIeFunction";
740     resp.jsonValue["@odata.id"] = boost::urls::format(
741         "/redfish/v1/Systems/system/PCIeDevices/{}/PCIeFunctions/{}",
742         pcieDeviceId, std::to_string(pcieFunctionId));
743     resp.jsonValue["Name"] = "PCIe Function";
744     resp.jsonValue["Id"] = std::to_string(pcieFunctionId);
745     resp.jsonValue["FunctionId"] = pcieFunctionId;
746     resp.jsonValue["Links"]["PCIeDevice"]["@odata.id"] = boost::urls::format(
747         "/redfish/v1/Systems/system/PCIeDevices/{}", pcieDeviceId);
748 }
749 
750 inline void
751     handlePCIeFunctionGet(App& app, const crow::Request& req,
752                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
753                           const std::string& systemName,
754                           const std::string& pcieDeviceId,
755                           const std::string& pcieFunctionIdStr)
756 {
757     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
758     {
759         return;
760     }
761     if constexpr (bmcwebEnableMultiHost)
762     {
763         // Option currently returns no systems.  TBD
764         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
765                                    systemName);
766         return;
767     }
768     if (systemName != "system")
769     {
770         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
771                                    systemName);
772         return;
773     }
774 
775     uint64_t pcieFunctionId = 0;
776     std::from_chars_result result = std::from_chars(
777         &*pcieFunctionIdStr.begin(), &*pcieFunctionIdStr.end(), pcieFunctionId);
778     if (result.ec != std::errc{} || result.ptr != &*pcieFunctionIdStr.end())
779     {
780         messages::resourceNotFound(asyncResp->res, "PCIeFunction",
781                                    pcieFunctionIdStr);
782         return;
783     }
784 
785     getValidPCIeDevicePath(pcieDeviceId, asyncResp,
786                            [asyncResp, pcieDeviceId,
787                             pcieFunctionId](const std::string& pcieDevicePath,
788                                             const std::string& service) {
789         getPCIeDeviceProperties(
790             asyncResp, pcieDevicePath, service,
791             [asyncResp, pcieDeviceId, pcieFunctionId](
792                 const dbus::utility::DBusPropertiesMap& pcieDevProperties) {
793             addPCIeFunctionCommonProperties(asyncResp->res, pcieDeviceId,
794                                             pcieFunctionId);
795             addPCIeFunctionProperties(asyncResp->res, pcieFunctionId,
796                                       pcieDevProperties);
797             });
798     });
799 }
800 
801 inline void requestRoutesSystemPCIeFunction(App& app)
802 {
803     BMCWEB_ROUTE(
804         app, "/redfish/v1/Systems/<str>/PCIeDevices/<str>/PCIeFunctions/<str>/")
805         .privileges(redfish::privileges::getPCIeFunction)
806         .methods(boost::beast::http::verb::get)(
807             std::bind_front(handlePCIeFunctionGet, std::ref(app)));
808 }
809 
810 } // namespace redfish
811