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