xref: /openbmc/bmcweb/redfish-core/lib/pcie.hpp (revision 8b2521a5)
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.PCIe.Device";
38 
39 static inline void
40     getPCIeDeviceList(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
41                       const std::string& name)
42 {
43     dbus::utility::getSubTreePaths(
44         pciePath, 1, {},
45         [asyncResp, name](const boost::system::error_code& ec,
46                           const dbus::utility::MapperGetSubTreePathsResponse&
47                               pcieDevicePaths) {
48         if (ec)
49         {
50             BMCWEB_LOG_DEBUG << "no PCIe device paths found ec: "
51                              << ec.message();
52             // Not an error, system just doesn't have PCIe info
53             return;
54         }
55         nlohmann::json& pcieDeviceList = asyncResp->res.jsonValue[name];
56         pcieDeviceList = nlohmann::json::array();
57         for (const std::string& pcieDevicePath : pcieDevicePaths)
58         {
59             size_t devStart = pcieDevicePath.rfind('/');
60             if (devStart == std::string::npos)
61             {
62                 continue;
63             }
64 
65             std::string devName = pcieDevicePath.substr(devStart + 1);
66             if (devName.empty())
67             {
68                 continue;
69             }
70             nlohmann::json::object_t pcieDevice;
71             pcieDevice["@odata.id"] = crow::utility::urlFromPieces(
72                 "redfish", "v1", "Systems", "system", "PCIeDevices", devName);
73             pcieDeviceList.push_back(std::move(pcieDevice));
74         }
75         asyncResp->res.jsonValue[name + "@odata.count"] = pcieDeviceList.size();
76         });
77 }
78 
79 static inline void handlePCIeDeviceCollectionGet(
80     crow::App& app, const crow::Request& req,
81     const std::shared_ptr<bmcweb::AsyncResp>& aResp,
82     const std::string& systemName)
83 {
84     if (!redfish::setUpRedfishRoute(app, req, aResp))
85     {
86         return;
87     }
88     if (systemName != "system")
89     {
90         messages::resourceNotFound(aResp->res, "ComputerSystem", systemName);
91         return;
92     }
93     aResp->res.addHeader(boost::beast::http::field::link,
94                          "</redfish/v1/JsonSchemas/PCIeDeviceCollection/"
95                          "PCIeDeviceCollection.json>; rel=describedby");
96     aResp->res.jsonValue["@odata.type"] =
97         "#PCIeDeviceCollection.PCIeDeviceCollection";
98     aResp->res.jsonValue["@odata.id"] =
99         "/redfish/v1/Systems/system/PCIeDevices";
100     aResp->res.jsonValue["Name"] = "PCIe Device Collection";
101     aResp->res.jsonValue["Description"] = "Collection of PCIe Devices";
102     aResp->res.jsonValue["Members"] = nlohmann::json::array();
103     aResp->res.jsonValue["Members@odata.count"] = 0;
104 
105     constexpr std::array<std::string_view, 1> interfaces{
106         "xyz.openbmc_project.Inventory.Item.PCIeDevice"};
107     collection_util::getCollectionMembers(
108         aResp, boost::urls::url("/redfish/v1/Systems/system/PCIeDevices"),
109         interfaces);
110 }
111 
112 inline void requestRoutesSystemPCIeDeviceCollection(App& app)
113 {
114     /**
115      * Functions triggers appropriate requests on DBus
116      */
117     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/PCIeDevices/")
118         .privileges(redfish::privileges::getPCIeDeviceCollection)
119         .methods(boost::beast::http::verb::get)(
120             std::bind_front(handlePCIeDeviceCollectionGet, std::ref(app)));
121 }
122 
123 inline std::optional<pcie_device::PCIeTypes>
124     redfishPcieGenerationFromDbus(const std::string& generationInUse)
125 {
126     if (generationInUse ==
127         "xyz.openbmc_project.Inventory.Item.PCIeSlot.Generations.Gen1")
128     {
129         return pcie_device::PCIeTypes::Gen1;
130     }
131     if (generationInUse ==
132         "xyz.openbmc_project.Inventory.Item.PCIeSlot.Generations.Gen2")
133     {
134         return pcie_device::PCIeTypes::Gen2;
135     }
136     if (generationInUse ==
137         "xyz.openbmc_project.Inventory.Item.PCIeSlot.Generations.Gen3")
138     {
139         return pcie_device::PCIeTypes::Gen3;
140     }
141     if (generationInUse ==
142         "xyz.openbmc_project.Inventory.Item.PCIeSlot.Generations.Gen4")
143     {
144         return pcie_device::PCIeTypes::Gen4;
145     }
146     if (generationInUse ==
147         "xyz.openbmc_project.Inventory.Item.PCIeSlot.Generations.Gen5")
148     {
149         return pcie_device::PCIeTypes::Gen5;
150     }
151     if (generationInUse.empty() ||
152         generationInUse ==
153             "xyz.openbmc_project.Inventory.Item.PCIeSlot.Generations.Unknown")
154     {
155         return pcie_device::PCIeTypes::Invalid;
156     }
157 
158     // The value is not unknown or Gen1-5, need return an internal error.
159     return std::nullopt;
160 }
161 
162 inline void requestRoutesSystemPCIeDevice(App& app)
163 {
164     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/PCIeDevices/<str>/")
165         .privileges(redfish::privileges::getPCIeDevice)
166         .methods(boost::beast::http::verb::get)(
167             [&app](const crow::Request& req,
168                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
169                    const std::string& systemName, const std::string& device) {
170         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
171         {
172             return;
173         }
174         if (systemName != "system")
175         {
176             messages::resourceNotFound(asyncResp->res, "ComputerSystem",
177                                        systemName);
178             return;
179         }
180 
181         auto getPCIeDeviceCallback =
182             [asyncResp, device](
183                 const boost::system::error_code& ec,
184                 const dbus::utility::DBusPropertiesMap& pcieDevProperties) {
185             if (ec)
186             {
187                 BMCWEB_LOG_DEBUG
188                     << "failed to get PCIe Device properties ec: " << ec.value()
189                     << ": " << ec.message();
190                 if (ec.value() ==
191                     boost::system::linux_error::bad_request_descriptor)
192                 {
193                     messages::resourceNotFound(asyncResp->res, "PCIeDevice",
194                                                device);
195                 }
196                 else
197                 {
198                     messages::internalError(asyncResp->res);
199                 }
200                 return;
201             }
202 
203             const std::string* manufacturer = nullptr;
204             const std::string* deviceType = nullptr;
205             const std::string* generationInUse = nullptr;
206             const size_t* lanesInUse = nullptr;
207 
208             const bool success = sdbusplus::unpackPropertiesNoThrow(
209                 dbus_utils::UnpackErrorPrinter(), pcieDevProperties,
210                 "Manufacturer", manufacturer, "DeviceType", deviceType,
211                 "LanesInUse", lanesInUse, "GenerationInUse", generationInUse);
212 
213             if (!success)
214             {
215                 messages::internalError(asyncResp->res);
216                 return;
217             }
218 
219             // The default value of LanesInUse is 0, and the field will be
220             // left as off if it is a default value.
221             if (lanesInUse != nullptr && *lanesInUse != 0)
222             {
223                 asyncResp->res.jsonValue["PCIeInterface"]["LanesInUse"] =
224                     *lanesInUse;
225             }
226 
227             if (generationInUse != nullptr)
228             {
229                 std::optional<pcie_device::PCIeTypes> redfishGenerationInUse =
230                     redfishPcieGenerationFromDbus(*generationInUse);
231                 if (!redfishGenerationInUse)
232                 {
233                     messages::internalError(asyncResp->res);
234                     return;
235                 }
236                 if (*redfishGenerationInUse != pcie_device::PCIeTypes::Invalid)
237                 {
238                     asyncResp->res.jsonValue["PCIeInterface"]["PCIeType"] =
239                         *redfishGenerationInUse;
240                 }
241             }
242 
243             if (manufacturer != nullptr)
244             {
245                 asyncResp->res.jsonValue["Manufacturer"] = *manufacturer;
246             }
247 
248             if (deviceType != nullptr)
249             {
250                 asyncResp->res.jsonValue["DeviceType"] = *deviceType;
251             }
252 
253             asyncResp->res.jsonValue["@odata.type"] =
254                 "#PCIeDevice.v1_4_0.PCIeDevice";
255             asyncResp->res.jsonValue["@odata.id"] =
256                 crow::utility::urlFromPieces("redfish", "v1", "Systems",
257                                              "system", "PCIeDevices", device);
258             asyncResp->res.jsonValue["Name"] = "PCIe Device";
259             asyncResp->res.jsonValue["Id"] = device;
260 
261             asyncResp->res.jsonValue["PCIeFunctions"]["@odata.id"] =
262                 crow::utility::urlFromPieces("redfish", "v1", "Systems",
263                                              "system", "PCIeDevices", device,
264                                              "PCIeFunctions");
265         };
266         std::string escapedPath = std::string(pciePath) + "/" + device;
267         dbus::utility::escapePathForDbus(escapedPath);
268         sdbusplus::asio::getAllProperties(
269             *crow::connections::systemBus, pcieService, escapedPath,
270             pcieDeviceInterface, std::move(getPCIeDeviceCallback));
271         });
272 }
273 
274 inline void requestRoutesSystemPCIeFunctionCollection(App& app)
275 {
276     /**
277      * Functions triggers appropriate requests on DBus
278      */
279     BMCWEB_ROUTE(app,
280                  "/redfish/v1/Systems/system/PCIeDevices/<str>/PCIeFunctions/")
281         .privileges(redfish::privileges::getPCIeFunctionCollection)
282         .methods(boost::beast::http::verb::get)(
283             [&app](const crow::Request& req,
284                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
285                    const std::string& device) {
286         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
287         {
288             return;
289         }
290 
291         asyncResp->res.jsonValue["@odata.type"] =
292             "#PCIeFunctionCollection.PCIeFunctionCollection";
293         asyncResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces(
294             "redfish", "v1", "Systems", "system", "PCIeDevices", device,
295             "PCIeFunctions");
296         asyncResp->res.jsonValue["Name"] = "PCIe Function Collection";
297         asyncResp->res.jsonValue["Description"] =
298             "Collection of PCIe Functions for PCIe Device " + device;
299 
300         auto getPCIeDeviceCallback =
301             [asyncResp, device](
302                 const boost::system::error_code& ec,
303                 const dbus::utility::DBusPropertiesMap& pcieDevProperties) {
304             if (ec)
305             {
306                 BMCWEB_LOG_DEBUG
307                     << "failed to get PCIe Device properties ec: " << ec.value()
308                     << ": " << ec.message();
309                 if (ec.value() ==
310                     boost::system::linux_error::bad_request_descriptor)
311                 {
312                     messages::resourceNotFound(asyncResp->res, "PCIeDevice",
313                                                device);
314                 }
315                 else
316                 {
317                     messages::internalError(asyncResp->res);
318                 }
319                 return;
320             }
321 
322             nlohmann::json& pcieFunctionList =
323                 asyncResp->res.jsonValue["Members"];
324             pcieFunctionList = nlohmann::json::array();
325             static constexpr const int maxPciFunctionNum = 8;
326             for (int functionNum = 0; functionNum < maxPciFunctionNum;
327                  functionNum++)
328             {
329                 // Check if this function exists by looking for a
330                 // device ID
331                 std::string devIDProperty =
332                     "Function" + std::to_string(functionNum) + "DeviceId";
333                 const std::string* property = nullptr;
334                 for (const auto& propEntry : pcieDevProperties)
335                 {
336                     if (propEntry.first == devIDProperty)
337                     {
338                         property = std::get_if<std::string>(&propEntry.second);
339                     }
340                 }
341                 if (property == nullptr || property->empty())
342                 {
343                     continue;
344                 }
345                 nlohmann::json::object_t pcieFunction;
346                 pcieFunction["@odata.id"] = crow::utility::urlFromPieces(
347                     "redfish", "v1", "Systems", "system", "PCIeDevices", device,
348                     "PCIeFunctions", std::to_string(functionNum));
349                 pcieFunctionList.push_back(std::move(pcieFunction));
350             }
351             asyncResp->res.jsonValue["Members@odata.count"] =
352                 pcieFunctionList.size();
353         };
354         std::string escapedPath = std::string(pciePath) + "/" + device;
355         dbus::utility::escapePathForDbus(escapedPath);
356         sdbusplus::asio::getAllProperties(
357             *crow::connections::systemBus, pcieService, escapedPath,
358             pcieDeviceInterface, std::move(getPCIeDeviceCallback));
359         });
360 }
361 
362 inline void requestRoutesSystemPCIeFunction(App& app)
363 {
364     BMCWEB_ROUTE(
365         app,
366         "/redfish/v1/Systems/system/PCIeDevices/<str>/PCIeFunctions/<str>/")
367         .privileges(redfish::privileges::getPCIeFunction)
368         .methods(boost::beast::http::verb::get)(
369             [&app](const crow::Request& req,
370                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
371                    const std::string& device, const std::string& function) {
372         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
373         {
374             return;
375         }
376         auto getPCIeDeviceCallback =
377             [asyncResp, device, function](
378                 const boost::system::error_code& ec,
379                 const dbus::utility::DBusPropertiesMap& pcieDevProperties) {
380             if (ec)
381             {
382                 BMCWEB_LOG_DEBUG
383                     << "failed to get PCIe Device properties ec: " << ec.value()
384                     << ": " << ec.message();
385                 if (ec.value() ==
386                     boost::system::linux_error::bad_request_descriptor)
387                 {
388                     messages::resourceNotFound(asyncResp->res, "PCIeDevice",
389                                                device);
390                 }
391                 else
392                 {
393                     messages::internalError(asyncResp->res);
394                 }
395                 return;
396             }
397 
398             // Check if this function exists by looking for a device
399             // ID
400             std::string functionName = "Function" + function;
401             std::string devIDProperty = functionName + "DeviceId";
402 
403             const std::string* devIdProperty = nullptr;
404             for (const auto& property : pcieDevProperties)
405             {
406                 if (property.first == devIDProperty)
407                 {
408                     devIdProperty = std::get_if<std::string>(&property.second);
409                     continue;
410                 }
411             }
412             if (devIdProperty == nullptr || devIdProperty->empty())
413             {
414                 messages::resourceNotFound(asyncResp->res, "PCIeFunction",
415                                            function);
416                 return;
417             }
418 
419             asyncResp->res.jsonValue["@odata.type"] =
420                 "#PCIeFunction.v1_2_0.PCIeFunction";
421             asyncResp->res.jsonValue["@odata.id"] =
422                 crow::utility::urlFromPieces("redfish", "v1", "Systems",
423                                              "system", "PCIeDevices", device,
424                                              "PCIeFunctions", function);
425             asyncResp->res.jsonValue["Name"] = "PCIe Function";
426             asyncResp->res.jsonValue["Id"] = function;
427             asyncResp->res.jsonValue["FunctionId"] = std::stoi(function);
428             asyncResp->res.jsonValue["Links"]["PCIeDevice"]["@odata.id"] =
429                 crow::utility::urlFromPieces("redfish", "v1", "Systems",
430                                              "system", "PCIeDevices", device);
431 
432             for (const auto& property : pcieDevProperties)
433             {
434                 const std::string* strProperty =
435                     std::get_if<std::string>(&property.second);
436                 if (property.first == functionName + "DeviceId")
437                 {
438                     asyncResp->res.jsonValue["DeviceId"] = *strProperty;
439                 }
440                 if (property.first == functionName + "VendorId")
441                 {
442                     asyncResp->res.jsonValue["VendorId"] = *strProperty;
443                 }
444                 if (property.first == functionName + "FunctionType")
445                 {
446                     asyncResp->res.jsonValue["FunctionType"] = *strProperty;
447                 }
448                 if (property.first == functionName + "DeviceClass")
449                 {
450                     asyncResp->res.jsonValue["DeviceClass"] = *strProperty;
451                 }
452                 if (property.first == functionName + "ClassCode")
453                 {
454                     asyncResp->res.jsonValue["ClassCode"] = *strProperty;
455                 }
456                 if (property.first == functionName + "RevisionId")
457                 {
458                     asyncResp->res.jsonValue["RevisionId"] = *strProperty;
459                 }
460                 if (property.first == functionName + "SubsystemId")
461                 {
462                     asyncResp->res.jsonValue["SubsystemId"] = *strProperty;
463                 }
464                 if (property.first == functionName + "SubsystemVendorId")
465                 {
466                     asyncResp->res.jsonValue["SubsystemVendorId"] =
467                         *strProperty;
468                 }
469             }
470         };
471         std::string escapedPath = std::string(pciePath) + "/" + device;
472         dbus::utility::escapePathForDbus(escapedPath);
473         sdbusplus::asio::getAllProperties(
474             *crow::connections::systemBus, pcieService, escapedPath,
475             pcieDeviceInterface, std::move(getPCIeDeviceCallback));
476         });
477 }
478 
479 } // namespace redfish
480