1 /** 2 * Copyright © 2017 IBM 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 #include "config.h" 17 18 #include "power_supply.hpp" 19 20 #include "elog-errors.hpp" 21 #include "gpio.hpp" 22 #include "names_values.hpp" 23 #include "pmbus.hpp" 24 #include "types.hpp" 25 #include "utility.hpp" 26 27 #include <org/open_power/Witherspoon/Fault/error.hpp> 28 #include <phosphor-logging/log.hpp> 29 #include <xyz/openbmc_project/Common/Device/error.hpp> 30 #include <xyz/openbmc_project/Software/Version/server.hpp> 31 32 #include <functional> 33 34 namespace phosphor 35 { 36 namespace power 37 { 38 namespace psu 39 { 40 41 using namespace phosphor::logging; 42 using namespace sdbusplus::org::open_power::Witherspoon::Fault::Error; 43 using namespace sdbusplus::xyz::openbmc_project::Common::Device::Error; 44 namespace version = sdbusplus::xyz::openbmc_project::Software::server; 45 46 PowerSupply::PowerSupply(const std::string& name, size_t inst, 47 const std::string& objpath, const std::string& invpath, 48 sdbusplus::bus::bus& bus, const sdeventplus::Event& e, 49 std::chrono::seconds& t, std::chrono::seconds& p) : 50 Device(name, inst), 51 monitorPath(objpath), pmbusIntf(objpath), 52 inventoryPath(INVENTORY_OBJ_PATH + invpath), bus(bus), presentInterval(p), 53 presentTimer(e, std::bind([this]() { 54 // The hwmon path may have changed. 55 pmbusIntf.findHwmonDir(); 56 this->present = true; 57 58 // Sync the INPUT_HISTORY data for all PSs 59 syncHistory(); 60 61 // Update the inventory for the new device 62 updateInventory(); 63 })), 64 powerOnInterval(t), 65 powerOnTimer(e, std::bind([this]() { this->powerOn = true; })) 66 { 67 getAccessType(); 68 69 using namespace sdbusplus::bus; 70 using namespace phosphor::pmbus; 71 std::uint16_t statusWord = 0; 72 try 73 { 74 // Read the 2 byte STATUS_WORD value to check for faults. 75 statusWord = pmbusIntf.read(STATUS_WORD, Type::Debug); 76 if (!((statusWord & status_word::INPUT_FAULT_WARN) || 77 (statusWord & status_word::VIN_UV_FAULT))) 78 { 79 resolveError(inventoryPath, 80 std::string(PowerSupplyInputFault::errName)); 81 } 82 } 83 catch (ReadFailure& e) 84 { 85 log<level::INFO>("Unable to read the 2 byte STATUS_WORD value to check " 86 "for power-supply input faults."); 87 } 88 presentMatch = std::make_unique<match_t>( 89 bus, match::rules::propertiesChanged(inventoryPath, INVENTORY_IFACE), 90 [this](auto& msg) { this->inventoryChanged(msg); }); 91 // Get initial presence state. 92 updatePresence(); 93 94 // Write the SN, PN, etc to the inventory 95 updateInventory(); 96 97 // Subscribe to power state changes 98 powerOnMatch = std::make_unique<match_t>( 99 bus, match::rules::propertiesChanged(POWER_OBJ_PATH, POWER_IFACE), 100 [this](auto& msg) { this->powerStateChanged(msg); }); 101 // Get initial power state. 102 updatePowerState(); 103 } 104 105 void PowerSupply::getAccessType() 106 { 107 using namespace phosphor::power::util; 108 fruJson = loadJSONFromFile(PSU_JSON_PATH); 109 if (fruJson == nullptr) 110 { 111 log<level::ERR>("InternalFailure when parsing the JSON file"); 112 return; 113 } 114 inventoryPMBusAccessType = getPMBusAccessType(fruJson); 115 } 116 117 void PowerSupply::captureCmd(util::NamesValues& nv, const std::string& cmd, 118 phosphor::pmbus::Type type) 119 { 120 if (pmbusIntf.exists(cmd, type)) 121 { 122 try 123 { 124 auto val = pmbusIntf.read(cmd, type); 125 nv.add(cmd, val); 126 } 127 catch (std::exception& e) 128 { 129 log<level::INFO>("Unable to capture metadata", 130 entry("CMD=%s", cmd.c_str())); 131 } 132 } 133 } 134 135 void PowerSupply::analyze() 136 { 137 using namespace phosphor::pmbus; 138 139 try 140 { 141 if (present) 142 { 143 std::uint16_t statusWord = 0; 144 145 // Read the 2 byte STATUS_WORD value to check for faults. 146 statusWord = pmbusIntf.read(STATUS_WORD, Type::Debug); 147 readFail = 0; 148 149 checkInputFault(statusWord); 150 151 if (powerOn && (inputFault == 0) && !faultFound) 152 { 153 checkFanFault(statusWord); 154 checkTemperatureFault(statusWord); 155 checkOutputOvervoltageFault(statusWord); 156 checkCurrentOutOverCurrentFault(statusWord); 157 checkPGOrUnitOffFault(statusWord); 158 } 159 160 updateHistory(); 161 } 162 } 163 catch (ReadFailure& e) 164 { 165 if (readFail < FAULT_COUNT) 166 { 167 readFail++; 168 } 169 170 if (!readFailLogged && readFail >= FAULT_COUNT) 171 { 172 commit<ReadFailure>(); 173 readFailLogged = true; 174 } 175 } 176 177 return; 178 } 179 180 void PowerSupply::inventoryChanged(sdbusplus::message::message& msg) 181 { 182 std::string msgSensor; 183 std::map<std::string, sdbusplus::message::variant<uint32_t, bool>> msgData; 184 msg.read(msgSensor, msgData); 185 186 // Check if it was the Present property that changed. 187 auto valPropMap = msgData.find(PRESENT_PROP); 188 if (valPropMap != msgData.end()) 189 { 190 if (sdbusplus::message::variant_ns::get<bool>(valPropMap->second)) 191 { 192 clearFaults(); 193 presentTimer.restartOnce(presentInterval); 194 } 195 else 196 { 197 present = false; 198 presentTimer.setEnabled(false); 199 200 // Clear out the now outdated inventory properties 201 updateInventory(); 202 } 203 } 204 205 return; 206 } 207 208 void PowerSupply::updatePresence() 209 { 210 // Use getProperty utility function to get presence status. 211 std::string service = "xyz.openbmc_project.Inventory.Manager"; 212 util::getProperty(INVENTORY_IFACE, PRESENT_PROP, inventoryPath, service, 213 bus, this->present); 214 } 215 216 void PowerSupply::powerStateChanged(sdbusplus::message::message& msg) 217 { 218 int32_t state = 0; 219 std::string msgSensor; 220 std::map<std::string, sdbusplus::message::variant<int32_t>> msgData; 221 msg.read(msgSensor, msgData); 222 223 // Check if it was the Present property that changed. 224 auto valPropMap = msgData.find("state"); 225 if (valPropMap != msgData.end()) 226 { 227 state = 228 sdbusplus::message::variant_ns::get<int32_t>(valPropMap->second); 229 230 // Power is on when state=1. Set the fault logged variables to false 231 // and start the power on timer when the state changes to 1. 232 if (state) 233 { 234 clearFaults(); 235 powerOnTimer.restartOnce(powerOnInterval); 236 } 237 else 238 { 239 powerOnTimer.setEnabled(false); 240 powerOn = false; 241 } 242 } 243 } 244 245 void PowerSupply::updatePowerState() 246 { 247 powerOn = util::isPoweredOn(bus); 248 } 249 250 void PowerSupply::checkInputFault(const uint16_t statusWord) 251 { 252 using namespace phosphor::pmbus; 253 254 if ((inputFault < FAULT_COUNT) && 255 ((statusWord & status_word::INPUT_FAULT_WARN) || 256 (statusWord & status_word::VIN_UV_FAULT))) 257 { 258 if (inputFault == 0) 259 { 260 log<level::INFO>("INPUT or VIN_UV fault", 261 entry("STATUS_WORD=0x%04X", statusWord)); 262 } 263 264 inputFault++; 265 } 266 else 267 { 268 if ((inputFault > 0) && !(statusWord & status_word::INPUT_FAULT_WARN) && 269 !(statusWord & status_word::VIN_UV_FAULT)) 270 { 271 inputFault = 0; 272 faultFound = false; 273 // When an input fault occurs, the power supply cannot be on. 274 // However, the check for the case where the power supply should be 275 // on will stop when there is a fault found. 276 // Clear the powerOnFault when the inputFault is cleared to reset 277 // the powerOnFault de-glitching. 278 powerOnFault = 0; 279 280 log<level::INFO>("INPUT_FAULT_WARN cleared", 281 entry("POWERSUPPLY=%s", inventoryPath.c_str())); 282 283 resolveError(inventoryPath, 284 std::string(PowerSupplyInputFault::errName)); 285 286 if (powerOn) 287 { 288 // The power supply will not be immediately powered on after 289 // the input power is restored. 290 powerOn = false; 291 // Start up the timer that will set the state to indicate we 292 // are ready for the powered on fault checks. 293 powerOnTimer.restartOnce(powerOnInterval); 294 } 295 } 296 } 297 298 if (!faultFound && (inputFault >= FAULT_COUNT)) 299 { 300 // If the power is on, report the fault in an error log entry. 301 if (powerOn) 302 { 303 util::NamesValues nv; 304 nv.add("STATUS_WORD", statusWord); 305 captureCmd(nv, STATUS_INPUT, Type::Debug); 306 307 using metadata = 308 org::open_power::Witherspoon::Fault::PowerSupplyInputFault; 309 310 report<PowerSupplyInputFault>( 311 metadata::RAW_STATUS(nv.get().c_str()), 312 metadata::CALLOUT_INVENTORY_PATH(inventoryPath.c_str())); 313 314 faultFound = true; 315 } 316 } 317 } 318 319 void PowerSupply::checkPGOrUnitOffFault(const uint16_t statusWord) 320 { 321 using namespace phosphor::pmbus; 322 323 if (powerOnFault < FAULT_COUNT) 324 { 325 // Check PG# and UNIT_IS_OFF 326 if ((statusWord & status_word::POWER_GOOD_NEGATED) || 327 (statusWord & status_word::UNIT_IS_OFF)) 328 { 329 log<level::INFO>("PGOOD or UNIT_IS_OFF bit bad", 330 entry("STATUS_WORD=0x%04X", statusWord)); 331 powerOnFault++; 332 } 333 else 334 { 335 if (powerOnFault > 0) 336 { 337 log<level::INFO>("PGOOD and UNIT_IS_OFF bits good"); 338 powerOnFault = 0; 339 } 340 } 341 342 if (!faultFound && (powerOnFault >= FAULT_COUNT)) 343 { 344 faultFound = true; 345 346 util::NamesValues nv; 347 nv.add("STATUS_WORD", statusWord); 348 captureCmd(nv, STATUS_INPUT, Type::Debug); 349 auto status0Vout = pmbusIntf.insertPageNum(STATUS_VOUT, 0); 350 captureCmd(nv, status0Vout, Type::Debug); 351 captureCmd(nv, STATUS_IOUT, Type::Debug); 352 captureCmd(nv, STATUS_MFR, Type::Debug); 353 354 using metadata = 355 org::open_power::Witherspoon::Fault::PowerSupplyShouldBeOn; 356 357 // A power supply is OFF (or pgood low) but should be on. 358 report<PowerSupplyShouldBeOn>( 359 metadata::RAW_STATUS(nv.get().c_str()), 360 metadata::CALLOUT_INVENTORY_PATH(inventoryPath.c_str())); 361 } 362 } 363 } 364 365 void PowerSupply::checkCurrentOutOverCurrentFault(const uint16_t statusWord) 366 { 367 using namespace phosphor::pmbus; 368 369 if (outputOCFault < FAULT_COUNT) 370 { 371 // Check for an output overcurrent fault. 372 if ((statusWord & status_word::IOUT_OC_FAULT)) 373 { 374 outputOCFault++; 375 } 376 else 377 { 378 if (outputOCFault > 0) 379 { 380 outputOCFault = 0; 381 } 382 } 383 384 if (!faultFound && (outputOCFault >= FAULT_COUNT)) 385 { 386 util::NamesValues nv; 387 nv.add("STATUS_WORD", statusWord); 388 captureCmd(nv, STATUS_INPUT, Type::Debug); 389 auto status0Vout = pmbusIntf.insertPageNum(STATUS_VOUT, 0); 390 captureCmd(nv, status0Vout, Type::Debug); 391 captureCmd(nv, STATUS_IOUT, Type::Debug); 392 captureCmd(nv, STATUS_MFR, Type::Debug); 393 394 using metadata = org::open_power::Witherspoon::Fault:: 395 PowerSupplyOutputOvercurrent; 396 397 report<PowerSupplyOutputOvercurrent>( 398 metadata::RAW_STATUS(nv.get().c_str()), 399 metadata::CALLOUT_INVENTORY_PATH(inventoryPath.c_str())); 400 401 faultFound = true; 402 } 403 } 404 } 405 406 void PowerSupply::checkOutputOvervoltageFault(const uint16_t statusWord) 407 { 408 using namespace phosphor::pmbus; 409 410 if (outputOVFault < FAULT_COUNT) 411 { 412 // Check for an output overvoltage fault. 413 if (statusWord & status_word::VOUT_OV_FAULT) 414 { 415 outputOVFault++; 416 } 417 else 418 { 419 if (outputOVFault > 0) 420 { 421 outputOVFault = 0; 422 } 423 } 424 425 if (!faultFound && (outputOVFault >= FAULT_COUNT)) 426 { 427 util::NamesValues nv; 428 nv.add("STATUS_WORD", statusWord); 429 captureCmd(nv, STATUS_INPUT, Type::Debug); 430 auto status0Vout = pmbusIntf.insertPageNum(STATUS_VOUT, 0); 431 captureCmd(nv, status0Vout, Type::Debug); 432 captureCmd(nv, STATUS_IOUT, Type::Debug); 433 captureCmd(nv, STATUS_MFR, Type::Debug); 434 435 using metadata = org::open_power::Witherspoon::Fault:: 436 PowerSupplyOutputOvervoltage; 437 438 report<PowerSupplyOutputOvervoltage>( 439 metadata::RAW_STATUS(nv.get().c_str()), 440 metadata::CALLOUT_INVENTORY_PATH(inventoryPath.c_str())); 441 442 faultFound = true; 443 } 444 } 445 } 446 447 void PowerSupply::checkFanFault(const uint16_t statusWord) 448 { 449 using namespace phosphor::pmbus; 450 451 if (fanFault < FAULT_COUNT) 452 { 453 // Check for a fan fault or warning condition 454 if (statusWord & status_word::FAN_FAULT) 455 { 456 fanFault++; 457 } 458 else 459 { 460 if (fanFault > 0) 461 { 462 fanFault = 0; 463 } 464 } 465 466 if (!faultFound && (fanFault >= FAULT_COUNT)) 467 { 468 util::NamesValues nv; 469 nv.add("STATUS_WORD", statusWord); 470 captureCmd(nv, STATUS_MFR, Type::Debug); 471 captureCmd(nv, STATUS_TEMPERATURE, Type::Debug); 472 captureCmd(nv, STATUS_FANS_1_2, Type::Debug); 473 474 using metadata = 475 org::open_power::Witherspoon::Fault::PowerSupplyFanFault; 476 477 report<PowerSupplyFanFault>( 478 metadata::RAW_STATUS(nv.get().c_str()), 479 metadata::CALLOUT_INVENTORY_PATH(inventoryPath.c_str())); 480 481 faultFound = true; 482 } 483 } 484 } 485 486 void PowerSupply::checkTemperatureFault(const uint16_t statusWord) 487 { 488 using namespace phosphor::pmbus; 489 490 // Due to how the PMBus core device driver sends a clear faults command 491 // the bit in STATUS_WORD will likely be cleared when we attempt to examine 492 // it for a Thermal Fault or Warning. So, check the STATUS_WORD and the 493 // STATUS_TEMPERATURE bits. If either indicates a fault, proceed with 494 // logging the over-temperature condition. 495 std::uint8_t statusTemperature = 0; 496 statusTemperature = pmbusIntf.read(STATUS_TEMPERATURE, Type::Debug); 497 if (temperatureFault < FAULT_COUNT) 498 { 499 if ((statusWord & status_word::TEMPERATURE_FAULT_WARN) || 500 (statusTemperature & status_temperature::OT_FAULT)) 501 { 502 temperatureFault++; 503 } 504 else 505 { 506 if (temperatureFault > 0) 507 { 508 temperatureFault = 0; 509 } 510 } 511 512 if (!faultFound && (temperatureFault >= FAULT_COUNT)) 513 { 514 // The power supply has had an over-temperature condition. 515 // This may not result in a shutdown if experienced for a short 516 // duration. 517 // This should not occur under normal conditions. 518 // The power supply may be faulty, or the paired supply may be 519 // putting out less current. 520 // Capture command responses with potentially relevant information, 521 // and call out the power supply reporting the condition. 522 util::NamesValues nv; 523 nv.add("STATUS_WORD", statusWord); 524 captureCmd(nv, STATUS_MFR, Type::Debug); 525 captureCmd(nv, STATUS_IOUT, Type::Debug); 526 nv.add("STATUS_TEMPERATURE", statusTemperature); 527 captureCmd(nv, STATUS_FANS_1_2, Type::Debug); 528 529 using metadata = org::open_power::Witherspoon::Fault:: 530 PowerSupplyTemperatureFault; 531 532 report<PowerSupplyTemperatureFault>( 533 metadata::RAW_STATUS(nv.get().c_str()), 534 metadata::CALLOUT_INVENTORY_PATH(inventoryPath.c_str())); 535 536 faultFound = true; 537 } 538 } 539 } 540 541 void PowerSupply::clearFaults() 542 { 543 readFail = 0; 544 readFailLogged = false; 545 inputFault = 0; 546 powerOnFault = 0; 547 outputOCFault = 0; 548 outputOVFault = 0; 549 fanFault = 0; 550 temperatureFault = 0; 551 faultFound = false; 552 553 return; 554 } 555 556 void PowerSupply::resolveError(const std::string& callout, 557 const std::string& message) 558 { 559 using EndpointList = std::vector<std::string>; 560 561 try 562 { 563 auto path = callout + "/fault"; 564 // Get the service name from the mapper for the fault callout 565 auto service = util::getService(path, ASSOCIATION_IFACE, bus); 566 567 // Use getProperty utility function to get log entries (endpoints) 568 EndpointList logEntries; 569 util::getProperty(ASSOCIATION_IFACE, ENDPOINTS_PROP, path, service, bus, 570 logEntries); 571 572 // It is possible that all such entries for this callout have since 573 // been deleted. 574 if (logEntries.empty()) 575 { 576 return; 577 } 578 579 auto logEntryService = 580 util::getService(logEntries[0], LOGGING_IFACE, bus); 581 if (logEntryService.empty()) 582 { 583 return; 584 } 585 586 // go through each log entry that matches this callout path 587 std::string logMessage; 588 for (const auto& logEntry : logEntries) 589 { 590 // Check to see if this logEntry has a message that matches. 591 util::getProperty(LOGGING_IFACE, MESSAGE_PROP, logEntry, 592 logEntryService, bus, logMessage); 593 594 if (message == logMessage) 595 { 596 // Log entry matches call out and message, set Resolved to true 597 bool resolved = true; 598 util::setProperty(LOGGING_IFACE, RESOLVED_PROP, logEntry, 599 logEntryService, bus, resolved); 600 } 601 } 602 } 603 catch (std::exception& e) 604 { 605 log<level::INFO>("Failed to resolve error", 606 entry("CALLOUT=%s", callout.c_str()), 607 entry("ERROR=%s", message.c_str())); 608 } 609 } 610 611 void PowerSupply::updateInventory() 612 { 613 using namespace phosphor::pmbus; 614 using namespace sdbusplus::message; 615 616 // Build the object map and send it to the inventory 617 using Properties = std::map<std::string, variant<std::string>>; 618 using Interfaces = std::map<std::string, Properties>; 619 using Object = std::map<object_path, Interfaces>; 620 Properties assetProps; 621 Properties versionProps; 622 Interfaces interfaces; 623 Object object; 624 625 // If any of these accesses fail, the fields will just be 626 // blank in the inventory. Leave logging ReadFailure errors 627 // to analyze() as it runs continuously and will most 628 // likely hit and threshold them first anyway. The 629 // readString() function will do the tracing of the failing 630 // path so this code doesn't need to. 631 for (const auto& fru : fruJson.at("fruConfigs")) 632 { 633 if (fru.at("interface") == ASSET_IFACE) 634 { 635 try 636 { 637 assetProps.emplace( 638 fru.at("propertyName"), 639 present ? pmbusIntf.readString(fru.at("fileName"), 640 inventoryPMBusAccessType) 641 : ""); 642 } 643 catch (ReadFailure& e) 644 { 645 } 646 } 647 else if (fru.at("interface") == VERSION_IFACE) 648 { 649 try 650 { 651 versionProps.emplace( 652 fru.at("propertyName"), 653 present ? pmbusIntf.readString(fru.at("fileName"), 654 inventoryPMBusAccessType) 655 : ""); 656 } 657 catch (const std::exception& e) 658 { 659 } 660 } 661 } 662 663 interfaces.emplace(ASSET_IFACE, std::move(assetProps)); 664 interfaces.emplace(VERSION_IFACE, std::move(versionProps)); 665 666 // For Notify(), just send the relative path of the inventory 667 // object so remove the INVENTORY_OBJ_PATH prefix 668 auto path = inventoryPath.substr(strlen(INVENTORY_OBJ_PATH)); 669 670 object.emplace(path, std::move(interfaces)); 671 672 try 673 { 674 auto service = 675 util::getService(INVENTORY_OBJ_PATH, INVENTORY_MGR_IFACE, bus); 676 677 if (service.empty()) 678 { 679 log<level::ERR>("Unable to get inventory manager service"); 680 return; 681 } 682 683 auto method = bus.new_method_call(service.c_str(), INVENTORY_OBJ_PATH, 684 INVENTORY_MGR_IFACE, "Notify"); 685 686 method.append(std::move(object)); 687 688 auto reply = bus.call(method); 689 690 // TODO: openbmc/openbmc#2756 691 // Calling Notify() with an enumerated property crashes inventory 692 // manager, so let it default to Unknown and now set it to the 693 // right value. 694 auto purpose = 695 version::convertForMessage(version::Version::VersionPurpose::Other); 696 697 util::setProperty(VERSION_IFACE, VERSION_PURPOSE_PROP, inventoryPath, 698 service, bus, purpose); 699 } 700 catch (std::exception& e) 701 { 702 log<level::ERR>(e.what(), entry("PATH=%s", inventoryPath.c_str())); 703 } 704 } 705 706 void PowerSupply::syncHistory() 707 { 708 using namespace phosphor::gpio; 709 710 if (syncGPIODevPath.empty()) 711 { 712 // Sync not implemented 713 return; 714 } 715 716 GPIO gpio{syncGPIODevPath, static_cast<gpioNum_t>(syncGPIONumber), 717 Direction::output}; 718 719 try 720 { 721 gpio.set(Value::low); 722 723 std::this_thread::sleep_for(std::chrono::milliseconds{5}); 724 725 gpio.set(Value::high); 726 727 recordManager->clear(); 728 } 729 catch (std::exception& e) 730 { 731 // Do nothing. There would already be a journal entry. 732 } 733 } 734 735 void PowerSupply::enableHistory(const std::string& objectPath, 736 size_t numRecords, 737 const std::string& syncGPIOPath, 738 size_t syncGPIONum) 739 { 740 historyObjectPath = objectPath; 741 syncGPIODevPath = syncGPIOPath; 742 syncGPIONumber = syncGPIONum; 743 744 recordManager = std::make_unique<history::RecordManager>(numRecords); 745 746 auto avgPath = historyObjectPath + '/' + history::Average::name; 747 auto maxPath = historyObjectPath + '/' + history::Maximum::name; 748 749 average = std::make_unique<history::Average>(bus, avgPath); 750 751 maximum = std::make_unique<history::Maximum>(bus, maxPath); 752 } 753 754 void PowerSupply::updateHistory() 755 { 756 if (!recordManager) 757 { 758 // Not enabled 759 return; 760 } 761 762 // Read just the most recent average/max record 763 auto data = 764 pmbusIntf.readBinary(INPUT_HISTORY, pmbus::Type::HwmonDeviceDebug, 765 history::RecordManager::RAW_RECORD_SIZE); 766 767 // Update D-Bus only if something changed (a new record ID, or cleared out) 768 auto changed = recordManager->add(data); 769 if (changed) 770 { 771 average->values(std::move(recordManager->getAverageRecords())); 772 maximum->values(std::move(recordManager->getMaximumRecords())); 773 } 774 } 775 776 } // namespace psu 777 } // namespace power 778 } // namespace phosphor 779