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 #pragma once 17 18 #include "app.hpp" 19 #include "dbus_singleton.hpp" 20 #include "dbus_utility.hpp" 21 #include "error_messages.hpp" 22 #include "health.hpp" 23 #include "query.hpp" 24 #include "registries/privilege_registry.hpp" 25 #include "utils/collection.hpp" 26 #include "utils/dbus_utils.hpp" 27 #include "utils/json_utils.hpp" 28 29 #include <boost/container/flat_map.hpp> 30 #include <boost/system/error_code.hpp> 31 #include <sdbusplus/asio/property.hpp> 32 #include <sdbusplus/message/native_types.hpp> 33 #include <sdbusplus/unpack_properties.hpp> 34 #include <sdbusplus/utility/dedup_variant.hpp> 35 36 #include <array> 37 #include <string_view> 38 39 namespace redfish 40 { 41 42 // Interfaces which imply a D-Bus object represents a Processor 43 constexpr std::array<std::string_view, 2> processorInterfaces = { 44 "xyz.openbmc_project.Inventory.Item.Cpu", 45 "xyz.openbmc_project.Inventory.Item.Accelerator"}; 46 47 /** 48 * @brief Fill out uuid info of a processor by 49 * requesting data from the given D-Bus object. 50 * 51 * @param[in,out] aResp Async HTTP response. 52 * @param[in] service D-Bus service to query. 53 * @param[in] objPath D-Bus object to query. 54 */ 55 inline void getProcessorUUID(std::shared_ptr<bmcweb::AsyncResp> aResp, 56 const std::string& service, 57 const std::string& objPath) 58 { 59 BMCWEB_LOG_DEBUG << "Get Processor UUID"; 60 sdbusplus::asio::getProperty<std::string>( 61 *crow::connections::systemBus, service, objPath, 62 "xyz.openbmc_project.Common.UUID", "UUID", 63 [objPath, aResp{std::move(aResp)}](const boost::system::error_code& ec, 64 const std::string& property) { 65 if (ec) 66 { 67 BMCWEB_LOG_DEBUG << "DBUS response error"; 68 messages::internalError(aResp->res); 69 return; 70 } 71 aResp->res.jsonValue["UUID"] = property; 72 }); 73 } 74 75 inline void getCpuDataByInterface( 76 const std::shared_ptr<bmcweb::AsyncResp>& aResp, 77 const dbus::utility::DBusInteracesMap& cpuInterfacesProperties) 78 { 79 BMCWEB_LOG_DEBUG << "Get CPU resources by interface."; 80 81 // Set the default value of state 82 aResp->res.jsonValue["Status"]["State"] = "Enabled"; 83 aResp->res.jsonValue["Status"]["Health"] = "OK"; 84 85 for (const auto& interface : cpuInterfacesProperties) 86 { 87 for (const auto& property : interface.second) 88 { 89 if (property.first == "Present") 90 { 91 const bool* cpuPresent = std::get_if<bool>(&property.second); 92 if (cpuPresent == nullptr) 93 { 94 // Important property not in desired type 95 messages::internalError(aResp->res); 96 return; 97 } 98 if (!*cpuPresent) 99 { 100 // Slot is not populated 101 aResp->res.jsonValue["Status"]["State"] = "Absent"; 102 } 103 } 104 else if (property.first == "Functional") 105 { 106 const bool* cpuFunctional = std::get_if<bool>(&property.second); 107 if (cpuFunctional == nullptr) 108 { 109 messages::internalError(aResp->res); 110 return; 111 } 112 if (!*cpuFunctional) 113 { 114 aResp->res.jsonValue["Status"]["Health"] = "Critical"; 115 } 116 } 117 else if (property.first == "CoreCount") 118 { 119 const uint16_t* coresCount = 120 std::get_if<uint16_t>(&property.second); 121 if (coresCount == nullptr) 122 { 123 messages::internalError(aResp->res); 124 return; 125 } 126 aResp->res.jsonValue["TotalCores"] = *coresCount; 127 } 128 else if (property.first == "MaxSpeedInMhz") 129 { 130 const uint32_t* value = std::get_if<uint32_t>(&property.second); 131 if (value != nullptr) 132 { 133 aResp->res.jsonValue["MaxSpeedMHz"] = *value; 134 } 135 } 136 else if (property.first == "Socket") 137 { 138 const std::string* value = 139 std::get_if<std::string>(&property.second); 140 if (value != nullptr) 141 { 142 aResp->res.jsonValue["Socket"] = *value; 143 } 144 } 145 else if (property.first == "ThreadCount") 146 { 147 const uint16_t* value = std::get_if<uint16_t>(&property.second); 148 if (value != nullptr) 149 { 150 aResp->res.jsonValue["TotalThreads"] = *value; 151 } 152 } 153 else if (property.first == "EffectiveFamily") 154 { 155 const uint16_t* value = std::get_if<uint16_t>(&property.second); 156 if (value != nullptr && *value != 2) 157 { 158 aResp->res.jsonValue["ProcessorId"]["EffectiveFamily"] = 159 "0x" + intToHexString(*value, 4); 160 } 161 } 162 else if (property.first == "EffectiveModel") 163 { 164 const uint16_t* value = std::get_if<uint16_t>(&property.second); 165 if (value == nullptr) 166 { 167 messages::internalError(aResp->res); 168 return; 169 } 170 if (*value != 0) 171 { 172 aResp->res.jsonValue["ProcessorId"]["EffectiveModel"] = 173 "0x" + intToHexString(*value, 4); 174 } 175 } 176 else if (property.first == "Id") 177 { 178 const uint64_t* value = std::get_if<uint64_t>(&property.second); 179 if (value != nullptr && *value != 0) 180 { 181 aResp->res 182 .jsonValue["ProcessorId"]["IdentificationRegisters"] = 183 "0x" + intToHexString(*value, 16); 184 } 185 } 186 else if (property.first == "Microcode") 187 { 188 const uint32_t* value = std::get_if<uint32_t>(&property.second); 189 if (value == nullptr) 190 { 191 messages::internalError(aResp->res); 192 return; 193 } 194 if (*value != 0) 195 { 196 aResp->res.jsonValue["ProcessorId"]["MicrocodeInfo"] = 197 "0x" + intToHexString(*value, 8); 198 } 199 } 200 else if (property.first == "Step") 201 { 202 const uint16_t* value = std::get_if<uint16_t>(&property.second); 203 if (value == nullptr) 204 { 205 messages::internalError(aResp->res); 206 return; 207 } 208 if (*value != 0) 209 { 210 aResp->res.jsonValue["ProcessorId"]["Step"] = 211 "0x" + intToHexString(*value, 4); 212 } 213 } 214 } 215 } 216 } 217 218 inline void getCpuDataByService(std::shared_ptr<bmcweb::AsyncResp> aResp, 219 const std::string& cpuId, 220 const std::string& service, 221 const std::string& objPath) 222 { 223 BMCWEB_LOG_DEBUG << "Get available system cpu resources by service."; 224 225 crow::connections::systemBus->async_method_call( 226 [cpuId, service, objPath, aResp{std::move(aResp)}]( 227 const boost::system::error_code& ec, 228 const dbus::utility::ManagedObjectType& dbusData) { 229 if (ec) 230 { 231 BMCWEB_LOG_DEBUG << "DBUS response error"; 232 messages::internalError(aResp->res); 233 return; 234 } 235 aResp->res.jsonValue["Id"] = cpuId; 236 aResp->res.jsonValue["Name"] = "Processor"; 237 aResp->res.jsonValue["ProcessorType"] = "CPU"; 238 239 bool slotPresent = false; 240 std::string corePath = objPath + "/core"; 241 size_t totalCores = 0; 242 for (const auto& object : dbusData) 243 { 244 if (object.first.str == objPath) 245 { 246 getCpuDataByInterface(aResp, object.second); 247 } 248 else if (object.first.str.starts_with(corePath)) 249 { 250 for (const auto& interface : object.second) 251 { 252 if (interface.first == "xyz.openbmc_project.Inventory.Item") 253 { 254 for (const auto& property : interface.second) 255 { 256 if (property.first == "Present") 257 { 258 const bool* present = 259 std::get_if<bool>(&property.second); 260 if (present != nullptr) 261 { 262 if (*present) 263 { 264 slotPresent = true; 265 totalCores++; 266 } 267 } 268 } 269 } 270 } 271 } 272 } 273 } 274 // In getCpuDataByInterface(), state and health are set 275 // based on the present and functional status. If core 276 // count is zero, then it has a higher precedence. 277 if (slotPresent) 278 { 279 if (totalCores == 0) 280 { 281 // Slot is not populated, set status end return 282 aResp->res.jsonValue["Status"]["State"] = "Absent"; 283 aResp->res.jsonValue["Status"]["Health"] = "OK"; 284 } 285 aResp->res.jsonValue["TotalCores"] = totalCores; 286 } 287 return; 288 }, 289 service, "/xyz/openbmc_project/inventory", 290 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 291 } 292 293 inline void getCpuAssetData(std::shared_ptr<bmcweb::AsyncResp> aResp, 294 const std::string& service, 295 const std::string& objPath) 296 { 297 BMCWEB_LOG_DEBUG << "Get Cpu Asset Data"; 298 sdbusplus::asio::getAllProperties( 299 *crow::connections::systemBus, service, objPath, 300 "xyz.openbmc_project.Inventory.Decorator.Asset", 301 [objPath, aResp{std::move(aResp)}]( 302 const boost::system::error_code& ec, 303 const dbus::utility::DBusPropertiesMap& properties) { 304 if (ec) 305 { 306 BMCWEB_LOG_DEBUG << "DBUS response error"; 307 messages::internalError(aResp->res); 308 return; 309 } 310 311 const std::string* serialNumber = nullptr; 312 const std::string* model = nullptr; 313 const std::string* manufacturer = nullptr; 314 const std::string* partNumber = nullptr; 315 const std::string* sparePartNumber = nullptr; 316 317 const bool success = sdbusplus::unpackPropertiesNoThrow( 318 dbus_utils::UnpackErrorPrinter(), properties, "SerialNumber", 319 serialNumber, "Model", model, "Manufacturer", manufacturer, 320 "PartNumber", partNumber, "SparePartNumber", sparePartNumber); 321 322 if (!success) 323 { 324 messages::internalError(aResp->res); 325 return; 326 } 327 328 if (serialNumber != nullptr && !serialNumber->empty()) 329 { 330 aResp->res.jsonValue["SerialNumber"] = *serialNumber; 331 } 332 333 if ((model != nullptr) && !model->empty()) 334 { 335 aResp->res.jsonValue["Model"] = *model; 336 } 337 338 if (manufacturer != nullptr) 339 { 340 aResp->res.jsonValue["Manufacturer"] = *manufacturer; 341 342 // Otherwise would be unexpected. 343 if (manufacturer->find("Intel") != std::string::npos) 344 { 345 aResp->res.jsonValue["ProcessorArchitecture"] = "x86"; 346 aResp->res.jsonValue["InstructionSet"] = "x86-64"; 347 } 348 else if (manufacturer->find("IBM") != std::string::npos) 349 { 350 aResp->res.jsonValue["ProcessorArchitecture"] = "Power"; 351 aResp->res.jsonValue["InstructionSet"] = "PowerISA"; 352 } 353 } 354 355 if (partNumber != nullptr) 356 { 357 aResp->res.jsonValue["PartNumber"] = *partNumber; 358 } 359 360 if (sparePartNumber != nullptr && !sparePartNumber->empty()) 361 { 362 aResp->res.jsonValue["SparePartNumber"] = *sparePartNumber; 363 } 364 }); 365 } 366 367 inline void getCpuRevisionData(std::shared_ptr<bmcweb::AsyncResp> aResp, 368 const std::string& service, 369 const std::string& objPath) 370 { 371 BMCWEB_LOG_DEBUG << "Get Cpu Revision Data"; 372 sdbusplus::asio::getAllProperties( 373 *crow::connections::systemBus, service, objPath, 374 "xyz.openbmc_project.Inventory.Decorator.Revision", 375 [objPath, aResp{std::move(aResp)}]( 376 const boost::system::error_code& ec, 377 const dbus::utility::DBusPropertiesMap& properties) { 378 if (ec) 379 { 380 BMCWEB_LOG_DEBUG << "DBUS response error"; 381 messages::internalError(aResp->res); 382 return; 383 } 384 385 const std::string* version = nullptr; 386 387 const bool success = sdbusplus::unpackPropertiesNoThrow( 388 dbus_utils::UnpackErrorPrinter(), properties, "Version", version); 389 390 if (!success) 391 { 392 messages::internalError(aResp->res); 393 return; 394 } 395 396 if (version != nullptr) 397 { 398 aResp->res.jsonValue["Version"] = *version; 399 } 400 }); 401 } 402 403 inline void getAcceleratorDataByService( 404 std::shared_ptr<bmcweb::AsyncResp> aResp, const std::string& acclrtrId, 405 const std::string& service, const std::string& objPath) 406 { 407 BMCWEB_LOG_DEBUG 408 << "Get available system Accelerator resources by service."; 409 sdbusplus::asio::getAllProperties( 410 *crow::connections::systemBus, service, objPath, "", 411 [acclrtrId, aResp{std::move(aResp)}]( 412 const boost::system::error_code& ec, 413 const dbus::utility::DBusPropertiesMap& properties) { 414 if (ec) 415 { 416 BMCWEB_LOG_DEBUG << "DBUS response error"; 417 messages::internalError(aResp->res); 418 return; 419 } 420 421 const bool* functional = nullptr; 422 const bool* present = nullptr; 423 424 const bool success = sdbusplus::unpackPropertiesNoThrow( 425 dbus_utils::UnpackErrorPrinter(), properties, "Functional", 426 functional, "Present", present); 427 428 if (!success) 429 { 430 messages::internalError(aResp->res); 431 return; 432 } 433 434 std::string state = "Enabled"; 435 std::string health = "OK"; 436 437 if (present != nullptr && !*present) 438 { 439 state = "Absent"; 440 } 441 442 if (functional != nullptr && !*functional) 443 { 444 if (state == "Enabled") 445 { 446 health = "Critical"; 447 } 448 } 449 450 aResp->res.jsonValue["Id"] = acclrtrId; 451 aResp->res.jsonValue["Name"] = "Processor"; 452 aResp->res.jsonValue["Status"]["State"] = state; 453 aResp->res.jsonValue["Status"]["Health"] = health; 454 aResp->res.jsonValue["ProcessorType"] = "Accelerator"; 455 }); 456 } 457 458 // OperatingConfig D-Bus Types 459 using TurboProfileProperty = std::vector<std::tuple<uint32_t, size_t>>; 460 using BaseSpeedPrioritySettingsProperty = 461 std::vector<std::tuple<uint32_t, std::vector<uint32_t>>>; 462 // uint32_t and size_t may or may not be the same type, requiring a dedup'd 463 // variant 464 465 /** 466 * Fill out the HighSpeedCoreIDs in a Processor resource from the given 467 * OperatingConfig D-Bus property. 468 * 469 * @param[in,out] aResp Async HTTP response. 470 * @param[in] baseSpeedSettings Full list of base speed priority groups, 471 * to use to determine the list of high 472 * speed cores. 473 */ 474 inline void highSpeedCoreIdsHandler( 475 const std::shared_ptr<bmcweb::AsyncResp>& aResp, 476 const BaseSpeedPrioritySettingsProperty& baseSpeedSettings) 477 { 478 // The D-Bus property does not indicate which bucket is the "high 479 // priority" group, so let's discern that by looking for the one with 480 // highest base frequency. 481 auto highPriorityGroup = baseSpeedSettings.cend(); 482 uint32_t highestBaseSpeed = 0; 483 for (auto it = baseSpeedSettings.cbegin(); it != baseSpeedSettings.cend(); 484 ++it) 485 { 486 const uint32_t baseFreq = std::get<uint32_t>(*it); 487 if (baseFreq > highestBaseSpeed) 488 { 489 highestBaseSpeed = baseFreq; 490 highPriorityGroup = it; 491 } 492 } 493 494 nlohmann::json& jsonCoreIds = aResp->res.jsonValue["HighSpeedCoreIDs"]; 495 jsonCoreIds = nlohmann::json::array(); 496 497 // There may not be any entries in the D-Bus property, so only populate 498 // if there was actually something there. 499 if (highPriorityGroup != baseSpeedSettings.cend()) 500 { 501 jsonCoreIds = std::get<std::vector<uint32_t>>(*highPriorityGroup); 502 } 503 } 504 505 /** 506 * Fill out OperatingConfig related items in a Processor resource by requesting 507 * data from the given D-Bus object. 508 * 509 * @param[in,out] aResp Async HTTP response. 510 * @param[in] cpuId CPU D-Bus name. 511 * @param[in] service D-Bus service to query. 512 * @param[in] objPath D-Bus object to query. 513 */ 514 inline void getCpuConfigData(const std::shared_ptr<bmcweb::AsyncResp>& aResp, 515 const std::string& cpuId, 516 const std::string& service, 517 const std::string& objPath) 518 { 519 BMCWEB_LOG_INFO << "Getting CPU operating configs for " << cpuId; 520 521 // First, GetAll CurrentOperatingConfig properties on the object 522 sdbusplus::asio::getAllProperties( 523 *crow::connections::systemBus, service, objPath, 524 "xyz.openbmc_project.Control.Processor.CurrentOperatingConfig", 525 [aResp, cpuId, 526 service](const boost::system::error_code& ec, 527 const dbus::utility::DBusPropertiesMap& properties) { 528 if (ec) 529 { 530 BMCWEB_LOG_WARNING << "D-Bus error: " << ec << ", " << ec.message(); 531 messages::internalError(aResp->res); 532 return; 533 } 534 535 nlohmann::json& json = aResp->res.jsonValue; 536 537 const sdbusplus::message::object_path* appliedConfig = nullptr; 538 const bool* baseSpeedPriorityEnabled = nullptr; 539 540 const bool success = sdbusplus::unpackPropertiesNoThrow( 541 dbus_utils::UnpackErrorPrinter(), properties, "AppliedConfig", 542 appliedConfig, "BaseSpeedPriorityEnabled", 543 baseSpeedPriorityEnabled); 544 545 if (!success) 546 { 547 messages::internalError(aResp->res); 548 return; 549 } 550 551 if (appliedConfig != nullptr) 552 { 553 const std::string& dbusPath = appliedConfig->str; 554 nlohmann::json::object_t operatingConfig; 555 operatingConfig["@odata.id"] = crow::utility::urlFromPieces( 556 "redfish", "v1", "Systems", "system", "Processors", cpuId, 557 "OperatingConfigs"); 558 json["OperatingConfigs"] = std::move(operatingConfig); 559 560 // Reuse the D-Bus config object name for the Redfish 561 // URI 562 size_t baseNamePos = dbusPath.rfind('/'); 563 if (baseNamePos == std::string::npos || 564 baseNamePos == (dbusPath.size() - 1)) 565 { 566 // If the AppliedConfig was somehow not a valid path, 567 // skip adding any more properties, since everything 568 // else is tied to this applied config. 569 messages::internalError(aResp->res); 570 return; 571 } 572 nlohmann::json::object_t appliedOperatingConfig; 573 appliedOperatingConfig["@odata.id"] = crow::utility::urlFromPieces( 574 "redfish", "v1", "Systems", "system", "Processors", cpuId, 575 "OperatingConfigs", dbusPath.substr(baseNamePos + 1)); 576 json["AppliedOperatingConfig"] = std::move(appliedOperatingConfig); 577 578 // Once we found the current applied config, queue another 579 // request to read the base freq core ids out of that 580 // config. 581 sdbusplus::asio::getProperty<BaseSpeedPrioritySettingsProperty>( 582 *crow::connections::systemBus, service, dbusPath, 583 "xyz.openbmc_project.Inventory.Item.Cpu." 584 "OperatingConfig", 585 "BaseSpeedPrioritySettings", 586 [aResp]( 587 const boost::system::error_code& ec2, 588 const BaseSpeedPrioritySettingsProperty& baseSpeedList) { 589 if (ec2) 590 { 591 BMCWEB_LOG_WARNING << "D-Bus Property Get error: " << ec2; 592 messages::internalError(aResp->res); 593 return; 594 } 595 596 highSpeedCoreIdsHandler(aResp, baseSpeedList); 597 }); 598 } 599 600 if (baseSpeedPriorityEnabled != nullptr) 601 { 602 json["BaseSpeedPriorityState"] = 603 *baseSpeedPriorityEnabled ? "Enabled" : "Disabled"; 604 } 605 }); 606 } 607 608 /** 609 * @brief Fill out location info of a processor by 610 * requesting data from the given D-Bus object. 611 * 612 * @param[in,out] aResp Async HTTP response. 613 * @param[in] service D-Bus service to query. 614 * @param[in] objPath D-Bus object to query. 615 */ 616 inline void getCpuLocationCode(std::shared_ptr<bmcweb::AsyncResp> aResp, 617 const std::string& service, 618 const std::string& objPath) 619 { 620 BMCWEB_LOG_DEBUG << "Get Cpu Location Data"; 621 sdbusplus::asio::getProperty<std::string>( 622 *crow::connections::systemBus, service, objPath, 623 "xyz.openbmc_project.Inventory.Decorator.LocationCode", "LocationCode", 624 [objPath, aResp{std::move(aResp)}](const boost::system::error_code& ec, 625 const std::string& property) { 626 if (ec) 627 { 628 BMCWEB_LOG_DEBUG << "DBUS response error"; 629 messages::internalError(aResp->res); 630 return; 631 } 632 633 aResp->res.jsonValue["Location"]["PartLocation"]["ServiceLabel"] = 634 property; 635 }); 636 } 637 638 /** 639 * Populate the unique identifier in a Processor resource by requesting data 640 * from the given D-Bus object. 641 * 642 * @param[in,out] aResp Async HTTP response. 643 * @param[in] service D-Bus service to query. 644 * @param[in] objPath D-Bus object to query. 645 */ 646 inline void getCpuUniqueId(const std::shared_ptr<bmcweb::AsyncResp>& aResp, 647 const std::string& service, 648 const std::string& objectPath) 649 { 650 BMCWEB_LOG_DEBUG << "Get CPU UniqueIdentifier"; 651 sdbusplus::asio::getProperty<std::string>( 652 *crow::connections::systemBus, service, objectPath, 653 "xyz.openbmc_project.Inventory.Decorator.UniqueIdentifier", 654 "UniqueIdentifier", 655 [aResp](const boost::system::error_code& ec, const std::string& id) { 656 if (ec) 657 { 658 BMCWEB_LOG_ERROR << "Failed to read cpu unique id: " << ec; 659 messages::internalError(aResp->res); 660 return; 661 } 662 aResp->res.jsonValue["ProcessorId"]["ProtectedIdentificationNumber"] = 663 id; 664 }); 665 } 666 667 /** 668 * Find the D-Bus object representing the requested Processor, and call the 669 * handler with the results. If matching object is not found, add 404 error to 670 * response and don't call the handler. 671 * 672 * @param[in,out] resp Async HTTP response. 673 * @param[in] processorId Redfish Processor Id. 674 * @param[in] handler Callback to continue processing request upon 675 * successfully finding object. 676 */ 677 template <typename Handler> 678 inline void getProcessorObject(const std::shared_ptr<bmcweb::AsyncResp>& resp, 679 const std::string& processorId, 680 Handler&& handler) 681 { 682 BMCWEB_LOG_DEBUG << "Get available system processor resources."; 683 684 // GetSubTree on all interfaces which provide info about a Processor 685 constexpr std::array<std::string_view, 8> interfaces = { 686 "xyz.openbmc_project.Common.UUID", 687 "xyz.openbmc_project.Inventory.Decorator.Asset", 688 "xyz.openbmc_project.Inventory.Decorator.Revision", 689 "xyz.openbmc_project.Inventory.Item.Cpu", 690 "xyz.openbmc_project.Inventory.Decorator.LocationCode", 691 "xyz.openbmc_project.Inventory.Item.Accelerator", 692 "xyz.openbmc_project.Control.Processor.CurrentOperatingConfig", 693 "xyz.openbmc_project.Inventory.Decorator.UniqueIdentifier"}; 694 dbus::utility::getSubTree( 695 "/xyz/openbmc_project/inventory", 0, interfaces, 696 [resp, processorId, handler = std::forward<Handler>(handler)]( 697 const boost::system::error_code& ec, 698 const dbus::utility::MapperGetSubTreeResponse& subtree) { 699 if (ec) 700 { 701 BMCWEB_LOG_DEBUG << "DBUS response error: " << ec; 702 messages::internalError(resp->res); 703 return; 704 } 705 for (const auto& [objectPath, serviceMap] : subtree) 706 { 707 // Ignore any objects which don't end with our desired cpu name 708 if (!objectPath.ends_with(processorId)) 709 { 710 continue; 711 } 712 713 bool found = false; 714 // Filter out objects that don't have the CPU-specific 715 // interfaces to make sure we can return 404 on non-CPUs 716 // (e.g. /redfish/../Processors/dimm0) 717 for (const auto& [serviceName, interfaceList] : serviceMap) 718 { 719 if (std::find_first_of( 720 interfaceList.begin(), interfaceList.end(), 721 processorInterfaces.begin(), 722 processorInterfaces.end()) != interfaceList.end()) 723 { 724 found = true; 725 break; 726 } 727 } 728 729 if (!found) 730 { 731 continue; 732 } 733 734 // Process the first object which does match our cpu name and 735 // required interfaces, and potentially ignore any other 736 // matching objects. Assume all interfaces we want to process 737 // must be on the same object path. 738 739 handler(objectPath, serviceMap); 740 return; 741 } 742 messages::resourceNotFound(resp->res, "Processor", processorId); 743 }); 744 } 745 746 inline void getProcessorData(const std::shared_ptr<bmcweb::AsyncResp>& aResp, 747 const std::string& processorId, 748 const std::string& objectPath, 749 const dbus::utility::MapperServiceMap& serviceMap) 750 { 751 for (const auto& [serviceName, interfaceList] : serviceMap) 752 { 753 for (const auto& interface : interfaceList) 754 { 755 if (interface == "xyz.openbmc_project.Inventory.Decorator.Asset") 756 { 757 getCpuAssetData(aResp, serviceName, objectPath); 758 } 759 else if (interface == 760 "xyz.openbmc_project.Inventory.Decorator.Revision") 761 { 762 getCpuRevisionData(aResp, serviceName, objectPath); 763 } 764 else if (interface == "xyz.openbmc_project.Inventory.Item.Cpu") 765 { 766 getCpuDataByService(aResp, processorId, serviceName, 767 objectPath); 768 } 769 else if (interface == 770 "xyz.openbmc_project.Inventory.Item.Accelerator") 771 { 772 getAcceleratorDataByService(aResp, processorId, serviceName, 773 objectPath); 774 } 775 else if ( 776 interface == 777 "xyz.openbmc_project.Control.Processor.CurrentOperatingConfig") 778 { 779 getCpuConfigData(aResp, processorId, serviceName, objectPath); 780 } 781 else if (interface == 782 "xyz.openbmc_project.Inventory.Decorator.LocationCode") 783 { 784 getCpuLocationCode(aResp, serviceName, objectPath); 785 } 786 else if (interface == "xyz.openbmc_project.Common.UUID") 787 { 788 getProcessorUUID(aResp, serviceName, objectPath); 789 } 790 else if (interface == 791 "xyz.openbmc_project.Inventory.Decorator.UniqueIdentifier") 792 { 793 getCpuUniqueId(aResp, serviceName, objectPath); 794 } 795 } 796 } 797 } 798 799 /** 800 * Request all the properties for the given D-Bus object and fill out the 801 * related entries in the Redfish OperatingConfig response. 802 * 803 * @param[in,out] aResp Async HTTP response. 804 * @param[in] service D-Bus service name to query. 805 * @param[in] objPath D-Bus object to query. 806 */ 807 inline void 808 getOperatingConfigData(const std::shared_ptr<bmcweb::AsyncResp>& aResp, 809 const std::string& service, 810 const std::string& objPath) 811 { 812 sdbusplus::asio::getAllProperties( 813 *crow::connections::systemBus, service, objPath, 814 "xyz.openbmc_project.Inventory.Item.Cpu.OperatingConfig", 815 [aResp](const boost::system::error_code& ec, 816 const dbus::utility::DBusPropertiesMap& properties) { 817 if (ec) 818 { 819 BMCWEB_LOG_WARNING << "D-Bus error: " << ec << ", " << ec.message(); 820 messages::internalError(aResp->res); 821 return; 822 } 823 824 const size_t* availableCoreCount = nullptr; 825 const uint32_t* baseSpeed = nullptr; 826 const uint32_t* maxJunctionTemperature = nullptr; 827 const uint32_t* maxSpeed = nullptr; 828 const uint32_t* powerLimit = nullptr; 829 const TurboProfileProperty* turboProfile = nullptr; 830 const BaseSpeedPrioritySettingsProperty* baseSpeedPrioritySettings = 831 nullptr; 832 833 const bool success = sdbusplus::unpackPropertiesNoThrow( 834 dbus_utils::UnpackErrorPrinter(), properties, "AvailableCoreCount", 835 availableCoreCount, "BaseSpeed", baseSpeed, 836 "MaxJunctionTemperature", maxJunctionTemperature, "MaxSpeed", 837 maxSpeed, "PowerLimit", powerLimit, "TurboProfile", turboProfile, 838 "BaseSpeedPrioritySettings", baseSpeedPrioritySettings); 839 840 if (!success) 841 { 842 messages::internalError(aResp->res); 843 return; 844 } 845 846 nlohmann::json& json = aResp->res.jsonValue; 847 848 if (availableCoreCount != nullptr) 849 { 850 json["TotalAvailableCoreCount"] = *availableCoreCount; 851 } 852 853 if (baseSpeed != nullptr) 854 { 855 json["BaseSpeedMHz"] = *baseSpeed; 856 } 857 858 if (maxJunctionTemperature != nullptr) 859 { 860 json["MaxJunctionTemperatureCelsius"] = *maxJunctionTemperature; 861 } 862 863 if (maxSpeed != nullptr) 864 { 865 json["MaxSpeedMHz"] = *maxSpeed; 866 } 867 868 if (powerLimit != nullptr) 869 { 870 json["TDPWatts"] = *powerLimit; 871 } 872 873 if (turboProfile != nullptr) 874 { 875 nlohmann::json& turboArray = json["TurboProfile"]; 876 turboArray = nlohmann::json::array(); 877 for (const auto& [turboSpeed, coreCount] : *turboProfile) 878 { 879 nlohmann::json::object_t turbo; 880 turbo["ActiveCoreCount"] = coreCount; 881 turbo["MaxSpeedMHz"] = turboSpeed; 882 turboArray.push_back(std::move(turbo)); 883 } 884 } 885 886 if (baseSpeedPrioritySettings != nullptr) 887 { 888 nlohmann::json& baseSpeedArray = json["BaseSpeedPrioritySettings"]; 889 baseSpeedArray = nlohmann::json::array(); 890 for (const auto& [baseSpeedMhz, coreList] : 891 *baseSpeedPrioritySettings) 892 { 893 nlohmann::json::object_t speed; 894 speed["CoreCount"] = coreList.size(); 895 speed["CoreIDs"] = coreList; 896 speed["BaseSpeedMHz"] = baseSpeedMhz; 897 baseSpeedArray.push_back(std::move(speed)); 898 } 899 } 900 }); 901 } 902 903 /** 904 * Handle the D-Bus response from attempting to set the CPU's AppliedConfig 905 * property. Main task is to translate error messages into Redfish errors. 906 * 907 * @param[in,out] resp HTTP response. 908 * @param[in] setPropVal Value which we attempted to set. 909 * @param[in] ec D-Bus response error code. 910 * @param[in] msg D-Bus response message. 911 */ 912 inline void 913 handleAppliedConfigResponse(const std::shared_ptr<bmcweb::AsyncResp>& resp, 914 const std::string& setPropVal, 915 const boost::system::error_code& ec, 916 const sdbusplus::message_t& msg) 917 { 918 if (!ec) 919 { 920 BMCWEB_LOG_DEBUG << "Set Property succeeded"; 921 return; 922 } 923 924 BMCWEB_LOG_DEBUG << "Set Property failed: " << ec; 925 926 const sd_bus_error* dbusError = msg.get_error(); 927 if (dbusError == nullptr) 928 { 929 messages::internalError(resp->res); 930 return; 931 } 932 933 // The asio error code doesn't know about our custom errors, so we have to 934 // parse the error string. Some of these D-Bus -> Redfish translations are a 935 // stretch, but it's good to try to communicate something vaguely useful. 936 if (strcmp(dbusError->name, 937 "xyz.openbmc_project.Common.Error.InvalidArgument") == 0) 938 { 939 // Service did not like the object_path we tried to set. 940 messages::propertyValueIncorrect( 941 resp->res, "AppliedOperatingConfig/@odata.id", setPropVal); 942 } 943 else if (strcmp(dbusError->name, 944 "xyz.openbmc_project.Common.Error.NotAllowed") == 0) 945 { 946 // Service indicates we can never change the config for this processor. 947 messages::propertyNotWritable(resp->res, "AppliedOperatingConfig"); 948 } 949 else if (strcmp(dbusError->name, 950 "xyz.openbmc_project.Common.Error.Unavailable") == 0) 951 { 952 // Service indicates the config cannot be changed right now, but maybe 953 // in a different system state. 954 messages::resourceInStandby(resp->res); 955 } 956 else 957 { 958 messages::internalError(resp->res); 959 } 960 } 961 962 /** 963 * Handle the PATCH operation of the AppliedOperatingConfig property. Do basic 964 * validation of the input data, and then set the D-Bus property. 965 * 966 * @param[in,out] resp Async HTTP response. 967 * @param[in] processorId Processor's Id. 968 * @param[in] appliedConfigUri New property value to apply. 969 * @param[in] cpuObjectPath Path of CPU object to modify. 970 * @param[in] serviceMap Service map for CPU object. 971 */ 972 inline void patchAppliedOperatingConfig( 973 const std::shared_ptr<bmcweb::AsyncResp>& resp, 974 const std::string& processorId, const std::string& appliedConfigUri, 975 const std::string& cpuObjectPath, 976 const dbus::utility::MapperServiceMap& serviceMap) 977 { 978 // Check that the property even exists by checking for the interface 979 const std::string* controlService = nullptr; 980 for (const auto& [serviceName, interfaceList] : serviceMap) 981 { 982 if (std::find(interfaceList.begin(), interfaceList.end(), 983 "xyz.openbmc_project.Control.Processor." 984 "CurrentOperatingConfig") != interfaceList.end()) 985 { 986 controlService = &serviceName; 987 break; 988 } 989 } 990 991 if (controlService == nullptr) 992 { 993 messages::internalError(resp->res); 994 return; 995 } 996 997 // Check that the config URI is a child of the cpu URI being patched. 998 std::string expectedPrefix("/redfish/v1/Systems/system/Processors/"); 999 expectedPrefix += processorId; 1000 expectedPrefix += "/OperatingConfigs/"; 1001 if (!appliedConfigUri.starts_with(expectedPrefix) || 1002 expectedPrefix.size() == appliedConfigUri.size()) 1003 { 1004 messages::propertyValueIncorrect( 1005 resp->res, "AppliedOperatingConfig/@odata.id", appliedConfigUri); 1006 return; 1007 } 1008 1009 // Generate the D-Bus path of the OperatingConfig object, by assuming it's a 1010 // direct child of the CPU object. 1011 // Strip the expectedPrefix from the config URI to get the "filename", and 1012 // append to the CPU's path. 1013 std::string configBaseName = appliedConfigUri.substr(expectedPrefix.size()); 1014 sdbusplus::message::object_path configPath(cpuObjectPath); 1015 configPath /= configBaseName; 1016 1017 BMCWEB_LOG_INFO << "Setting config to " << configPath.str; 1018 1019 // Set the property, with handler to check error responses 1020 crow::connections::systemBus->async_method_call( 1021 [resp, appliedConfigUri](const boost::system::error_code& ec, 1022 const sdbusplus::message_t& msg) { 1023 handleAppliedConfigResponse(resp, appliedConfigUri, ec, msg); 1024 }, 1025 *controlService, cpuObjectPath, "org.freedesktop.DBus.Properties", 1026 "Set", "xyz.openbmc_project.Control.Processor.CurrentOperatingConfig", 1027 "AppliedConfig", dbus::utility::DbusVariantType(std::move(configPath))); 1028 } 1029 1030 inline void handleProcessorHead(crow::App& app, const crow::Request& req, 1031 const std::shared_ptr<bmcweb::AsyncResp>& aResp, 1032 const std::string& /* systemName */, 1033 const std::string& /* processorId */) 1034 { 1035 if (!redfish::setUpRedfishRoute(app, req, aResp)) 1036 { 1037 return; 1038 } 1039 aResp->res.addHeader( 1040 boost::beast::http::field::link, 1041 "</redfish/v1/JsonSchemas/Processor/Processor.json>; rel=describedby"); 1042 } 1043 1044 inline void handleProcessorCollectionHead( 1045 crow::App& app, const crow::Request& req, 1046 const std::shared_ptr<bmcweb::AsyncResp>& aResp, 1047 const std::string& /* systemName */) 1048 { 1049 if (!redfish::setUpRedfishRoute(app, req, aResp)) 1050 { 1051 return; 1052 } 1053 aResp->res.addHeader( 1054 boost::beast::http::field::link, 1055 "</redfish/v1/JsonSchemas/ProcessorCollection/ProcessorCollection.json>; rel=describedby"); 1056 } 1057 1058 inline void requestRoutesOperatingConfigCollection(App& app) 1059 { 1060 1061 BMCWEB_ROUTE( 1062 app, "/redfish/v1/Systems/system/Processors/<str>/OperatingConfigs/") 1063 .privileges(redfish::privileges::getOperatingConfigCollection) 1064 .methods(boost::beast::http::verb::get)( 1065 [&app](const crow::Request& req, 1066 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1067 const std::string& cpuName) { 1068 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1069 { 1070 return; 1071 } 1072 asyncResp->res.jsonValue["@odata.type"] = 1073 "#OperatingConfigCollection.OperatingConfigCollection"; 1074 asyncResp->res.jsonValue["@odata.id"] = req.url; 1075 asyncResp->res.jsonValue["Name"] = "Operating Config Collection"; 1076 1077 // First find the matching CPU object so we know how to 1078 // constrain our search for related Config objects. 1079 const std::array<std::string_view, 1> interfaces = { 1080 "xyz.openbmc_project.Control.Processor.CurrentOperatingConfig"}; 1081 dbus::utility::getSubTreePaths( 1082 "/xyz/openbmc_project/inventory", 0, interfaces, 1083 [asyncResp, cpuName]( 1084 const boost::system::error_code& ec, 1085 const dbus::utility::MapperGetSubTreePathsResponse& objects) { 1086 if (ec) 1087 { 1088 BMCWEB_LOG_WARNING << "D-Bus error: " << ec << ", " 1089 << ec.message(); 1090 messages::internalError(asyncResp->res); 1091 return; 1092 } 1093 1094 for (const std::string& object : objects) 1095 { 1096 if (!object.ends_with(cpuName)) 1097 { 1098 continue; 1099 } 1100 1101 // Not expected that there will be multiple matching 1102 // CPU objects, but if there are just use the first 1103 // one. 1104 1105 // Use the common search routine to construct the 1106 // Collection of all Config objects under this CPU. 1107 constexpr std::array<std::string_view, 1> interface { 1108 "xyz.openbmc_project.Inventory.Item.Cpu.OperatingConfig" 1109 }; 1110 collection_util::getCollectionMembers( 1111 asyncResp, 1112 crow::utility::urlFromPieces("redfish", "v1", "Systems", 1113 "system", "Processors", 1114 cpuName, "OperatingConfigs"), 1115 interface, object.c_str()); 1116 return; 1117 } 1118 }); 1119 }); 1120 } 1121 1122 inline void requestRoutesOperatingConfig(App& app) 1123 { 1124 BMCWEB_ROUTE( 1125 app, 1126 "/redfish/v1/Systems/system/Processors/<str>/OperatingConfigs/<str>/") 1127 .privileges(redfish::privileges::getOperatingConfig) 1128 .methods(boost::beast::http::verb::get)( 1129 [&app](const crow::Request& req, 1130 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1131 const std::string& cpuName, const std::string& configName) { 1132 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1133 { 1134 return; 1135 } 1136 // Ask for all objects implementing OperatingConfig so we can search 1137 // for one with a matching name 1138 constexpr std::array<std::string_view, 1> interfaces = { 1139 "xyz.openbmc_project.Inventory.Item.Cpu.OperatingConfig"}; 1140 dbus::utility::getSubTree( 1141 "/xyz/openbmc_project/inventory", 0, interfaces, 1142 [asyncResp, cpuName, configName, reqUrl{req.url}]( 1143 const boost::system::error_code& ec, 1144 const dbus::utility::MapperGetSubTreeResponse& subtree) { 1145 if (ec) 1146 { 1147 BMCWEB_LOG_WARNING << "D-Bus error: " << ec << ", " 1148 << ec.message(); 1149 messages::internalError(asyncResp->res); 1150 return; 1151 } 1152 const std::string expectedEnding = cpuName + '/' + configName; 1153 for (const auto& [objectPath, serviceMap] : subtree) 1154 { 1155 // Ignore any configs without matching cpuX/configY 1156 if (!objectPath.ends_with(expectedEnding) || serviceMap.empty()) 1157 { 1158 continue; 1159 } 1160 1161 nlohmann::json& json = asyncResp->res.jsonValue; 1162 json["@odata.type"] = "#OperatingConfig.v1_0_0.OperatingConfig"; 1163 json["@odata.id"] = reqUrl; 1164 json["Name"] = "Processor Profile"; 1165 json["Id"] = configName; 1166 1167 // Just use the first implementation of the object - not 1168 // expected that there would be multiple matching 1169 // services 1170 getOperatingConfigData(asyncResp, serviceMap.begin()->first, 1171 objectPath); 1172 return; 1173 } 1174 messages::resourceNotFound(asyncResp->res, "OperatingConfig", 1175 configName); 1176 }); 1177 }); 1178 } 1179 1180 inline void requestRoutesProcessorCollection(App& app) 1181 { 1182 /** 1183 * Functions triggers appropriate requests on DBus 1184 */ 1185 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Processors/") 1186 .privileges(redfish::privileges::headProcessorCollection) 1187 .methods(boost::beast::http::verb::head)( 1188 std::bind_front(handleProcessorCollectionHead, std::ref(app))); 1189 1190 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Processors/") 1191 .privileges(redfish::privileges::getProcessorCollection) 1192 .methods(boost::beast::http::verb::get)( 1193 [&app](const crow::Request& req, 1194 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1195 const std::string& systemName) { 1196 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1197 { 1198 return; 1199 } 1200 if (systemName != "system") 1201 { 1202 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 1203 systemName); 1204 return; 1205 } 1206 1207 asyncResp->res.addHeader( 1208 boost::beast::http::field::link, 1209 "</redfish/v1/JsonSchemas/ProcessorCollection/ProcessorCollection.json>; rel=describedby"); 1210 1211 asyncResp->res.jsonValue["@odata.type"] = 1212 "#ProcessorCollection.ProcessorCollection"; 1213 asyncResp->res.jsonValue["Name"] = "Processor Collection"; 1214 1215 asyncResp->res.jsonValue["@odata.id"] = 1216 "/redfish/v1/Systems/system/Processors"; 1217 1218 collection_util::getCollectionMembers( 1219 asyncResp, 1220 boost::urls::url("/redfish/v1/Systems/system/Processors"), 1221 processorInterfaces); 1222 }); 1223 } 1224 1225 inline void requestRoutesProcessor(App& app) 1226 { 1227 /** 1228 * Functions triggers appropriate requests on DBus 1229 */ 1230 1231 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Processors/<str>/") 1232 .privileges(redfish::privileges::headProcessor) 1233 .methods(boost::beast::http::verb::head)( 1234 std::bind_front(handleProcessorHead, std::ref(app))); 1235 1236 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Processors/<str>/") 1237 .privileges(redfish::privileges::getProcessor) 1238 .methods(boost::beast::http::verb::get)( 1239 [&app](const crow::Request& req, 1240 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1241 const std::string& systemName, 1242 const std::string& processorId) { 1243 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1244 { 1245 return; 1246 } 1247 if (systemName != "system") 1248 { 1249 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 1250 systemName); 1251 return; 1252 } 1253 1254 asyncResp->res.addHeader( 1255 boost::beast::http::field::link, 1256 "</redfish/v1/JsonSchemas/Processor/Processor.json>; rel=describedby"); 1257 asyncResp->res.jsonValue["@odata.type"] = 1258 "#Processor.v1_11_0.Processor"; 1259 asyncResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces( 1260 "redfish", "v1", "Systems", "system", "Processors", processorId); 1261 1262 getProcessorObject( 1263 asyncResp, processorId, 1264 std::bind_front(getProcessorData, asyncResp, processorId)); 1265 }); 1266 1267 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Processors/<str>/") 1268 .privileges(redfish::privileges::patchProcessor) 1269 .methods(boost::beast::http::verb::patch)( 1270 [&app](const crow::Request& req, 1271 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1272 const std::string& systemName, 1273 const std::string& processorId) { 1274 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1275 { 1276 return; 1277 } 1278 if (systemName != "system") 1279 { 1280 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 1281 systemName); 1282 return; 1283 } 1284 1285 std::optional<nlohmann::json> appliedConfigJson; 1286 if (!json_util::readJsonPatch(req, asyncResp->res, 1287 "AppliedOperatingConfig", 1288 appliedConfigJson)) 1289 { 1290 return; 1291 } 1292 1293 if (appliedConfigJson) 1294 { 1295 std::string appliedConfigUri; 1296 if (!json_util::readJson(*appliedConfigJson, asyncResp->res, 1297 "@odata.id", appliedConfigUri)) 1298 { 1299 return; 1300 } 1301 // Check for 404 and find matching D-Bus object, then run 1302 // property patch handlers if that all succeeds. 1303 getProcessorObject(asyncResp, processorId, 1304 std::bind_front(patchAppliedOperatingConfig, 1305 asyncResp, processorId, 1306 appliedConfigUri)); 1307 } 1308 }); 1309 } 1310 1311 } // namespace redfish 1312