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