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