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