xref: /openbmc/bmcweb/redfish-core/lib/pcie.hpp (revision 6c58a03e)
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 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, pcieDeviceInterface,
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 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 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
254                 // PCIe 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 afterGetDbusObject(
263     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
264     const std::string& pcieDeviceSlot, const boost::system::error_code& ec,
265     const dbus::utility::MapperGetObject& object)
266 {
267     if (ec || object.empty())
268     {
269         BMCWEB_LOG_ERROR("DBUS response error for getDbusObject {}",
270                          ec.value());
271         messages::internalError(asyncResp->res);
272         return;
273     }
274     sdbusplus::asio::getAllProperties(
275         *crow::connections::systemBus, object.begin()->first, pcieDeviceSlot,
276         "xyz.openbmc_project.Inventory.Item.PCIeSlot",
277         [asyncResp](
278             const boost::system::error_code& ec2,
279             const dbus::utility::DBusPropertiesMap& pcieSlotProperties) {
280             addPCIeSlotProperties(asyncResp->res, ec2, pcieSlotProperties);
281         });
282 }
283 
284 inline void afterGetPCIeDeviceSlotPath(
285     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
286     const std::string& pcieDeviceSlot)
287 {
288     dbus::utility::getDbusObject(
289         pcieDeviceSlot, pcieSlotInterface,
290         [asyncResp,
291          pcieDeviceSlot](const boost::system::error_code& ec,
292                          const dbus::utility::MapperGetObject& object) {
293             afterGetDbusObject(asyncResp, pcieDeviceSlot, ec, object);
294         });
295 }
296 
297 inline void getPCIeDeviceHealth(
298     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
299     const std::string& pcieDevicePath, const std::string& service)
300 {
301     sdbusplus::asio::getProperty<bool>(
302         *crow::connections::systemBus, service, pcieDevicePath,
303         "xyz.openbmc_project.State.Decorator.OperationalStatus", "Functional",
304         [asyncResp](const boost::system::error_code& ec, const bool value) {
305             if (ec)
306             {
307                 if (ec.value() != EBADR)
308                 {
309                     BMCWEB_LOG_ERROR("DBUS response error for Health {}",
310                                      ec.value());
311                     messages::internalError(asyncResp->res);
312                 }
313                 return;
314             }
315 
316             if (!value)
317             {
318                 asyncResp->res.jsonValue["Status"]["Health"] =
319                     resource::Health::Critical;
320             }
321         });
322 }
323 
324 inline void getPCIeDeviceState(
325     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
326     const std::string& pcieDevicePath, const std::string& service)
327 {
328     sdbusplus::asio::getProperty<bool>(
329         *crow::connections::systemBus, service, pcieDevicePath,
330         "xyz.openbmc_project.Inventory.Item", "Present",
331         [asyncResp](const boost::system::error_code& ec, bool value) {
332             if (ec)
333             {
334                 if (ec.value() != EBADR)
335                 {
336                     BMCWEB_LOG_ERROR("DBUS response error for State");
337                     messages::internalError(asyncResp->res);
338                 }
339                 return;
340             }
341 
342             if (!value)
343             {
344                 asyncResp->res.jsonValue["Status"]["State"] =
345                     resource::State::Absent;
346             }
347         });
348 }
349 
350 inline void getPCIeDeviceAsset(
351     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
352     const std::string& pcieDevicePath, const std::string& service)
353 {
354     sdbusplus::asio::getAllProperties(
355         *crow::connections::systemBus, service, pcieDevicePath,
356         "xyz.openbmc_project.Inventory.Decorator.Asset",
357         [pcieDevicePath, asyncResp{asyncResp}](
358             const boost::system::error_code& ec,
359             const dbus::utility::DBusPropertiesMap& assetList) {
360             if (ec)
361             {
362                 if (ec.value() != EBADR)
363                 {
364                     BMCWEB_LOG_ERROR("DBUS response error for Properties{}",
365                                      ec.value());
366                     messages::internalError(asyncResp->res);
367                 }
368                 return;
369             }
370 
371             const std::string* manufacturer = nullptr;
372             const std::string* model = nullptr;
373             const std::string* partNumber = nullptr;
374             const std::string* serialNumber = nullptr;
375             const std::string* sparePartNumber = nullptr;
376 
377             const bool success = sdbusplus::unpackPropertiesNoThrow(
378                 dbus_utils::UnpackErrorPrinter(), assetList, "Manufacturer",
379                 manufacturer, "Model", model, "PartNumber", partNumber,
380                 "SerialNumber", serialNumber, "SparePartNumber",
381                 sparePartNumber);
382 
383             if (!success)
384             {
385                 messages::internalError(asyncResp->res);
386                 return;
387             }
388 
389             if (manufacturer != nullptr)
390             {
391                 asyncResp->res.jsonValue["Manufacturer"] = *manufacturer;
392             }
393             if (model != nullptr)
394             {
395                 asyncResp->res.jsonValue["Model"] = *model;
396             }
397 
398             if (partNumber != nullptr)
399             {
400                 asyncResp->res.jsonValue["PartNumber"] = *partNumber;
401             }
402 
403             if (serialNumber != nullptr)
404             {
405                 asyncResp->res.jsonValue["SerialNumber"] = *serialNumber;
406             }
407 
408             if (sparePartNumber != nullptr && !sparePartNumber->empty())
409             {
410                 asyncResp->res.jsonValue["SparePartNumber"] = *sparePartNumber;
411             }
412         });
413 }
414 
415 inline void addPCIeDeviceProperties(
416     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
417     const std::string& pcieDeviceId,
418     const dbus::utility::DBusPropertiesMap& pcieDevProperties)
419 {
420     const std::string* generationInUse = nullptr;
421     const std::string* generationSupported = nullptr;
422     const size_t* lanesInUse = nullptr;
423     const size_t* maxLanes = nullptr;
424 
425     const bool success = sdbusplus::unpackPropertiesNoThrow(
426         dbus_utils::UnpackErrorPrinter(), pcieDevProperties, "GenerationInUse",
427         generationInUse, "GenerationSupported", generationSupported,
428         "LanesInUse", lanesInUse, "MaxLanes", maxLanes);
429 
430     if (!success)
431     {
432         messages::internalError(asyncResp->res);
433         return;
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     if (generationSupported != nullptr)
461     {
462         std::optional<pcie_device::PCIeTypes> redfishGenerationSupported =
463             pcie_util::redfishPcieGenerationFromDbus(*generationSupported);
464 
465         if (!redfishGenerationSupported)
466         {
467             BMCWEB_LOG_WARNING("Unknown PCIe Device Generation: {}",
468                                *generationSupported);
469         }
470         else
471         {
472             if (*redfishGenerationSupported == pcie_device::PCIeTypes::Invalid)
473             {
474                 BMCWEB_LOG_ERROR("Invalid PCIe Device Generation: {}",
475                                  *generationSupported);
476                 messages::internalError(asyncResp->res);
477                 return;
478             }
479             asyncResp->res.jsonValue["PCIeInterface"]["MaxPCIeType"] =
480                 *redfishGenerationSupported;
481         }
482     }
483 
484     if (lanesInUse != nullptr)
485     {
486         if (*lanesInUse == std::numeric_limits<size_t>::max())
487         {
488             // The default value of LanesInUse is "maxint", and the field will
489             // be null if it is a default value.
490             asyncResp->res.jsonValue["PCIeInterface"]["LanesInUse"] = nullptr;
491         }
492         else
493         {
494             asyncResp->res.jsonValue["PCIeInterface"]["LanesInUse"] =
495                 *lanesInUse;
496         }
497     }
498     // The default value of MaxLanes is 0, and the field will be
499     // left as off if it is a default value.
500     if (maxLanes != nullptr && *maxLanes != 0)
501     {
502         asyncResp->res.jsonValue["PCIeInterface"]["MaxLanes"] = *maxLanes;
503     }
504 
505     asyncResp->res.jsonValue["PCIeFunctions"]["@odata.id"] =
506         boost::urls::format(
507             "/redfish/v1/Systems/{}/PCIeDevices/{}/PCIeFunctions",
508             BMCWEB_REDFISH_SYSTEM_URI_NAME, pcieDeviceId);
509 }
510 
511 inline void getPCIeDeviceProperties(
512     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
513     const std::string& pcieDevicePath, const std::string& service,
514     const std::function<void(
515         const dbus::utility::DBusPropertiesMap& pcieDevProperties)>&& callback)
516 {
517     sdbusplus::asio::getAllProperties(
518         *crow::connections::systemBus, service, pcieDevicePath,
519         "xyz.openbmc_project.Inventory.Item.PCIeDevice",
520         [asyncResp,
521          callback](const boost::system::error_code& ec,
522                    const dbus::utility::DBusPropertiesMap& pcieDevProperties) {
523             if (ec)
524             {
525                 if (ec.value() != EBADR)
526                 {
527                     BMCWEB_LOG_ERROR("DBUS response error for Properties");
528                     messages::internalError(asyncResp->res);
529                 }
530                 return;
531             }
532             callback(pcieDevProperties);
533         });
534 }
535 
536 inline void addPCIeDeviceCommonProperties(
537     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
538     const std::string& pcieDeviceId)
539 {
540     asyncResp->res.addHeader(
541         boost::beast::http::field::link,
542         "</redfish/v1/JsonSchemas/PCIeDevice/PCIeDevice.json>; rel=describedby");
543     asyncResp->res.jsonValue["@odata.type"] = "#PCIeDevice.v1_9_0.PCIeDevice";
544     asyncResp->res.jsonValue["@odata.id"] =
545         boost::urls::format("/redfish/v1/Systems/{}/PCIeDevices/{}",
546                             BMCWEB_REDFISH_SYSTEM_URI_NAME, pcieDeviceId);
547     asyncResp->res.jsonValue["Name"] = "PCIe Device";
548     asyncResp->res.jsonValue["Id"] = pcieDeviceId;
549     asyncResp->res.jsonValue["Status"]["State"] = resource::State::Enabled;
550     asyncResp->res.jsonValue["Status"]["Health"] = resource::Health::OK;
551 }
552 
553 inline void afterGetValidPcieDevicePath(
554     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
555     const std::string& pcieDeviceId, const std::string& pcieDevicePath,
556     const std::string& service)
557 {
558     addPCIeDeviceCommonProperties(asyncResp, pcieDeviceId);
559     getPCIeDeviceAsset(asyncResp, pcieDevicePath, service);
560     getPCIeDeviceState(asyncResp, pcieDevicePath, service);
561     getPCIeDeviceHealth(asyncResp, pcieDevicePath, service);
562     getPCIeDeviceProperties(
563         asyncResp, pcieDevicePath, service,
564         std::bind_front(addPCIeDeviceProperties, asyncResp, pcieDeviceId));
565     getPCIeDeviceSlotPath(
566         pcieDevicePath, asyncResp,
567         std::bind_front(afterGetPCIeDeviceSlotPath, asyncResp));
568 }
569 
570 inline void handlePCIeDeviceGet(
571     App& app, const crow::Request& req,
572     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
573     const std::string& systemName, const std::string& pcieDeviceId)
574 {
575     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
576     {
577         return;
578     }
579     if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
580     {
581         // Option currently returns no systems.  TBD
582         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
583                                    systemName);
584         return;
585     }
586     if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
587     {
588         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
589                                    systemName);
590         return;
591     }
592 
593     getValidPCIeDevicePath(
594         pcieDeviceId, asyncResp,
595         std::bind_front(afterGetValidPcieDevicePath, asyncResp, pcieDeviceId));
596 }
597 
598 inline void requestRoutesSystemPCIeDevice(App& app)
599 {
600     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/PCIeDevices/<str>/")
601         .privileges(redfish::privileges::getPCIeDevice)
602         .methods(boost::beast::http::verb::get)(
603             std::bind_front(handlePCIeDeviceGet, std::ref(app)));
604 }
605 
606 inline void addPCIeFunctionList(
607     crow::Response& res, const std::string& pcieDeviceId,
608     const dbus::utility::DBusPropertiesMap& pcieDevProperties)
609 {
610     nlohmann::json& pcieFunctionList = res.jsonValue["Members"];
611     pcieFunctionList = nlohmann::json::array();
612     static constexpr const int maxPciFunctionNum = 8;
613 
614     for (int functionNum = 0; functionNum < maxPciFunctionNum; functionNum++)
615     {
616         // Check if this function exists by
617         // looking for a device ID
618         std::string devIDProperty =
619             "Function" + std::to_string(functionNum) + "DeviceId";
620         const std::string* property = nullptr;
621         for (const auto& propEntry : pcieDevProperties)
622         {
623             if (propEntry.first == devIDProperty)
624             {
625                 property = std::get_if<std::string>(&propEntry.second);
626                 break;
627             }
628         }
629         if (property == nullptr || property->empty())
630         {
631             continue;
632         }
633 
634         nlohmann::json::object_t pcieFunction;
635         pcieFunction["@odata.id"] = boost::urls::format(
636             "/redfish/v1/Systems/{}/PCIeDevices/{}/PCIeFunctions/{}",
637             BMCWEB_REDFISH_SYSTEM_URI_NAME, pcieDeviceId,
638             std::to_string(functionNum));
639         pcieFunctionList.emplace_back(std::move(pcieFunction));
640     }
641     res.jsonValue["PCIeFunctions@odata.count"] = pcieFunctionList.size();
642 }
643 
644 inline void handlePCIeFunctionCollectionGet(
645     App& app, const crow::Request& req,
646     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
647     const std::string& systemName, const std::string& pcieDeviceId)
648 {
649     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
650     {
651         return;
652     }
653     if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
654     {
655         // Option currently returns no systems.  TBD
656         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
657                                    systemName);
658         return;
659     }
660 
661     getValidPCIeDevicePath(
662         pcieDeviceId, asyncResp,
663         [asyncResp, pcieDeviceId](const std::string& pcieDevicePath,
664                                   const std::string& service) {
665             asyncResp->res.addHeader(
666                 boost::beast::http::field::link,
667                 "</redfish/v1/JsonSchemas/PCIeFunctionCollection/PCIeFunctionCollection.json>; rel=describedby");
668             asyncResp->res.jsonValue["@odata.type"] =
669                 "#PCIeFunctionCollection.PCIeFunctionCollection";
670             asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
671                 "/redfish/v1/Systems/{}/PCIeDevices/{}/PCIeFunctions",
672                 BMCWEB_REDFISH_SYSTEM_URI_NAME, pcieDeviceId);
673             asyncResp->res.jsonValue["Name"] = "PCIe Function Collection";
674             asyncResp->res.jsonValue["Description"] =
675                 "Collection of PCIe Functions for PCIe Device " + pcieDeviceId;
676             getPCIeDeviceProperties(
677                 asyncResp, pcieDevicePath, service,
678                 [asyncResp, pcieDeviceId](
679                     const dbus::utility::DBusPropertiesMap& pcieDevProperties) {
680                     addPCIeFunctionList(asyncResp->res, pcieDeviceId,
681                                         pcieDevProperties);
682                 });
683         });
684 }
685 
686 inline void requestRoutesSystemPCIeFunctionCollection(App& app)
687 {
688     /**
689      * Functions triggers appropriate requests on DBus
690      */
691     BMCWEB_ROUTE(app,
692                  "/redfish/v1/Systems/<str>/PCIeDevices/<str>/PCIeFunctions/")
693         .privileges(redfish::privileges::getPCIeFunctionCollection)
694         .methods(boost::beast::http::verb::get)(
695             std::bind_front(handlePCIeFunctionCollectionGet, std::ref(app)));
696 }
697 
698 inline bool validatePCIeFunctionId(
699     uint64_t pcieFunctionId,
700     const dbus::utility::DBusPropertiesMap& pcieDevProperties)
701 {
702     std::string functionName = "Function" + std::to_string(pcieFunctionId);
703     std::string devIDProperty = functionName + "DeviceId";
704 
705     const std::string* devIdProperty = nullptr;
706     for (const auto& property : pcieDevProperties)
707     {
708         if (property.first == devIDProperty)
709         {
710             devIdProperty = std::get_if<std::string>(&property.second);
711             break;
712         }
713     }
714     return (devIdProperty != nullptr && !devIdProperty->empty());
715 }
716 
717 inline void addPCIeFunctionProperties(
718     crow::Response& resp, uint64_t pcieFunctionId,
719     const dbus::utility::DBusPropertiesMap& pcieDevProperties)
720 {
721     std::string functionName = "Function" + std::to_string(pcieFunctionId);
722     for (const auto& property : pcieDevProperties)
723     {
724         const std::string* strProperty =
725             std::get_if<std::string>(&property.second);
726         if (strProperty == nullptr)
727         {
728             continue;
729         }
730         if (property.first == functionName + "DeviceId")
731         {
732             resp.jsonValue["DeviceId"] = *strProperty;
733         }
734         if (property.first == functionName + "VendorId")
735         {
736             resp.jsonValue["VendorId"] = *strProperty;
737         }
738         // TODO: FunctionType and DeviceClass are Redfish enums. The D-Bus
739         // property strings should be mapped correctly to ensure these
740         // strings are Redfish enum values. For now just check for empty.
741         if (property.first == functionName + "FunctionType")
742         {
743             if (!strProperty->empty())
744             {
745                 resp.jsonValue["FunctionType"] = *strProperty;
746             }
747         }
748         if (property.first == functionName + "DeviceClass")
749         {
750             if (!strProperty->empty())
751             {
752                 resp.jsonValue["DeviceClass"] = *strProperty;
753             }
754         }
755         if (property.first == functionName + "ClassCode")
756         {
757             resp.jsonValue["ClassCode"] = *strProperty;
758         }
759         if (property.first == functionName + "RevisionId")
760         {
761             resp.jsonValue["RevisionId"] = *strProperty;
762         }
763         if (property.first == functionName + "SubsystemId")
764         {
765             resp.jsonValue["SubsystemId"] = *strProperty;
766         }
767         if (property.first == functionName + "SubsystemVendorId")
768         {
769             resp.jsonValue["SubsystemVendorId"] = *strProperty;
770         }
771     }
772 }
773 
774 inline void addPCIeFunctionCommonProperties(crow::Response& resp,
775                                             const std::string& pcieDeviceId,
776                                             uint64_t pcieFunctionId)
777 {
778     resp.addHeader(
779         boost::beast::http::field::link,
780         "</redfish/v1/JsonSchemas/PCIeFunction/PCIeFunction.json>; rel=describedby");
781     resp.jsonValue["@odata.type"] = "#PCIeFunction.v1_2_3.PCIeFunction";
782     resp.jsonValue["@odata.id"] = boost::urls::format(
783         "/redfish/v1/Systems/{}/PCIeDevices/{}/PCIeFunctions/{}",
784         BMCWEB_REDFISH_SYSTEM_URI_NAME, pcieDeviceId,
785         std::to_string(pcieFunctionId));
786     resp.jsonValue["Name"] = "PCIe Function";
787     resp.jsonValue["Id"] = std::to_string(pcieFunctionId);
788     resp.jsonValue["FunctionId"] = pcieFunctionId;
789     resp.jsonValue["Links"]["PCIeDevice"]["@odata.id"] =
790         boost::urls::format("/redfish/v1/Systems/{}/PCIeDevices/{}",
791                             BMCWEB_REDFISH_SYSTEM_URI_NAME, pcieDeviceId);
792 }
793 
794 inline void handlePCIeFunctionGet(
795     App& app, const crow::Request& req,
796     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
797     const std::string& systemName, const std::string& pcieDeviceId,
798     const std::string& pcieFunctionIdStr)
799 {
800     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
801     {
802         return;
803     }
804     if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
805     {
806         // Option currently returns no systems.  TBD
807         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
808                                    systemName);
809         return;
810     }
811     if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
812     {
813         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
814                                    systemName);
815         return;
816     }
817     std::string_view pcieFunctionIdView = pcieFunctionIdStr;
818 
819     uint64_t pcieFunctionId = 0;
820     std::from_chars_result result = std::from_chars(
821         pcieFunctionIdView.begin(), pcieFunctionIdView.end(), pcieFunctionId);
822     if (result.ec != std::errc{} || result.ptr != pcieFunctionIdView.end())
823     {
824         messages::resourceNotFound(asyncResp->res, "PCIeFunction",
825                                    pcieFunctionIdStr);
826         return;
827     }
828 
829     getValidPCIeDevicePath(
830         pcieDeviceId, asyncResp,
831         [asyncResp, pcieDeviceId, pcieFunctionId](
832             const std::string& pcieDevicePath, const std::string& service) {
833             getPCIeDeviceProperties(
834                 asyncResp, pcieDevicePath, service,
835                 [asyncResp, pcieDeviceId, pcieFunctionId](
836                     const dbus::utility::DBusPropertiesMap& pcieDevProperties) {
837                     addPCIeFunctionCommonProperties(
838                         asyncResp->res, pcieDeviceId, pcieFunctionId);
839                     addPCIeFunctionProperties(asyncResp->res, pcieFunctionId,
840                                               pcieDevProperties);
841                 });
842         });
843 }
844 
845 inline void requestRoutesSystemPCIeFunction(App& app)
846 {
847     BMCWEB_ROUTE(
848         app, "/redfish/v1/Systems/<str>/PCIeDevices/<str>/PCIeFunctions/<str>/")
849         .privileges(redfish::privileges::getPCIeFunction)
850         .methods(boost::beast::http::verb::get)(
851             std::bind_front(handlePCIeFunctionGet, std::ref(app)));
852 }
853 
854 } // namespace redfish
855