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