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