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