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