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