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