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