xref: /openbmc/bmcweb/features/redfish/lib/pcie.hpp (revision 35ad613d2846884a5d654acf31d56ab3a6255afa)
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 "generated/enums/pcie_device.hpp"
22 #include "query.hpp"
23 #include "registries/privilege_registry.hpp"
24 #include "utils/collection.hpp"
25 #include "utils/dbus_utils.hpp"
26 
27 #include <boost/system/linux_error.hpp>
28 #include <sdbusplus/asio/property.hpp>
29 #include <sdbusplus/unpack_properties.hpp>
30 
31 namespace redfish
32 {
33 
34 static constexpr char const* pcieService = "xyz.openbmc_project.PCIe";
35 static constexpr char const* pciePath = "/xyz/openbmc_project/PCIe";
36 static constexpr char const* 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>& aResp,
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, aResp,
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(aResp->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(aResp->res, "PCIeDevice", pcieDeviceId);
74 }
75 
76 static inline void getValidPCIeDevicePath(
77     const std::string& pcieDeviceId,
78     const std::shared_ptr<bmcweb::AsyncResp>& aResp,
79     const std::function<void(const std::string& pcieDevicePath,
80                              const std::string& service)>& callback)
81 {
82     constexpr std::array<std::string_view, 1> interfaces{
83         "xyz.openbmc_project.Inventory.Item.PCIeDevice"};
84 
85     dbus::utility::getSubTreePaths(
86         "/xyz/openbmc_project/inventory", 0, interfaces,
87         [pcieDeviceId, aResp,
88          callback](const boost::system::error_code& ec,
89                    const dbus::utility::MapperGetSubTreePathsResponse&
90                        pcieDevicePaths) {
91         if (ec)
92         {
93             BMCWEB_LOG_ERROR << "D-Bus response error on GetSubTree " << ec;
94             messages::internalError(aResp->res);
95             return;
96         }
97         handlePCIeDevicePath(pcieDeviceId, aResp, pcieDevicePaths, callback);
98         return;
99         });
100 }
101 
102 static inline void
103     getPCIeDeviceList(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
104                       const std::string& name)
105 {
106     dbus::utility::getSubTreePaths(
107         pciePath, 1, {},
108         [asyncResp, name](const boost::system::error_code& ec,
109                           const dbus::utility::MapperGetSubTreePathsResponse&
110                               pcieDevicePaths) {
111         if (ec)
112         {
113             BMCWEB_LOG_DEBUG << "no PCIe device paths found ec: "
114                              << ec.message();
115             // Not an error, system just doesn't have PCIe info
116             return;
117         }
118         nlohmann::json& pcieDeviceList = asyncResp->res.jsonValue[name];
119         pcieDeviceList = nlohmann::json::array();
120         for (const std::string& pcieDevicePath : pcieDevicePaths)
121         {
122             size_t devStart = pcieDevicePath.rfind('/');
123             if (devStart == std::string::npos)
124             {
125                 continue;
126             }
127 
128             std::string devName = pcieDevicePath.substr(devStart + 1);
129             if (devName.empty())
130             {
131                 continue;
132             }
133             nlohmann::json::object_t pcieDevice;
134             pcieDevice["@odata.id"] = crow::utility::urlFromPieces(
135                 "redfish", "v1", "Systems", "system", "PCIeDevices", devName);
136             pcieDeviceList.push_back(std::move(pcieDevice));
137         }
138         asyncResp->res.jsonValue[name + "@odata.count"] = pcieDeviceList.size();
139         });
140 }
141 
142 static inline void handlePCIeDeviceCollectionGet(
143     crow::App& app, const crow::Request& req,
144     const std::shared_ptr<bmcweb::AsyncResp>& aResp,
145     const std::string& systemName)
146 {
147     if (!redfish::setUpRedfishRoute(app, req, aResp))
148     {
149         return;
150     }
151     if (systemName != "system")
152     {
153         messages::resourceNotFound(aResp->res, "ComputerSystem", systemName);
154         return;
155     }
156 
157     aResp->res.addHeader(boost::beast::http::field::link,
158                          "</redfish/v1/JsonSchemas/PCIeDeviceCollection/"
159                          "PCIeDeviceCollection.json>; rel=describedby");
160     aResp->res.jsonValue["@odata.type"] =
161         "#PCIeDeviceCollection.PCIeDeviceCollection";
162     aResp->res.jsonValue["@odata.id"] =
163         "/redfish/v1/Systems/system/PCIeDevices";
164     aResp->res.jsonValue["Name"] = "PCIe Device Collection";
165     aResp->res.jsonValue["Description"] = "Collection of PCIe Devices";
166     aResp->res.jsonValue["Members"] = nlohmann::json::array();
167     aResp->res.jsonValue["Members@odata.count"] = 0;
168 
169     constexpr std::array<std::string_view, 1> interfaces{
170         "xyz.openbmc_project.Inventory.Item.PCIeDevice"};
171     collection_util::getCollectionMembers(
172         aResp, boost::urls::url("/redfish/v1/Systems/system/PCIeDevices"),
173         interfaces);
174 }
175 
176 inline void requestRoutesSystemPCIeDeviceCollection(App& app)
177 {
178     /**
179      * Functions triggers appropriate requests on DBus
180      */
181     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/PCIeDevices/")
182         .privileges(redfish::privileges::getPCIeDeviceCollection)
183         .methods(boost::beast::http::verb::get)(
184             std::bind_front(handlePCIeDeviceCollectionGet, std::ref(app)));
185 }
186 
187 inline std::optional<pcie_device::PCIeTypes>
188     redfishPcieGenerationFromDbus(const std::string& generationInUse)
189 {
190     if (generationInUse ==
191         "xyz.openbmc_project.Inventory.Item.PCIeSlot.Generations.Gen1")
192     {
193         return pcie_device::PCIeTypes::Gen1;
194     }
195     if (generationInUse ==
196         "xyz.openbmc_project.Inventory.Item.PCIeSlot.Generations.Gen2")
197     {
198         return pcie_device::PCIeTypes::Gen2;
199     }
200     if (generationInUse ==
201         "xyz.openbmc_project.Inventory.Item.PCIeSlot.Generations.Gen3")
202     {
203         return pcie_device::PCIeTypes::Gen3;
204     }
205     if (generationInUse ==
206         "xyz.openbmc_project.Inventory.Item.PCIeSlot.Generations.Gen4")
207     {
208         return pcie_device::PCIeTypes::Gen4;
209     }
210     if (generationInUse ==
211         "xyz.openbmc_project.Inventory.Item.PCIeSlot.Generations.Gen5")
212     {
213         return pcie_device::PCIeTypes::Gen5;
214     }
215     if (generationInUse.empty() ||
216         generationInUse ==
217             "xyz.openbmc_project.Inventory.Item.PCIeSlot.Generations.Unknown")
218     {
219         return pcie_device::PCIeTypes::Invalid;
220     }
221 
222     // The value is not unknown or Gen1-5, need return an internal error.
223     return std::nullopt;
224 }
225 
226 inline void addPCIeDeviceProperties(
227     crow::Response& resp, const std::string& pcieDeviceId,
228     const dbus::utility::DBusPropertiesMap& pcieDevProperties)
229 {
230     const std::string* manufacturer = nullptr;
231     const std::string* deviceType = nullptr;
232     const std::string* generationInUse = nullptr;
233     const int64_t* lanesInUse = nullptr;
234 
235     const bool success = sdbusplus::unpackPropertiesNoThrow(
236         dbus_utils::UnpackErrorPrinter(), pcieDevProperties, "DeviceType",
237         deviceType, "GenerationInUse", generationInUse, "LanesInUse",
238         lanesInUse, "Manufacturer", manufacturer);
239 
240     if (!success)
241     {
242         messages::internalError(resp);
243         return;
244     }
245 
246     if (deviceType != nullptr && !deviceType->empty())
247     {
248         resp.jsonValue["PCIeInterface"]["DeviceType"] = *deviceType;
249     }
250 
251     if (generationInUse != nullptr)
252     {
253         std::optional<pcie_device::PCIeTypes> redfishGenerationInUse =
254             redfishPcieGenerationFromDbus(*generationInUse);
255 
256         if (!redfishGenerationInUse)
257         {
258             messages::internalError(resp);
259             return;
260         }
261         if (*redfishGenerationInUse != pcie_device::PCIeTypes::Invalid)
262         {
263             resp.jsonValue["PCIeInterface"]["PCIeType"] =
264                 *redfishGenerationInUse;
265         }
266     }
267 
268     // The default value of LanesInUse is 0, and the field will be
269     // left as off if it is a default value.
270     if (lanesInUse != nullptr && *lanesInUse != 0)
271     {
272         resp.jsonValue["PCIeInterface"]["LanesInUse"] = *lanesInUse;
273     }
274 
275     if (manufacturer != nullptr)
276     {
277         resp.jsonValue["PCIeInterface"]["Manufacturer"] = *manufacturer;
278     }
279 
280     resp.jsonValue["PCIeFunctions"]["@odata.id"] = crow::utility::urlFromPieces(
281         "redfish", "v1", "Systems", "system", "PCIeDevices", pcieDeviceId,
282         "PCIeFunctions");
283 }
284 
285 inline void getPCIeDeviceProperties(
286     const std::shared_ptr<bmcweb::AsyncResp>& aResp,
287     const std::string& pcieDevicePath, const std::string& service,
288     const std::function<void(
289         const dbus::utility::DBusPropertiesMap& pcieDevProperties)>&& callback)
290 {
291     sdbusplus::asio::getAllProperties(
292         *crow::connections::systemBus, service, pcieDevicePath,
293         "xyz.openbmc_project.Inventory.Item.PCIeDevice",
294         [aResp,
295          callback](const boost::system::error_code& ec,
296                    const dbus::utility::DBusPropertiesMap& pcieDevProperties) {
297         if (ec)
298         {
299             if (ec.value() != EBADR)
300             {
301                 BMCWEB_LOG_ERROR << "DBUS response error for Properties";
302                 messages::internalError(aResp->res);
303             }
304             return;
305         }
306         callback(pcieDevProperties);
307         });
308 }
309 
310 inline void addPCIeDeviceCommonProperties(
311     const std::shared_ptr<bmcweb::AsyncResp>& aResp,
312     const std::string& pcieDeviceId)
313 {
314     aResp->res.addHeader(
315         boost::beast::http::field::link,
316         "</redfish/v1/JsonSchemas/PCIeDevice/PCIeDevice.json>; rel=describedby");
317     aResp->res.jsonValue["@odata.type"] = "#PCIeDevice.v1_9_0.PCIeDevice";
318     aResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces(
319         "redfish", "v1", "Systems", "system", "PCIeDevices", pcieDeviceId);
320     aResp->res.jsonValue["Name"] = "PCIe Device";
321     aResp->res.jsonValue["Id"] = pcieDeviceId;
322 }
323 
324 inline void handlePCIeDeviceGet(App& app, const crow::Request& req,
325                                 const std::shared_ptr<bmcweb::AsyncResp>& aResp,
326                                 const std::string& systemName,
327                                 const std::string& pcieDeviceId)
328 {
329     if (!redfish::setUpRedfishRoute(app, req, aResp))
330     {
331         return;
332     }
333     if (systemName != "system")
334     {
335         messages::resourceNotFound(aResp->res, "ComputerSystem", systemName);
336         return;
337     }
338 
339     getValidPCIeDevicePath(
340         pcieDeviceId, aResp,
341         [aResp, pcieDeviceId](const std::string& pcieDevicePath,
342                               const std::string& service) {
343         addPCIeDeviceCommonProperties(aResp, pcieDeviceId);
344         getPCIeDeviceProperties(
345             aResp, pcieDevicePath, service,
346             [aResp, pcieDeviceId](
347                 const dbus::utility::DBusPropertiesMap& pcieDevProperties) {
348             addPCIeDeviceProperties(aResp->res, pcieDeviceId,
349                                     pcieDevProperties);
350             });
351         });
352 }
353 
354 inline void requestRoutesSystemPCIeDevice(App& app)
355 {
356     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/PCIeDevices/<str>/")
357         .privileges(redfish::privileges::getPCIeDevice)
358         .methods(boost::beast::http::verb::get)(
359             std::bind_front(handlePCIeDeviceGet, std::ref(app)));
360 }
361 
362 inline void addPCIeFunctionList(
363     crow::Response& res, const std::string& pcieDeviceId,
364     const dbus::utility::DBusPropertiesMap& pcieDevProperties)
365 {
366     nlohmann::json& pcieFunctionList = res.jsonValue["Members"];
367     pcieFunctionList = nlohmann::json::array();
368     static constexpr const int maxPciFunctionNum = 8;
369 
370     for (int functionNum = 0; functionNum < maxPciFunctionNum; functionNum++)
371     {
372         // Check if this function exists by
373         // looking for a device ID
374         std::string devIDProperty =
375             "Function" + std::to_string(functionNum) + "DeviceId";
376         const std::string* property = nullptr;
377         for (const auto& propEntry : pcieDevProperties)
378         {
379             if (propEntry.first == devIDProperty)
380             {
381                 property = std::get_if<std::string>(&propEntry.second);
382                 break;
383             }
384         }
385         if (property == nullptr || property->empty())
386         {
387             continue;
388         }
389 
390         nlohmann::json::object_t pcieFunction;
391         pcieFunction["@odata.id"] = crow::utility::urlFromPieces(
392             "redfish", "v1", "Systems", "system", "PCIeDevices", pcieDeviceId,
393             "PCIeFunctions", std::to_string(functionNum));
394         pcieFunctionList.push_back(std::move(pcieFunction));
395     }
396     res.jsonValue["PCIeFunctions@odata.count"] = pcieFunctionList.size();
397 }
398 
399 inline void handlePCIeFunctionCollectionGet(
400     App& app, const crow::Request& req,
401     const std::shared_ptr<bmcweb::AsyncResp>& aResp,
402     const std::string& pcieDeviceId)
403 {
404     if (!redfish::setUpRedfishRoute(app, req, aResp))
405     {
406         return;
407     }
408 
409     getValidPCIeDevicePath(
410         pcieDeviceId, aResp,
411         [aResp, pcieDeviceId](const std::string& pcieDevicePath,
412                               const std::string& service) {
413         aResp->res.addHeader(
414             boost::beast::http::field::link,
415             "</redfish/v1/JsonSchemas/PCIeFunctionCollection/PCIeFunctionCollection.json>; rel=describedby");
416         aResp->res.jsonValue["@odata.type"] =
417             "#PCIeFunctionCollection.PCIeFunctionCollection";
418         aResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces(
419             "redfish", "v1", "Systems", "system", "PCIeDevices", pcieDeviceId,
420             "PCIeFunctions");
421         aResp->res.jsonValue["Name"] = "PCIe Function Collection";
422         aResp->res.jsonValue["Description"] =
423             "Collection of PCIe Functions for PCIe Device " + pcieDeviceId;
424         getPCIeDeviceProperties(
425             aResp, pcieDevicePath, service,
426             [aResp, pcieDeviceId](
427                 const dbus::utility::DBusPropertiesMap& pcieDevProperties) {
428             addPCIeFunctionList(aResp->res, pcieDeviceId, pcieDevProperties);
429             });
430         });
431 }
432 
433 inline void requestRoutesSystemPCIeFunctionCollection(App& app)
434 {
435     /**
436      * Functions triggers appropriate requests on DBus
437      */
438     BMCWEB_ROUTE(app,
439                  "/redfish/v1/Systems/system/PCIeDevices/<str>/PCIeFunctions/")
440         .privileges(redfish::privileges::getPCIeFunctionCollection)
441         .methods(boost::beast::http::verb::get)(
442             std::bind_front(handlePCIeFunctionCollectionGet, std::ref(app)));
443 }
444 
445 inline void requestRoutesSystemPCIeFunction(App& app)
446 {
447     BMCWEB_ROUTE(
448         app,
449         "/redfish/v1/Systems/system/PCIeDevices/<str>/PCIeFunctions/<str>/")
450         .privileges(redfish::privileges::getPCIeFunction)
451         .methods(boost::beast::http::verb::get)(
452             [&app](const crow::Request& req,
453                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
454                    const std::string& device, const std::string& function) {
455         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
456         {
457             return;
458         }
459         auto getPCIeDeviceCallback =
460             [asyncResp, device, function](
461                 const boost::system::error_code& ec,
462                 const dbus::utility::DBusPropertiesMap& pcieDevProperties) {
463             if (ec)
464             {
465                 BMCWEB_LOG_DEBUG
466                     << "failed to get PCIe Device properties ec: " << ec.value()
467                     << ": " << ec.message();
468                 if (ec.value() ==
469                     boost::system::linux_error::bad_request_descriptor)
470                 {
471                     messages::resourceNotFound(asyncResp->res, "PCIeDevice",
472                                                device);
473                 }
474                 else
475                 {
476                     messages::internalError(asyncResp->res);
477                 }
478                 return;
479             }
480 
481             // Check if this function exists by looking for a device
482             // ID
483             std::string functionName = "Function" + function;
484             std::string devIDProperty = functionName + "DeviceId";
485 
486             const std::string* devIdProperty = nullptr;
487             for (const auto& property : pcieDevProperties)
488             {
489                 if (property.first == devIDProperty)
490                 {
491                     devIdProperty = std::get_if<std::string>(&property.second);
492                     continue;
493                 }
494             }
495             if (devIdProperty == nullptr || devIdProperty->empty())
496             {
497                 messages::resourceNotFound(asyncResp->res, "PCIeFunction",
498                                            function);
499                 return;
500             }
501 
502             asyncResp->res.jsonValue["@odata.type"] =
503                 "#PCIeFunction.v1_2_0.PCIeFunction";
504             asyncResp->res.jsonValue["@odata.id"] =
505                 crow::utility::urlFromPieces("redfish", "v1", "Systems",
506                                              "system", "PCIeDevices", device,
507                                              "PCIeFunctions", function);
508             asyncResp->res.jsonValue["Name"] = "PCIe Function";
509             asyncResp->res.jsonValue["Id"] = function;
510             asyncResp->res.jsonValue["FunctionId"] = std::stoi(function);
511             asyncResp->res.jsonValue["Links"]["PCIeDevice"]["@odata.id"] =
512                 crow::utility::urlFromPieces("redfish", "v1", "Systems",
513                                              "system", "PCIeDevices", device);
514 
515             for (const auto& property : pcieDevProperties)
516             {
517                 const std::string* strProperty =
518                     std::get_if<std::string>(&property.second);
519                 if (property.first == functionName + "DeviceId")
520                 {
521                     asyncResp->res.jsonValue["DeviceId"] = *strProperty;
522                 }
523                 if (property.first == functionName + "VendorId")
524                 {
525                     asyncResp->res.jsonValue["VendorId"] = *strProperty;
526                 }
527                 if (property.first == functionName + "FunctionType")
528                 {
529                     asyncResp->res.jsonValue["FunctionType"] = *strProperty;
530                 }
531                 if (property.first == functionName + "DeviceClass")
532                 {
533                     asyncResp->res.jsonValue["DeviceClass"] = *strProperty;
534                 }
535                 if (property.first == functionName + "ClassCode")
536                 {
537                     asyncResp->res.jsonValue["ClassCode"] = *strProperty;
538                 }
539                 if (property.first == functionName + "RevisionId")
540                 {
541                     asyncResp->res.jsonValue["RevisionId"] = *strProperty;
542                 }
543                 if (property.first == functionName + "SubsystemId")
544                 {
545                     asyncResp->res.jsonValue["SubsystemId"] = *strProperty;
546                 }
547                 if (property.first == functionName + "SubsystemVendorId")
548                 {
549                     asyncResp->res.jsonValue["SubsystemVendorId"] =
550                         *strProperty;
551                 }
552             }
553         };
554         std::string escapedPath = std::string(pciePath) + "/" + device;
555         dbus::utility::escapePathForDbus(escapedPath);
556         sdbusplus::asio::getAllProperties(
557             *crow::connections::systemBus, pcieService, escapedPath,
558             pcieDeviceInterface, std::move(getPCIeDeviceCallback));
559         });
560 }
561 
562 } // namespace redfish
563