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