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