xref: /openbmc/bmcweb/redfish-core/lib/pcie.hpp (revision f80a87f2)
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 #include <limits>
33 
34 namespace redfish
35 {
36 
37 static constexpr const char* inventoryPath = "/xyz/openbmc_project/inventory";
38 static constexpr std::array<std::string_view, 1> pcieDeviceInterface = {
39     "xyz.openbmc_project.Inventory.Item.PCIeDevice"};
40 static constexpr std::array<std::string_view, 1> pcieSlotInterface = {
41     "xyz.openbmc_project.Inventory.Item.PCIeSlot"};
42 
43 static inline void handlePCIeDevicePath(
44     const std::string& pcieDeviceId,
45     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
46     const dbus::utility::MapperGetSubTreePathsResponse& pcieDevicePaths,
47     const std::function<void(const std::string& pcieDevicePath,
48                              const std::string& service)>& callback)
49 
50 {
51     for (const std::string& pcieDevicePath : pcieDevicePaths)
52     {
53         std::string pciecDeviceName =
54             sdbusplus::message::object_path(pcieDevicePath).filename();
55         if (pciecDeviceName.empty() || pciecDeviceName != pcieDeviceId)
56         {
57             continue;
58         }
59 
60         dbus::utility::getDbusObject(
61             pcieDevicePath, {},
62             [pcieDevicePath, asyncResp,
63              callback](const boost::system::error_code& ec,
64                        const dbus::utility::MapperGetObject& object) {
65             if (ec || object.empty())
66             {
67                 BMCWEB_LOG_ERROR("DBUS response error {}", ec);
68                 messages::internalError(asyncResp->res);
69                 return;
70             }
71             callback(pcieDevicePath, object.begin()->first);
72         });
73         return;
74     }
75 
76     BMCWEB_LOG_WARNING("PCIe Device not found");
77     messages::resourceNotFound(asyncResp->res, "PCIeDevice", pcieDeviceId);
78 }
79 
80 static inline void getValidPCIeDevicePath(
81     const std::string& pcieDeviceId,
82     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
83     const std::function<void(const std::string& pcieDevicePath,
84                              const std::string& service)>& callback)
85 {
86     dbus::utility::getSubTreePaths(
87         inventoryPath, 0, pcieDeviceInterface,
88         [pcieDeviceId, asyncResp,
89          callback](const boost::system::error_code& ec,
90                    const dbus::utility::MapperGetSubTreePathsResponse&
91                        pcieDevicePaths) {
92         if (ec)
93         {
94             BMCWEB_LOG_ERROR("D-Bus response error on GetSubTree {}", ec);
95             messages::internalError(asyncResp->res);
96             return;
97         }
98         handlePCIeDevicePath(pcieDeviceId, asyncResp, pcieDevicePaths,
99                              callback);
100         return;
101     });
102 }
103 
104 static inline void handlePCIeDeviceCollectionGet(
105     crow::App& app, const crow::Request& req,
106     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
107     const std::string& systemName)
108 {
109     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
110     {
111         return;
112     }
113     if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
114     {
115         // Option currently returns no systems.  TBD
116         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
117                                    systemName);
118         return;
119     }
120     if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
121     {
122         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
123                                    systemName);
124         return;
125     }
126 
127     asyncResp->res.addHeader(boost::beast::http::field::link,
128                              "</redfish/v1/JsonSchemas/PCIeDeviceCollection/"
129                              "PCIeDeviceCollection.json>; rel=describedby");
130     asyncResp->res.jsonValue["@odata.type"] =
131         "#PCIeDeviceCollection.PCIeDeviceCollection";
132     asyncResp->res.jsonValue["@odata.id"] = std::format(
133         "/redfish/v1/Systems/{}/PCIeDevices", BMCWEB_REDFISH_SYSTEM_URI_NAME);
134     asyncResp->res.jsonValue["Name"] = "PCIe Device Collection";
135     asyncResp->res.jsonValue["Description"] = "Collection of PCIe Devices";
136 
137     pcie_util::getPCIeDeviceList(asyncResp,
138                                  nlohmann::json::json_pointer("/Members"));
139 }
140 
141 inline void requestRoutesSystemPCIeDeviceCollection(App& app)
142 {
143     /**
144      * Functions triggers appropriate requests on DBus
145      */
146     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/PCIeDevices/")
147         .privileges(redfish::privileges::getPCIeDeviceCollection)
148         .methods(boost::beast::http::verb::get)(
149             std::bind_front(handlePCIeDeviceCollectionGet, std::ref(app)));
150 }
151 
152 inline void addPCIeSlotProperties(
153     crow::Response& res, const boost::system::error_code& ec,
154     const dbus::utility::DBusPropertiesMap& pcieSlotProperties)
155 {
156     if (ec)
157     {
158         BMCWEB_LOG_ERROR("DBUS response error for getAllProperties{}",
159                          ec.value());
160         messages::internalError(res);
161         return;
162     }
163     std::string generation;
164     size_t lanes = 0;
165     std::string slotType;
166 
167     bool success = sdbusplus::unpackPropertiesNoThrow(
168         dbus_utils::UnpackErrorPrinter(), pcieSlotProperties, "Generation",
169         generation, "Lanes", lanes, "SlotType", slotType);
170 
171     if (!success)
172     {
173         messages::internalError(res);
174         return;
175     }
176 
177     std::optional<pcie_device::PCIeTypes> pcieType =
178         pcie_util::redfishPcieGenerationFromDbus(generation);
179     if (!pcieType)
180     {
181         BMCWEB_LOG_WARNING("Unknown PCIeType: {}", generation);
182     }
183     else
184     {
185         if (*pcieType == pcie_device::PCIeTypes::Invalid)
186         {
187             BMCWEB_LOG_ERROR("Invalid PCIeType: {}", generation);
188             messages::internalError(res);
189             return;
190         }
191         res.jsonValue["Slot"]["PCIeType"] = *pcieType;
192     }
193 
194     if (lanes != 0)
195     {
196         res.jsonValue["Slot"]["Lanes"] = lanes;
197     }
198 
199     std::optional<pcie_slots::SlotTypes> redfishSlotType =
200         pcie_util::dbusSlotTypeToRf(slotType);
201     if (!redfishSlotType)
202     {
203         BMCWEB_LOG_WARNING("Unknown PCIeSlot Type: {}", slotType);
204     }
205     else
206     {
207         if (*redfishSlotType == pcie_slots::SlotTypes::Invalid)
208         {
209             BMCWEB_LOG_ERROR("Invalid PCIeSlot type: {}", slotType);
210             messages::internalError(res);
211             return;
212         }
213         res.jsonValue["Slot"]["SlotType"] = *redfishSlotType;
214     }
215 }
216 
217 inline void getPCIeDeviceSlotPath(
218     const std::string& pcieDevicePath,
219     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
220     std::function<void(const std::string& pcieDeviceSlot)>&& callback)
221 {
222     std::string associationPath = pcieDevicePath + "/contained_by";
223     dbus::utility::getAssociatedSubTreePaths(
224         associationPath, sdbusplus::message::object_path(inventoryPath), 0,
225         pcieSlotInterface,
226         [callback = std::move(callback), asyncResp, pcieDevicePath](
227             const boost::system::error_code& ec,
228             const dbus::utility::MapperGetSubTreePathsResponse& endpoints) {
229         if (ec)
230         {
231             if (ec.value() == EBADR)
232             {
233                 // Missing association is not an error
234                 return;
235             }
236             BMCWEB_LOG_ERROR(
237                 "DBUS response error for getAssociatedSubTreePaths {}",
238                 ec.value());
239             messages::internalError(asyncResp->res);
240             return;
241         }
242         if (endpoints.size() > 1)
243         {
244             BMCWEB_LOG_ERROR(
245                 "PCIeDevice is associated with more than one PCIeSlot: {}",
246                 endpoints.size());
247             messages::internalError(asyncResp->res);
248             return;
249         }
250         if (endpoints.empty())
251         {
252             // If the device doesn't have an association, return without PCIe
253             // Slot properties
254             BMCWEB_LOG_DEBUG("PCIeDevice is not associated with PCIeSlot");
255             return;
256         }
257         callback(endpoints[0]);
258     });
259 }
260 
261 inline void
262     afterGetDbusObject(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
263                        const std::string& pcieDeviceSlot,
264                        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
298     getPCIeDeviceHealth(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
299                         const std::string& pcieDevicePath,
300                         const std::string& service)
301 {
302     sdbusplus::asio::getProperty<bool>(
303         *crow::connections::systemBus, service, pcieDevicePath,
304         "xyz.openbmc_project.State.Decorator.OperationalStatus", "Functional",
305         [asyncResp](const boost::system::error_code& ec, const bool value) {
306         if (ec)
307         {
308             if (ec.value() != EBADR)
309             {
310                 BMCWEB_LOG_ERROR("DBUS response error for Health {}",
311                                  ec.value());
312                 messages::internalError(asyncResp->res);
313             }
314             return;
315         }
316 
317         if (!value)
318         {
319             asyncResp->res.jsonValue["Status"]["Health"] = "Critical";
320         }
321     });
322 }
323 
324 inline void
325     getPCIeDeviceState(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
326                        const std::string& pcieDevicePath,
327                        const std::string& service)
328 {
329     sdbusplus::asio::getProperty<bool>(
330         *crow::connections::systemBus, service, pcieDevicePath,
331         "xyz.openbmc_project.Inventory.Item", "Present",
332         [asyncResp](const boost::system::error_code& ec, bool value) {
333         if (ec)
334         {
335             if (ec.value() != EBADR)
336             {
337                 BMCWEB_LOG_ERROR("DBUS response error for State");
338                 messages::internalError(asyncResp->res);
339             }
340             return;
341         }
342 
343         if (!value)
344         {
345             asyncResp->res.jsonValue["Status"]["State"] = "Absent";
346         }
347     });
348 }
349 
350 inline void
351     getPCIeDeviceAsset(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
352                        const std::string& pcieDevicePath,
353                        const std::string& service)
354 {
355     sdbusplus::asio::getAllProperties(
356         *crow::connections::systemBus, service, pcieDevicePath,
357         "xyz.openbmc_project.Inventory.Decorator.Asset",
358         [pcieDevicePath, asyncResp{asyncResp}](
359             const boost::system::error_code& ec,
360             const dbus::utility::DBusPropertiesMap& assetList) {
361         if (ec)
362         {
363             if (ec.value() != EBADR)
364             {
365                 BMCWEB_LOG_ERROR("DBUS response error for Properties{}",
366                                  ec.value());
367                 messages::internalError(asyncResp->res);
368             }
369             return;
370         }
371 
372         const std::string* manufacturer = nullptr;
373         const std::string* model = nullptr;
374         const std::string* partNumber = nullptr;
375         const std::string* serialNumber = nullptr;
376         const std::string* sparePartNumber = nullptr;
377 
378         const bool success = sdbusplus::unpackPropertiesNoThrow(
379             dbus_utils::UnpackErrorPrinter(), assetList, "Manufacturer",
380             manufacturer, "Model", model, "PartNumber", partNumber,
381             "SerialNumber", serialNumber, "SparePartNumber", 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"] = "Enabled";
550     asyncResp->res.jsonValue["Status"]["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
571     handlePCIeDeviceGet(App& app, const crow::Request& req,
572                         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
573                         const std::string& systemName,
574                         const std::string& pcieDeviceId)
575 {
576     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
577     {
578         return;
579     }
580     if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
581     {
582         // Option currently returns no systems.  TBD
583         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
584                                    systemName);
585         return;
586     }
587     if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
588     {
589         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
590                                    systemName);
591         return;
592     }
593 
594     getValidPCIeDevicePath(
595         pcieDeviceId, asyncResp,
596         std::bind_front(afterGetValidPcieDevicePath, asyncResp, pcieDeviceId));
597 }
598 
599 inline void requestRoutesSystemPCIeDevice(App& app)
600 {
601     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/PCIeDevices/<str>/")
602         .privileges(redfish::privileges::getPCIeDevice)
603         .methods(boost::beast::http::verb::get)(
604             std::bind_front(handlePCIeDeviceGet, std::ref(app)));
605 }
606 
607 inline void addPCIeFunctionList(
608     crow::Response& res, const std::string& pcieDeviceId,
609     const dbus::utility::DBusPropertiesMap& pcieDevProperties)
610 {
611     nlohmann::json& pcieFunctionList = res.jsonValue["Members"];
612     pcieFunctionList = nlohmann::json::array();
613     static constexpr const int maxPciFunctionNum = 8;
614 
615     for (int functionNum = 0; functionNum < maxPciFunctionNum; functionNum++)
616     {
617         // Check if this function exists by
618         // looking for a device ID
619         std::string devIDProperty = "Function" + std::to_string(functionNum) +
620                                     "DeviceId";
621         const std::string* property = nullptr;
622         for (const auto& propEntry : pcieDevProperties)
623         {
624             if (propEntry.first == devIDProperty)
625             {
626                 property = std::get_if<std::string>(&propEntry.second);
627                 break;
628             }
629         }
630         if (property == nullptr || property->empty())
631         {
632             continue;
633         }
634 
635         nlohmann::json::object_t pcieFunction;
636         pcieFunction["@odata.id"] = boost::urls::format(
637             "/redfish/v1/Systems/{}/PCIeDevices/{}/PCIeFunctions/{}",
638             BMCWEB_REDFISH_SYSTEM_URI_NAME, pcieDeviceId,
639             std::to_string(functionNum));
640         pcieFunctionList.emplace_back(std::move(pcieFunction));
641     }
642     res.jsonValue["PCIeFunctions@odata.count"] = pcieFunctionList.size();
643 }
644 
645 inline void handlePCIeFunctionCollectionGet(
646     App& app, const crow::Request& req,
647     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
648     const std::string& systemName, const std::string& pcieDeviceId)
649 {
650     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
651     {
652         return;
653     }
654     if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
655     {
656         // Option currently returns no systems.  TBD
657         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
658                                    systemName);
659         return;
660     }
661 
662     getValidPCIeDevicePath(
663         pcieDeviceId, asyncResp,
664         [asyncResp, pcieDeviceId](const std::string& pcieDevicePath,
665                                   const std::string& service) {
666         asyncResp->res.addHeader(
667             boost::beast::http::field::link,
668             "</redfish/v1/JsonSchemas/PCIeFunctionCollection/PCIeFunctionCollection.json>; rel=describedby");
669         asyncResp->res.jsonValue["@odata.type"] =
670             "#PCIeFunctionCollection.PCIeFunctionCollection";
671         asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
672             "/redfish/v1/Systems/{}/PCIeDevices/{}/PCIeFunctions",
673             BMCWEB_REDFISH_SYSTEM_URI_NAME, pcieDeviceId);
674         asyncResp->res.jsonValue["Name"] = "PCIe Function Collection";
675         asyncResp->res.jsonValue["Description"] =
676             "Collection of PCIe Functions for PCIe Device " + pcieDeviceId;
677         getPCIeDeviceProperties(
678             asyncResp, pcieDevicePath, service,
679             [asyncResp, pcieDeviceId](
680                 const dbus::utility::DBusPropertiesMap& pcieDevProperties) {
681             addPCIeFunctionList(asyncResp->res, pcieDeviceId,
682                                 pcieDevProperties);
683         });
684     });
685 }
686 
687 inline void requestRoutesSystemPCIeFunctionCollection(App& app)
688 {
689     /**
690      * Functions triggers appropriate requests on DBus
691      */
692     BMCWEB_ROUTE(app,
693                  "/redfish/v1/Systems/<str>/PCIeDevices/<str>/PCIeFunctions/")
694         .privileges(redfish::privileges::getPCIeFunctionCollection)
695         .methods(boost::beast::http::verb::get)(
696             std::bind_front(handlePCIeFunctionCollectionGet, std::ref(app)));
697 }
698 
699 inline bool validatePCIeFunctionId(
700     uint64_t pcieFunctionId,
701     const dbus::utility::DBusPropertiesMap& pcieDevProperties)
702 {
703     std::string functionName = "Function" + std::to_string(pcieFunctionId);
704     std::string devIDProperty = functionName + "DeviceId";
705 
706     const std::string* devIdProperty = nullptr;
707     for (const auto& property : pcieDevProperties)
708     {
709         if (property.first == devIDProperty)
710         {
711             devIdProperty = std::get_if<std::string>(&property.second);
712             break;
713         }
714     }
715     return (devIdProperty != nullptr && !devIdProperty->empty());
716 }
717 
718 inline void addPCIeFunctionProperties(
719     crow::Response& resp, uint64_t pcieFunctionId,
720     const dbus::utility::DBusPropertiesMap& pcieDevProperties)
721 {
722     std::string functionName = "Function" + std::to_string(pcieFunctionId);
723     for (const auto& property : pcieDevProperties)
724     {
725         const std::string* strProperty =
726             std::get_if<std::string>(&property.second);
727         if (strProperty == nullptr)
728         {
729             BMCWEB_LOG_ERROR("Function wasn't a string?");
730             continue;
731         }
732         if (property.first == functionName + "DeviceId")
733         {
734             resp.jsonValue["DeviceId"] = *strProperty;
735         }
736         if (property.first == functionName + "VendorId")
737         {
738             resp.jsonValue["VendorId"] = *strProperty;
739         }
740         // TODO: FunctionType and DeviceClass are Redfish enums. The D-Bus
741         // property strings should be mapped correctly to ensure these
742         // strings are Redfish enum values. For now just check for empty.
743         if (property.first == functionName + "FunctionType")
744         {
745             if (!strProperty->empty())
746             {
747                 resp.jsonValue["FunctionType"] = *strProperty;
748             }
749         }
750         if (property.first == functionName + "DeviceClass")
751         {
752             if (!strProperty->empty())
753             {
754                 resp.jsonValue["DeviceClass"] = *strProperty;
755             }
756         }
757         if (property.first == functionName + "ClassCode")
758         {
759             resp.jsonValue["ClassCode"] = *strProperty;
760         }
761         if (property.first == functionName + "RevisionId")
762         {
763             resp.jsonValue["RevisionId"] = *strProperty;
764         }
765         if (property.first == functionName + "SubsystemId")
766         {
767             resp.jsonValue["SubsystemId"] = *strProperty;
768         }
769         if (property.first == functionName + "SubsystemVendorId")
770         {
771             resp.jsonValue["SubsystemVendorId"] = *strProperty;
772         }
773     }
774 }
775 
776 inline void addPCIeFunctionCommonProperties(crow::Response& resp,
777                                             const std::string& pcieDeviceId,
778                                             uint64_t pcieFunctionId)
779 {
780     resp.addHeader(
781         boost::beast::http::field::link,
782         "</redfish/v1/JsonSchemas/PCIeFunction/PCIeFunction.json>; rel=describedby");
783     resp.jsonValue["@odata.type"] = "#PCIeFunction.v1_2_3.PCIeFunction";
784     resp.jsonValue["@odata.id"] = boost::urls::format(
785         "/redfish/v1/Systems/{}/PCIeDevices/{}/PCIeFunctions/{}",
786         BMCWEB_REDFISH_SYSTEM_URI_NAME, pcieDeviceId,
787         std::to_string(pcieFunctionId));
788     resp.jsonValue["Name"] = "PCIe Function";
789     resp.jsonValue["Id"] = std::to_string(pcieFunctionId);
790     resp.jsonValue["FunctionId"] = pcieFunctionId;
791     resp.jsonValue["Links"]["PCIeDevice"]["@odata.id"] =
792         boost::urls::format("/redfish/v1/Systems/{}/PCIeDevices/{}",
793                             BMCWEB_REDFISH_SYSTEM_URI_NAME, pcieDeviceId);
794 }
795 
796 inline void
797     handlePCIeFunctionGet(App& app, const crow::Request& req,
798                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
799                           const std::string& systemName,
800                           const std::string& pcieDeviceId,
801                           const std::string& pcieFunctionIdStr)
802 {
803     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
804     {
805         return;
806     }
807     if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
808     {
809         // Option currently returns no systems.  TBD
810         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
811                                    systemName);
812         return;
813     }
814     if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
815     {
816         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
817                                    systemName);
818         return;
819     }
820     std::string_view pcieFunctionIdView = pcieFunctionIdStr;
821 
822     uint64_t pcieFunctionId = 0;
823     std::from_chars_result result = std::from_chars(
824         pcieFunctionIdView.begin(), pcieFunctionIdView.end(), pcieFunctionId);
825     if (result.ec != std::errc{} || result.ptr != pcieFunctionIdView.end())
826     {
827         messages::resourceNotFound(asyncResp->res, "PCIeFunction",
828                                    pcieFunctionIdStr);
829         return;
830     }
831 
832     getValidPCIeDevicePath(pcieDeviceId, asyncResp,
833                            [asyncResp, pcieDeviceId,
834                             pcieFunctionId](const std::string& pcieDevicePath,
835                                             const std::string& service) {
836         getPCIeDeviceProperties(
837             asyncResp, pcieDevicePath, service,
838             [asyncResp, pcieDeviceId, pcieFunctionId](
839                 const dbus::utility::DBusPropertiesMap& pcieDevProperties) {
840             addPCIeFunctionCommonProperties(asyncResp->res, pcieDeviceId,
841                                             pcieFunctionId);
842             addPCIeFunctionProperties(asyncResp->res, pcieFunctionId,
843                                       pcieDevProperties);
844         });
845     });
846 }
847 
848 inline void requestRoutesSystemPCIeFunction(App& app)
849 {
850     BMCWEB_ROUTE(
851         app, "/redfish/v1/Systems/<str>/PCIeDevices/<str>/PCIeFunctions/<str>/")
852         .privileges(redfish::privileges::getPCIeFunction)
853         .methods(boost::beast::http::verb::get)(
854             std::bind_front(handlePCIeFunctionGet, std::ref(app)));
855 }
856 
857 } // namespace redfish
858