xref: /openbmc/bmcweb/redfish-core/lib/pcie.hpp (revision c49c329d)
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 
39 static inline void handlePCIeDevicePath(
40     const std::string& pcieDeviceId,
41     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
42     const dbus::utility::MapperGetSubTreePathsResponse& pcieDevicePaths,
43     const std::function<void(const std::string& pcieDevicePath,
44                              const std::string& service)>& callback)
45 
46 {
47     for (const std::string& pcieDevicePath : pcieDevicePaths)
48     {
49         std::string pciecDeviceName =
50             sdbusplus::message::object_path(pcieDevicePath).filename();
51         if (pciecDeviceName.empty() || pciecDeviceName != pcieDeviceId)
52         {
53             continue;
54         }
55 
56         dbus::utility::getDbusObject(
57             pcieDevicePath, {},
58             [pcieDevicePath, asyncResp,
59              callback](const boost::system::error_code& ec,
60                        const dbus::utility::MapperGetObject& object) {
61             if (ec || object.empty())
62             {
63                 BMCWEB_LOG_ERROR << "DBUS response error " << ec;
64                 messages::internalError(asyncResp->res);
65                 return;
66             }
67             callback(pcieDevicePath, object.begin()->first);
68             });
69         return;
70     }
71 
72     BMCWEB_LOG_WARNING << "PCIe Device not found";
73     messages::resourceNotFound(asyncResp->res, "PCIeDevice", pcieDeviceId);
74 }
75 
76 static inline void getValidPCIeDevicePath(
77     const std::string& pcieDeviceId,
78     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
79     const std::function<void(const std::string& pcieDevicePath,
80                              const std::string& service)>& callback)
81 {
82     dbus::utility::getSubTreePaths(
83         inventoryPath, 0, pcieDeviceInterface,
84         [pcieDeviceId, asyncResp,
85          callback](const boost::system::error_code& ec,
86                    const dbus::utility::MapperGetSubTreePathsResponse&
87                        pcieDevicePaths) {
88         if (ec)
89         {
90             BMCWEB_LOG_ERROR << "D-Bus response error on GetSubTree " << ec;
91             messages::internalError(asyncResp->res);
92             return;
93         }
94         handlePCIeDevicePath(pcieDeviceId, asyncResp, pcieDevicePaths,
95                              callback);
96         return;
97         });
98 }
99 
100 static inline void handlePCIeDeviceCollectionGet(
101     crow::App& app, const crow::Request& req,
102     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
103     const std::string& systemName)
104 {
105     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
106     {
107         return;
108     }
109     if (systemName != "system")
110     {
111         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
112                                    systemName);
113         return;
114     }
115 
116     asyncResp->res.addHeader(boost::beast::http::field::link,
117                              "</redfish/v1/JsonSchemas/PCIeDeviceCollection/"
118                              "PCIeDeviceCollection.json>; rel=describedby");
119     asyncResp->res.jsonValue["@odata.type"] =
120         "#PCIeDeviceCollection.PCIeDeviceCollection";
121     asyncResp->res.jsonValue["@odata.id"] =
122         "/redfish/v1/Systems/system/PCIeDevices";
123     asyncResp->res.jsonValue["Name"] = "PCIe Device Collection";
124     asyncResp->res.jsonValue["Description"] = "Collection of PCIe Devices";
125     asyncResp->res.jsonValue["Members"] = nlohmann::json::array();
126     asyncResp->res.jsonValue["Members@odata.count"] = 0;
127 
128     collection_util::getCollectionMembers(
129         asyncResp, boost::urls::url("/redfish/v1/Systems/system/PCIeDevices"),
130         pcieDeviceInterface);
131 }
132 
133 inline void requestRoutesSystemPCIeDeviceCollection(App& app)
134 {
135     /**
136      * Functions triggers appropriate requests on DBus
137      */
138     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/PCIeDevices/")
139         .privileges(redfish::privileges::getPCIeDeviceCollection)
140         .methods(boost::beast::http::verb::get)(
141             std::bind_front(handlePCIeDeviceCollectionGet, std::ref(app)));
142 }
143 
144 inline void
145     getPCIeDeviceState(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
146                        const std::string& pcieDevicePath,
147                        const std::string& service)
148 {
149     sdbusplus::asio::getProperty<bool>(
150         *crow::connections::systemBus, service, pcieDevicePath,
151         "xyz.openbmc_project.Inventory.Item", "Present",
152         [asyncResp](const boost::system::error_code& ec, const bool value) {
153         if (ec)
154         {
155             if (ec.value() != EBADR)
156             {
157                 BMCWEB_LOG_ERROR << "DBUS response error for State";
158                 messages::internalError(asyncResp->res);
159             }
160             return;
161         }
162 
163         if (!value)
164         {
165             asyncResp->res.jsonValue["Status"]["State"] = "Absent";
166         }
167         });
168 }
169 
170 inline void
171     getPCIeDeviceAsset(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
172                        const std::string& pcieDevicePath,
173                        const std::string& service)
174 {
175     sdbusplus::asio::getAllProperties(
176         *crow::connections::systemBus, service, pcieDevicePath,
177         "xyz.openbmc_project.Inventory.Decorator.Asset",
178         [pcieDevicePath, asyncResp{asyncResp}](
179             const boost::system::error_code& ec,
180             const dbus::utility::DBusPropertiesMap& assetList) {
181         if (ec)
182         {
183             if (ec.value() != EBADR)
184             {
185                 BMCWEB_LOG_ERROR << "DBUS response error for Properties"
186                                  << ec.value();
187                 messages::internalError(asyncResp->res);
188             }
189             return;
190         }
191 
192         const std::string* manufacturer = nullptr;
193         const std::string* model = nullptr;
194         const std::string* partNumber = nullptr;
195         const std::string* serialNumber = nullptr;
196         const std::string* sparePartNumber = nullptr;
197 
198         const bool success = sdbusplus::unpackPropertiesNoThrow(
199             dbus_utils::UnpackErrorPrinter(), assetList, "Manufacturer",
200             manufacturer, "Model", model, "PartNumber", partNumber,
201             "SerialNumber", serialNumber, "SparePartNumber", sparePartNumber);
202 
203         if (!success)
204         {
205             messages::internalError(asyncResp->res);
206             return;
207         }
208 
209         if (manufacturer != nullptr)
210         {
211             asyncResp->res.jsonValue["Manufacturer"] = *manufacturer;
212         }
213         if (model != nullptr)
214         {
215             asyncResp->res.jsonValue["Model"] = *model;
216         }
217 
218         if (partNumber != nullptr)
219         {
220             asyncResp->res.jsonValue["PartNumber"] = *partNumber;
221         }
222 
223         if (serialNumber != nullptr)
224         {
225             asyncResp->res.jsonValue["SerialNumber"] = *serialNumber;
226         }
227 
228         if (sparePartNumber != nullptr && !sparePartNumber->empty())
229         {
230             asyncResp->res.jsonValue["SparePartNumber"] = *sparePartNumber;
231         }
232         });
233 }
234 
235 inline void addPCIeDeviceProperties(
236     crow::Response& resp, const std::string& pcieDeviceId,
237     const dbus::utility::DBusPropertiesMap& pcieDevProperties)
238 {
239     const std::string* deviceType = nullptr;
240     const std::string* generationInUse = nullptr;
241     const int64_t* lanesInUse = nullptr;
242 
243     const bool success = sdbusplus::unpackPropertiesNoThrow(
244         dbus_utils::UnpackErrorPrinter(), pcieDevProperties, "DeviceType",
245         deviceType, "GenerationInUse", generationInUse, "LanesInUse",
246         lanesInUse);
247 
248     if (!success)
249     {
250         messages::internalError(resp);
251         return;
252     }
253 
254     if (deviceType != nullptr && !deviceType->empty())
255     {
256         resp.jsonValue["PCIeInterface"]["DeviceType"] = *deviceType;
257     }
258 
259     if (generationInUse != nullptr)
260     {
261         std::optional<pcie_device::PCIeTypes> redfishGenerationInUse =
262             pcie_util::redfishPcieGenerationFromDbus(*generationInUse);
263 
264         if (!redfishGenerationInUse)
265         {
266             messages::internalError(resp);
267             return;
268         }
269         if (*redfishGenerationInUse != pcie_device::PCIeTypes::Invalid)
270         {
271             resp.jsonValue["PCIeInterface"]["PCIeType"] =
272                 *redfishGenerationInUse;
273         }
274     }
275 
276     // The default value of LanesInUse is 0, and the field will be
277     // left as off if it is a default value.
278     if (lanesInUse != nullptr && *lanesInUse != 0)
279     {
280         resp.jsonValue["PCIeInterface"]["LanesInUse"] = *lanesInUse;
281     }
282 
283     resp.jsonValue["PCIeFunctions"]["@odata.id"] = boost::urls::format(
284         "/redfish/v1/Systems/system/PCIeDevices/{}/PCIeFunctions",
285         pcieDeviceId);
286 }
287 
288 inline void getPCIeDeviceProperties(
289     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
290     const std::string& pcieDevicePath, const std::string& service,
291     const std::function<void(
292         const dbus::utility::DBusPropertiesMap& pcieDevProperties)>&& callback)
293 {
294     sdbusplus::asio::getAllProperties(
295         *crow::connections::systemBus, service, pcieDevicePath,
296         "xyz.openbmc_project.Inventory.Item.PCIeDevice",
297         [asyncResp,
298          callback](const boost::system::error_code& ec,
299                    const dbus::utility::DBusPropertiesMap& pcieDevProperties) {
300         if (ec)
301         {
302             if (ec.value() != EBADR)
303             {
304                 BMCWEB_LOG_ERROR << "DBUS response error for Properties";
305                 messages::internalError(asyncResp->res);
306             }
307             return;
308         }
309         callback(pcieDevProperties);
310         });
311 }
312 
313 inline void addPCIeDeviceCommonProperties(
314     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
315     const std::string& pcieDeviceId)
316 {
317     asyncResp->res.addHeader(
318         boost::beast::http::field::link,
319         "</redfish/v1/JsonSchemas/PCIeDevice/PCIeDevice.json>; rel=describedby");
320     asyncResp->res.jsonValue["@odata.type"] = "#PCIeDevice.v1_9_0.PCIeDevice";
321     asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
322         "/redfish/v1/Systems/system/PCIeDevices/{}", pcieDeviceId);
323     asyncResp->res.jsonValue["Name"] = "PCIe Device";
324     asyncResp->res.jsonValue["Id"] = pcieDeviceId;
325     asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
326 }
327 
328 inline void
329     handlePCIeDeviceGet(App& app, const crow::Request& req,
330                         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
331                         const std::string& systemName,
332                         const std::string& pcieDeviceId)
333 {
334     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
335     {
336         return;
337     }
338     if (systemName != "system")
339     {
340         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
341                                    systemName);
342         return;
343     }
344 
345     getValidPCIeDevicePath(
346         pcieDeviceId, asyncResp,
347         [asyncResp, pcieDeviceId](const std::string& pcieDevicePath,
348                                   const std::string& service) {
349         addPCIeDeviceCommonProperties(asyncResp, pcieDeviceId);
350         getPCIeDeviceAsset(asyncResp, pcieDevicePath, service);
351         getPCIeDeviceState(asyncResp, pcieDevicePath, service);
352         getPCIeDeviceProperties(
353             asyncResp, pcieDevicePath, service,
354             [asyncResp, pcieDeviceId](
355                 const dbus::utility::DBusPropertiesMap& pcieDevProperties) {
356             addPCIeDeviceProperties(asyncResp->res, pcieDeviceId,
357                                     pcieDevProperties);
358             });
359         });
360 }
361 
362 inline void requestRoutesSystemPCIeDevice(App& app)
363 {
364     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/PCIeDevices/<str>/")
365         .privileges(redfish::privileges::getPCIeDevice)
366         .methods(boost::beast::http::verb::get)(
367             std::bind_front(handlePCIeDeviceGet, std::ref(app)));
368 }
369 
370 inline void addPCIeFunctionList(
371     crow::Response& res, const std::string& pcieDeviceId,
372     const dbus::utility::DBusPropertiesMap& pcieDevProperties)
373 {
374     nlohmann::json& pcieFunctionList = res.jsonValue["Members"];
375     pcieFunctionList = nlohmann::json::array();
376     static constexpr const int maxPciFunctionNum = 8;
377 
378     for (int functionNum = 0; functionNum < maxPciFunctionNum; functionNum++)
379     {
380         // Check if this function exists by
381         // looking for a device ID
382         std::string devIDProperty = "Function" + std::to_string(functionNum) +
383                                     "DeviceId";
384         const std::string* property = nullptr;
385         for (const auto& propEntry : pcieDevProperties)
386         {
387             if (propEntry.first == devIDProperty)
388             {
389                 property = std::get_if<std::string>(&propEntry.second);
390                 break;
391             }
392         }
393         if (property == nullptr || property->empty())
394         {
395             continue;
396         }
397 
398         nlohmann::json::object_t pcieFunction;
399         pcieFunction["@odata.id"] = boost::urls::format(
400             "/redfish/v1/Systems/system/PCIeDevices/{}/PCIeFunctions/{}",
401             pcieDeviceId, std::to_string(functionNum));
402         pcieFunctionList.emplace_back(std::move(pcieFunction));
403     }
404     res.jsonValue["PCIeFunctions@odata.count"] = pcieFunctionList.size();
405 }
406 
407 inline void handlePCIeFunctionCollectionGet(
408     App& app, const crow::Request& req,
409     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
410     const std::string& pcieDeviceId)
411 {
412     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
413     {
414         return;
415     }
416 
417     getValidPCIeDevicePath(
418         pcieDeviceId, asyncResp,
419         [asyncResp, pcieDeviceId](const std::string& pcieDevicePath,
420                                   const std::string& service) {
421         asyncResp->res.addHeader(
422             boost::beast::http::field::link,
423             "</redfish/v1/JsonSchemas/PCIeFunctionCollection/PCIeFunctionCollection.json>; rel=describedby");
424         asyncResp->res.jsonValue["@odata.type"] =
425             "#PCIeFunctionCollection.PCIeFunctionCollection";
426         asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
427             "/redfish/v1/Systems/system/PCIeDevices/{}/PCIeFunctions",
428             pcieDeviceId);
429         asyncResp->res.jsonValue["Name"] = "PCIe Function Collection";
430         asyncResp->res.jsonValue["Description"] =
431             "Collection of PCIe Functions for PCIe Device " + pcieDeviceId;
432         getPCIeDeviceProperties(
433             asyncResp, pcieDevicePath, service,
434             [asyncResp, pcieDeviceId](
435                 const dbus::utility::DBusPropertiesMap& pcieDevProperties) {
436             addPCIeFunctionList(asyncResp->res, pcieDeviceId,
437                                 pcieDevProperties);
438             });
439         });
440 }
441 
442 inline void requestRoutesSystemPCIeFunctionCollection(App& app)
443 {
444     /**
445      * Functions triggers appropriate requests on DBus
446      */
447     BMCWEB_ROUTE(app,
448                  "/redfish/v1/Systems/system/PCIeDevices/<str>/PCIeFunctions/")
449         .privileges(redfish::privileges::getPCIeFunctionCollection)
450         .methods(boost::beast::http::verb::get)(
451             std::bind_front(handlePCIeFunctionCollectionGet, std::ref(app)));
452 }
453 
454 inline bool validatePCIeFunctionId(
455     uint64_t pcieFunctionId,
456     const dbus::utility::DBusPropertiesMap& pcieDevProperties)
457 {
458     std::string functionName = "Function" + std::to_string(pcieFunctionId);
459     std::string devIDProperty = functionName + "DeviceId";
460 
461     const std::string* devIdProperty = nullptr;
462     for (const auto& property : pcieDevProperties)
463     {
464         if (property.first == devIDProperty)
465         {
466             devIdProperty = std::get_if<std::string>(&property.second);
467             break;
468         }
469     }
470     return (devIdProperty != nullptr && !devIdProperty->empty());
471 }
472 
473 inline void addPCIeFunctionProperties(
474     crow::Response& resp, uint64_t pcieFunctionId,
475     const dbus::utility::DBusPropertiesMap& pcieDevProperties)
476 {
477     std::string functionName = "Function" + std::to_string(pcieFunctionId);
478     for (const auto& property : pcieDevProperties)
479     {
480         const std::string* strProperty =
481             std::get_if<std::string>(&property.second);
482 
483         if (property.first == functionName + "DeviceId")
484         {
485             resp.jsonValue["DeviceId"] = *strProperty;
486         }
487         if (property.first == functionName + "VendorId")
488         {
489             resp.jsonValue["VendorId"] = *strProperty;
490         }
491         // TODO: FunctionType and DeviceClass are Redfish enums. The D-Bus
492         // property strings should be mapped correctly to ensure these
493         // strings are Redfish enum values. For now just check for empty.
494         if (property.first == functionName + "FunctionType")
495         {
496             if (!strProperty->empty())
497             {
498                 resp.jsonValue["FunctionType"] = *strProperty;
499             }
500         }
501         if (property.first == functionName + "DeviceClass")
502         {
503             if (!strProperty->empty())
504             {
505                 resp.jsonValue["DeviceClass"] = *strProperty;
506             }
507         }
508         if (property.first == functionName + "ClassCode")
509         {
510             resp.jsonValue["ClassCode"] = *strProperty;
511         }
512         if (property.first == functionName + "RevisionId")
513         {
514             resp.jsonValue["RevisionId"] = *strProperty;
515         }
516         if (property.first == functionName + "SubsystemId")
517         {
518             resp.jsonValue["SubsystemId"] = *strProperty;
519         }
520         if (property.first == functionName + "SubsystemVendorId")
521         {
522             resp.jsonValue["SubsystemVendorId"] = *strProperty;
523         }
524     }
525 }
526 
527 inline void addPCIeFunctionCommonProperties(crow::Response& resp,
528                                             const std::string& pcieDeviceId,
529                                             uint64_t pcieFunctionId)
530 {
531     resp.addHeader(
532         boost::beast::http::field::link,
533         "</redfish/v1/JsonSchemas/PCIeFunction/PCIeFunction.json>; rel=describedby");
534     resp.jsonValue["@odata.type"] = "#PCIeFunction.v1_2_3.PCIeFunction";
535     resp.jsonValue["@odata.id"] = boost::urls::format(
536         "/redfish/v1/Systems/system/PCIeDevices/{}/PCIeFunctions/{}",
537         pcieDeviceId, pcieFunctionId);
538     resp.jsonValue["Name"] = "PCIe Function";
539     resp.jsonValue["Id"] = std::to_string(pcieFunctionId);
540     resp.jsonValue["FunctionId"] = pcieFunctionId;
541     resp.jsonValue["Links"]["PCIeDevice"]["@odata.id"] = boost::urls::format(
542         "/redfish/v1/Systems/system/PCIeDevices/{}", pcieDeviceId);
543 }
544 
545 inline void
546     handlePCIeFunctionGet(App& app, const crow::Request& req,
547                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
548                           const std::string& pcieDeviceId,
549                           const std::string& pcieFunctionIdStr)
550 {
551     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
552     {
553         return;
554     }
555     uint64_t pcieFunctionId = 0;
556     std::from_chars_result result = std::from_chars(
557         &*pcieFunctionIdStr.begin(), &*pcieFunctionIdStr.end(), pcieFunctionId);
558     if (result.ec != std::errc{} || result.ptr != &*pcieFunctionIdStr.end())
559     {
560         messages::resourceNotFound(asyncResp->res, "PCIeFunction",
561                                    pcieFunctionIdStr);
562         return;
563     }
564 
565     getValidPCIeDevicePath(pcieDeviceId, asyncResp,
566                            [asyncResp, pcieDeviceId,
567                             pcieFunctionId](const std::string& pcieDevicePath,
568                                             const std::string& service) {
569         getPCIeDeviceProperties(
570             asyncResp, pcieDevicePath, service,
571             [asyncResp, pcieDeviceId, pcieFunctionId](
572                 const dbus::utility::DBusPropertiesMap& pcieDevProperties) {
573             addPCIeFunctionCommonProperties(asyncResp->res, pcieDeviceId,
574                                             pcieFunctionId);
575             addPCIeFunctionProperties(asyncResp->res, pcieFunctionId,
576                                       pcieDevProperties);
577             });
578     });
579 }
580 
581 inline void requestRoutesSystemPCIeFunction(App& app)
582 {
583     BMCWEB_ROUTE(
584         app,
585         "/redfish/v1/Systems/system/PCIeDevices/<str>/PCIeFunctions/<str>/")
586         .privileges(redfish::privileges::getPCIeFunction)
587         .methods(boost::beast::http::verb::get)(
588             std::bind_front(handlePCIeFunctionGet, std::ref(app)));
589 }
590 
591 } // namespace redfish
592