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