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