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