1 #include "config.h" 2 3 #include "power_supply.hpp" 4 5 #include "types.hpp" 6 #include "util.hpp" 7 8 #include <fmt/format.h> 9 10 #include <xyz/openbmc_project/Common/Device/error.hpp> 11 12 #include <chrono> // sleep_for() 13 #include <cmath> 14 #include <cstdint> // uint8_t... 15 #include <fstream> 16 #include <regex> 17 #include <thread> // sleep_for() 18 19 namespace phosphor::power::psu 20 { 21 // Amount of time in milliseconds to delay between power supply going from 22 // missing to present before running the bind command(s). 23 constexpr auto bindDelay = 1000; 24 25 // The number of INPUT_HISTORY records to keep on D-Bus. 26 // Each record covers a 30-second span. That means two records are needed to 27 // cover a minute of time. If we want one (1) hour of data, that would be 120 28 // records. 29 constexpr auto INPUT_HISTORY_MAX_RECORDS = 120; 30 31 using namespace phosphor::logging; 32 using namespace sdbusplus::xyz::openbmc_project::Common::Device::Error; 33 34 PowerSupply::PowerSupply(sdbusplus::bus_t& bus, const std::string& invpath, 35 std::uint8_t i2cbus, std::uint16_t i2caddr, 36 const std::string& driver, 37 const std::string& gpioLineName, 38 std::function<bool()>&& callback) : 39 bus(bus), 40 inventoryPath(invpath), bindPath("/sys/bus/i2c/drivers/" + driver), 41 isPowerOn(std::move(callback)), driverName(driver) 42 { 43 if (inventoryPath.empty()) 44 { 45 throw std::invalid_argument{"Invalid empty inventoryPath"}; 46 } 47 48 if (gpioLineName.empty()) 49 { 50 throw std::invalid_argument{"Invalid empty gpioLineName"}; 51 } 52 53 shortName = findShortName(inventoryPath); 54 55 log<level::DEBUG>( 56 fmt::format("{} gpioLineName: {}", shortName, gpioLineName).c_str()); 57 presenceGPIO = createGPIO(gpioLineName); 58 59 std::ostringstream ss; 60 ss << std::hex << std::setw(4) << std::setfill('0') << i2caddr; 61 std::string addrStr = ss.str(); 62 std::string busStr = std::to_string(i2cbus); 63 bindDevice = busStr; 64 bindDevice.append("-"); 65 bindDevice.append(addrStr); 66 67 pmbusIntf = phosphor::pmbus::createPMBus(i2cbus, addrStr); 68 69 // Get the current state of the Present property. 70 try 71 { 72 updatePresenceGPIO(); 73 } 74 catch (...) 75 { 76 // If the above attempt to use the GPIO failed, it likely means that the 77 // GPIOs are in use by the kernel, meaning it is using gpio-keys. 78 // So, I should rely on phosphor-gpio-presence to update D-Bus, and 79 // work that way for power supply presence. 80 presenceGPIO = nullptr; 81 // Setup the functions to call when the D-Bus inventory path for the 82 // Present property changes. 83 presentMatch = std::make_unique<sdbusplus::bus::match_t>( 84 bus, 85 sdbusplus::bus::match::rules::propertiesChanged(inventoryPath, 86 INVENTORY_IFACE), 87 [this](auto& msg) { this->inventoryChanged(msg); }); 88 89 presentAddedMatch = std::make_unique<sdbusplus::bus::match_t>( 90 bus, 91 sdbusplus::bus::match::rules::interfacesAdded() + 92 sdbusplus::bus::match::rules::argNpath(0, inventoryPath), 93 [this](auto& msg) { this->inventoryAdded(msg); }); 94 95 updatePresence(); 96 updateInventory(); 97 setupInputHistory(); 98 } 99 100 setInputVoltageRating(); 101 } 102 103 void PowerSupply::bindOrUnbindDriver(bool present) 104 { 105 auto action = (present) ? "bind" : "unbind"; 106 auto path = bindPath / action; 107 108 if (present) 109 { 110 std::this_thread::sleep_for(std::chrono::milliseconds(bindDelay)); 111 log<level::INFO>( 112 fmt::format("Binding device driver. path: {} device: {}", 113 path.string(), bindDevice) 114 .c_str()); 115 } 116 else 117 { 118 log<level::INFO>( 119 fmt::format("Unbinding device driver. path: {} device: {}", 120 path.string(), bindDevice) 121 .c_str()); 122 } 123 124 std::ofstream file; 125 126 file.exceptions(std::ofstream::failbit | std::ofstream::badbit | 127 std::ofstream::eofbit); 128 129 try 130 { 131 file.open(path); 132 file << bindDevice; 133 file.close(); 134 } 135 catch (const std::exception& e) 136 { 137 auto err = errno; 138 139 log<level::ERR>( 140 fmt::format("Failed binding or unbinding device. errno={}", err) 141 .c_str()); 142 } 143 } 144 145 void PowerSupply::updatePresence() 146 { 147 try 148 { 149 present = getPresence(bus, inventoryPath); 150 } 151 catch (const sdbusplus::exception_t& e) 152 { 153 // Relying on property change or interface added to retry. 154 // Log an informational trace to the journal. 155 log<level::INFO>( 156 fmt::format("D-Bus property {} access failure exception", 157 inventoryPath) 158 .c_str()); 159 } 160 } 161 162 void PowerSupply::updatePresenceGPIO() 163 { 164 bool presentOld = present; 165 166 try 167 { 168 if (presenceGPIO->read() > 0) 169 { 170 present = true; 171 } 172 else 173 { 174 present = false; 175 } 176 } 177 catch (const std::exception& e) 178 { 179 log<level::ERR>( 180 fmt::format("presenceGPIO read fail: {}", e.what()).c_str()); 181 throw; 182 } 183 184 if (presentOld != present) 185 { 186 log<level::DEBUG>(fmt::format("{} presentOld: {} present: {}", 187 shortName, presentOld, present) 188 .c_str()); 189 190 auto invpath = inventoryPath.substr(strlen(INVENTORY_OBJ_PATH)); 191 192 bindOrUnbindDriver(present); 193 if (present) 194 { 195 // If the power supply was present, then missing, and present again, 196 // the hwmon path may have changed. We will need the correct/updated 197 // path before any reads or writes are attempted. 198 pmbusIntf->findHwmonDir(); 199 } 200 201 setPresence(bus, invpath, present, shortName); 202 setupInputHistory(); 203 updateInventory(); 204 205 // Need Functional to already be correct before calling this. 206 checkAvailability(); 207 208 if (present) 209 { 210 onOffConfig(phosphor::pmbus::ON_OFF_CONFIG_CONTROL_PIN_ONLY); 211 clearFaults(); 212 // Indicate that the input history data and timestamps between all 213 // the power supplies that are present in the system need to be 214 // synchronized. 215 syncHistoryRequired = true; 216 } 217 } 218 } 219 220 void PowerSupply::analyzeCMLFault() 221 { 222 if (statusWord & phosphor::pmbus::status_word::CML_FAULT) 223 { 224 if (cmlFault < DEGLITCH_LIMIT) 225 { 226 if (statusWord != statusWordOld) 227 { 228 log<level::ERR>( 229 fmt::format("{} CML fault: STATUS_WORD = {:#06x}, " 230 "STATUS_CML = {:#02x}", 231 shortName, statusWord, statusCML) 232 .c_str()); 233 } 234 cmlFault++; 235 } 236 } 237 else 238 { 239 cmlFault = 0; 240 } 241 } 242 243 void PowerSupply::analyzeInputFault() 244 { 245 if (statusWord & phosphor::pmbus::status_word::INPUT_FAULT_WARN) 246 { 247 if (inputFault < DEGLITCH_LIMIT) 248 { 249 if (statusWord != statusWordOld) 250 { 251 log<level::ERR>( 252 fmt::format("{} INPUT fault: STATUS_WORD = {:#06x}, " 253 "STATUS_MFR_SPECIFIC = {:#04x}, " 254 "STATUS_INPUT = {:#04x}", 255 shortName, statusWord, statusMFR, statusInput) 256 .c_str()); 257 } 258 inputFault++; 259 } 260 } 261 262 // If had INPUT/VIN_UV fault, and now off. 263 // Trace that odd behavior. 264 if (inputFault && 265 !(statusWord & phosphor::pmbus::status_word::INPUT_FAULT_WARN)) 266 { 267 log<level::INFO>( 268 fmt::format("{} INPUT fault cleared: STATUS_WORD = {:#06x}, " 269 "STATUS_MFR_SPECIFIC = {:#04x}, " 270 "STATUS_INPUT = {:#04x}", 271 shortName, statusWord, statusMFR, statusInput) 272 .c_str()); 273 inputFault = 0; 274 } 275 } 276 277 void PowerSupply::analyzeVoutOVFault() 278 { 279 if (statusWord & phosphor::pmbus::status_word::VOUT_OV_FAULT) 280 { 281 if (voutOVFault < DEGLITCH_LIMIT) 282 { 283 if (statusWord != statusWordOld) 284 { 285 log<level::ERR>( 286 fmt::format( 287 "{} VOUT_OV_FAULT fault: STATUS_WORD = {:#06x}, " 288 "STATUS_MFR_SPECIFIC = {:#04x}, " 289 "STATUS_VOUT = {:#02x}", 290 shortName, statusWord, statusMFR, statusVout) 291 .c_str()); 292 } 293 294 voutOVFault++; 295 } 296 } 297 else 298 { 299 voutOVFault = 0; 300 } 301 } 302 303 void PowerSupply::analyzeIoutOCFault() 304 { 305 if (statusWord & phosphor::pmbus::status_word::IOUT_OC_FAULT) 306 { 307 if (ioutOCFault < DEGLITCH_LIMIT) 308 { 309 if (statusWord != statusWordOld) 310 { 311 log<level::ERR>( 312 fmt::format("{} IOUT fault: STATUS_WORD = {:#06x}, " 313 "STATUS_MFR_SPECIFIC = {:#04x}, " 314 "STATUS_IOUT = {:#04x}", 315 shortName, statusWord, statusMFR, statusIout) 316 .c_str()); 317 } 318 319 ioutOCFault++; 320 } 321 } 322 else 323 { 324 ioutOCFault = 0; 325 } 326 } 327 328 void PowerSupply::analyzeVoutUVFault() 329 { 330 if ((statusWord & phosphor::pmbus::status_word::VOUT_FAULT) && 331 !(statusWord & phosphor::pmbus::status_word::VOUT_OV_FAULT)) 332 { 333 if (voutUVFault < DEGLITCH_LIMIT) 334 { 335 if (statusWord != statusWordOld) 336 { 337 log<level::ERR>( 338 fmt::format( 339 "{} VOUT_UV_FAULT fault: STATUS_WORD = {:#06x}, " 340 "STATUS_MFR_SPECIFIC = {:#04x}, " 341 "STATUS_VOUT = {:#04x}", 342 shortName, statusWord, statusMFR, statusVout) 343 .c_str()); 344 } 345 voutUVFault++; 346 } 347 } 348 else 349 { 350 voutUVFault = 0; 351 } 352 } 353 354 void PowerSupply::analyzeFanFault() 355 { 356 if (statusWord & phosphor::pmbus::status_word::FAN_FAULT) 357 { 358 if (fanFault < DEGLITCH_LIMIT) 359 { 360 if (statusWord != statusWordOld) 361 { 362 log<level::ERR>(fmt::format("{} FANS fault/warning: " 363 "STATUS_WORD = {:#06x}, " 364 "STATUS_MFR_SPECIFIC = {:#04x}, " 365 "STATUS_FANS_1_2 = {:#04x}", 366 shortName, statusWord, statusMFR, 367 statusFans12) 368 .c_str()); 369 } 370 fanFault++; 371 } 372 } 373 else 374 { 375 fanFault = 0; 376 } 377 } 378 379 void PowerSupply::analyzeTemperatureFault() 380 { 381 if (statusWord & phosphor::pmbus::status_word::TEMPERATURE_FAULT_WARN) 382 { 383 if (tempFault < DEGLITCH_LIMIT) 384 { 385 if (statusWord != statusWordOld) 386 { 387 log<level::ERR>(fmt::format("{} TEMPERATURE fault/warning: " 388 "STATUS_WORD = {:#06x}, " 389 "STATUS_MFR_SPECIFIC = {:#04x}, " 390 "STATUS_TEMPERATURE = {:#04x}", 391 shortName, statusWord, statusMFR, 392 statusTemperature) 393 .c_str()); 394 } 395 tempFault++; 396 } 397 } 398 else 399 { 400 tempFault = 0; 401 } 402 } 403 404 void PowerSupply::analyzePgoodFault() 405 { 406 if ((statusWord & phosphor::pmbus::status_word::POWER_GOOD_NEGATED) || 407 (statusWord & phosphor::pmbus::status_word::UNIT_IS_OFF)) 408 { 409 if (pgoodFault < PGOOD_DEGLITCH_LIMIT) 410 { 411 if (statusWord != statusWordOld) 412 { 413 log<level::ERR>(fmt::format("{} PGOOD fault: " 414 "STATUS_WORD = {:#06x}, " 415 "STATUS_MFR_SPECIFIC = {:#04x}", 416 shortName, statusWord, statusMFR) 417 .c_str()); 418 } 419 pgoodFault++; 420 } 421 } 422 else 423 { 424 pgoodFault = 0; 425 } 426 } 427 428 void PowerSupply::determineMFRFault() 429 { 430 if (bindPath.string().find(IBMCFFPS_DD_NAME) != std::string::npos) 431 { 432 // IBM MFR_SPECIFIC[4] is PS_Kill fault 433 if (statusMFR & 0x10) 434 { 435 if (psKillFault < DEGLITCH_LIMIT) 436 { 437 psKillFault++; 438 } 439 } 440 else 441 { 442 psKillFault = 0; 443 } 444 // IBM MFR_SPECIFIC[6] is 12Vcs fault. 445 if (statusMFR & 0x40) 446 { 447 if (ps12VcsFault < DEGLITCH_LIMIT) 448 { 449 ps12VcsFault++; 450 } 451 } 452 else 453 { 454 ps12VcsFault = 0; 455 } 456 // IBM MFR_SPECIFIC[7] is 12V Current-Share fault. 457 if (statusMFR & 0x80) 458 { 459 if (psCS12VFault < DEGLITCH_LIMIT) 460 { 461 psCS12VFault++; 462 } 463 } 464 else 465 { 466 psCS12VFault = 0; 467 } 468 } 469 } 470 471 void PowerSupply::analyzeMFRFault() 472 { 473 if (statusWord & phosphor::pmbus::status_word::MFR_SPECIFIC_FAULT) 474 { 475 if (mfrFault < DEGLITCH_LIMIT) 476 { 477 if (statusWord != statusWordOld) 478 { 479 log<level::ERR>(fmt::format("{} MFR fault: " 480 "STATUS_WORD = {:#06x} " 481 "STATUS_MFR_SPECIFIC = {:#04x}", 482 shortName, statusWord, statusMFR) 483 .c_str()); 484 } 485 mfrFault++; 486 } 487 488 determineMFRFault(); 489 } 490 else 491 { 492 mfrFault = 0; 493 } 494 } 495 496 void PowerSupply::analyzeVinUVFault() 497 { 498 if (statusWord & phosphor::pmbus::status_word::VIN_UV_FAULT) 499 { 500 if (vinUVFault < DEGLITCH_LIMIT) 501 { 502 if (statusWord != statusWordOld) 503 { 504 log<level::ERR>( 505 fmt::format("{} VIN_UV fault: STATUS_WORD = {:#06x}, " 506 "STATUS_MFR_SPECIFIC = {:#04x}, " 507 "STATUS_INPUT = {:#04x}", 508 shortName, statusWord, statusMFR, statusInput) 509 .c_str()); 510 } 511 vinUVFault++; 512 } 513 // Remember that this PSU has seen an AC fault 514 acFault = AC_FAULT_LIMIT; 515 } 516 else 517 { 518 if (vinUVFault != 0) 519 { 520 log<level::INFO>( 521 fmt::format("{} VIN_UV fault cleared: STATUS_WORD = {:#06x}, " 522 "STATUS_MFR_SPECIFIC = {:#04x}, " 523 "STATUS_INPUT = {:#04x}", 524 shortName, statusWord, statusMFR, statusInput) 525 .c_str()); 526 vinUVFault = 0; 527 } 528 // No AC fail, decrement counter 529 if (acFault != 0) 530 { 531 --acFault; 532 } 533 } 534 } 535 536 void PowerSupply::analyze() 537 { 538 using namespace phosphor::pmbus; 539 540 if (presenceGPIO) 541 { 542 updatePresenceGPIO(); 543 } 544 545 if (present) 546 { 547 try 548 { 549 statusWordOld = statusWord; 550 statusWord = pmbusIntf->read(STATUS_WORD, Type::Debug, 551 (readFail < LOG_LIMIT)); 552 // Read worked, reset the fail count. 553 readFail = 0; 554 555 if (statusWord) 556 { 557 statusInput = pmbusIntf->read(STATUS_INPUT, Type::Debug); 558 if (bindPath.string().find(IBMCFFPS_DD_NAME) != 559 std::string::npos) 560 { 561 statusMFR = pmbusIntf->read(STATUS_MFR, Type::Debug); 562 } 563 statusCML = pmbusIntf->read(STATUS_CML, Type::Debug); 564 auto status0Vout = pmbusIntf->insertPageNum(STATUS_VOUT, 0); 565 statusVout = pmbusIntf->read(status0Vout, Type::Debug); 566 statusIout = pmbusIntf->read(STATUS_IOUT, Type::Debug); 567 statusFans12 = pmbusIntf->read(STATUS_FANS_1_2, Type::Debug); 568 statusTemperature = pmbusIntf->read(STATUS_TEMPERATURE, 569 Type::Debug); 570 571 analyzeCMLFault(); 572 573 analyzeInputFault(); 574 575 analyzeVoutOVFault(); 576 577 analyzeIoutOCFault(); 578 579 analyzeVoutUVFault(); 580 581 analyzeFanFault(); 582 583 analyzeTemperatureFault(); 584 585 analyzePgoodFault(); 586 587 analyzeMFRFault(); 588 589 analyzeVinUVFault(); 590 } 591 else 592 { 593 if (statusWord != statusWordOld) 594 { 595 log<level::INFO>(fmt::format("{} STATUS_WORD = {:#06x}", 596 shortName, statusWord) 597 .c_str()); 598 } 599 600 // if INPUT/VIN_UV fault was on, it cleared, trace it. 601 if (inputFault) 602 { 603 log<level::INFO>( 604 fmt::format( 605 "{} INPUT fault cleared: STATUS_WORD = {:#06x}", 606 shortName, statusWord) 607 .c_str()); 608 } 609 610 if (vinUVFault) 611 { 612 log<level::INFO>( 613 fmt::format("{} VIN_UV cleared: STATUS_WORD = {:#06x}", 614 shortName, statusWord) 615 .c_str()); 616 } 617 618 if (pgoodFault > 0) 619 { 620 log<level::INFO>( 621 fmt::format("{} pgoodFault cleared", shortName) 622 .c_str()); 623 } 624 625 clearFaultFlags(); 626 // No AC fail, decrement counter 627 if (acFault != 0) 628 { 629 --acFault; 630 } 631 } 632 633 // Save off old inputVoltage value. 634 // Get latest inputVoltage. 635 // If voltage went from below minimum, and now is not, clear faults. 636 // Note: getInputVoltage() has its own try/catch. 637 int inputVoltageOld = inputVoltage; 638 double actualInputVoltageOld = actualInputVoltage; 639 getInputVoltage(actualInputVoltage, inputVoltage); 640 if ((inputVoltageOld == in_input::VIN_VOLTAGE_0) && 641 (inputVoltage != in_input::VIN_VOLTAGE_0)) 642 { 643 log<level::INFO>( 644 fmt::format( 645 "{} READ_VIN back in range: actualInputVoltageOld = {} " 646 "actualInputVoltage = {}", 647 shortName, actualInputVoltageOld, actualInputVoltage) 648 .c_str()); 649 clearVinUVFault(); 650 } 651 else if (vinUVFault && (inputVoltage != in_input::VIN_VOLTAGE_0)) 652 { 653 log<level::INFO>( 654 fmt::format( 655 "{} CLEAR_FAULTS: vinUVFault {} actualInputVoltage {}", 656 shortName, vinUVFault, actualInputVoltage) 657 .c_str()); 658 // Do we have a VIN_UV fault latched that can now be cleared 659 // due to voltage back in range? Attempt to clear the 660 // fault(s), re-check faults on next call. 661 clearVinUVFault(); 662 } 663 else if (std::abs(actualInputVoltageOld - actualInputVoltage) > 664 10.0) 665 { 666 log<level::INFO>( 667 fmt::format( 668 "{} actualInputVoltageOld = {} actualInputVoltage = {}", 669 shortName, actualInputVoltageOld, actualInputVoltage) 670 .c_str()); 671 } 672 673 checkAvailability(); 674 675 if (inputHistorySupported) 676 { 677 updateHistory(); 678 } 679 } 680 catch (const ReadFailure& e) 681 { 682 if (readFail < SIZE_MAX) 683 { 684 readFail++; 685 } 686 if (readFail == LOG_LIMIT) 687 { 688 phosphor::logging::commit<ReadFailure>(); 689 } 690 } 691 } 692 } 693 694 void PowerSupply::onOffConfig(uint8_t data) 695 { 696 using namespace phosphor::pmbus; 697 698 if (present) 699 { 700 log<level::INFO>("ON_OFF_CONFIG write", entry("DATA=0x%02X", data)); 701 try 702 { 703 std::vector<uint8_t> configData{data}; 704 pmbusIntf->writeBinary(ON_OFF_CONFIG, configData, 705 Type::HwmonDeviceDebug); 706 } 707 catch (...) 708 { 709 // The underlying code in writeBinary will log a message to the 710 // journal if the write fails. If the ON_OFF_CONFIG is not setup 711 // as desired, later fault detection and analysis code should 712 // catch any of the fall out. We should not need to terminate 713 // the application if this write fails. 714 } 715 } 716 } 717 718 void PowerSupply::clearVinUVFault() 719 { 720 // Read in1_lcrit_alarm to clear bits 3 and 4 of STATUS_INPUT. 721 // The fault bits in STAUTS_INPUT roll-up to STATUS_WORD. Clearing those 722 // bits in STATUS_INPUT should result in the corresponding STATUS_WORD bits 723 // also clearing. 724 // 725 // Do not care about return value. Should be 1 if active, 0 if not. 726 static_cast<void>( 727 pmbusIntf->read("in1_lcrit_alarm", phosphor::pmbus::Type::Hwmon)); 728 vinUVFault = 0; 729 } 730 731 void PowerSupply::clearFaults() 732 { 733 log<level::DEBUG>( 734 fmt::format("clearFaults() inventoryPath: {}", inventoryPath).c_str()); 735 faultLogged = false; 736 // The PMBus device driver does not allow for writing CLEAR_FAULTS 737 // directly. However, the pmbus hwmon device driver code will send a 738 // CLEAR_FAULTS after reading from any of the hwmon "files" in sysfs, so 739 // reading in1_input should result in clearing the fault bits in 740 // STATUS_BYTE/STATUS_WORD. 741 // I do not care what the return value is. 742 if (present) 743 { 744 clearFaultFlags(); 745 checkAvailability(); 746 readFail = 0; 747 748 try 749 { 750 clearVinUVFault(); 751 static_cast<void>( 752 pmbusIntf->read("in1_input", phosphor::pmbus::Type::Hwmon)); 753 } 754 catch (const ReadFailure& e) 755 { 756 // Since I do not care what the return value is, I really do not 757 // care much if it gets a ReadFailure either. However, this 758 // should not prevent the application from continuing to run, so 759 // catching the read failure. 760 } 761 } 762 } 763 764 void PowerSupply::inventoryChanged(sdbusplus::message_t& msg) 765 { 766 std::string msgSensor; 767 std::map<std::string, std::variant<uint32_t, bool>> msgData; 768 msg.read(msgSensor, msgData); 769 770 // Check if it was the Present property that changed. 771 auto valPropMap = msgData.find(PRESENT_PROP); 772 if (valPropMap != msgData.end()) 773 { 774 if (std::get<bool>(valPropMap->second)) 775 { 776 present = true; 777 // TODO: Immediately trying to read or write the "files" causes 778 // read or write failures. 779 using namespace std::chrono_literals; 780 std::this_thread::sleep_for(20ms); 781 pmbusIntf->findHwmonDir(); 782 onOffConfig(phosphor::pmbus::ON_OFF_CONFIG_CONTROL_PIN_ONLY); 783 clearFaults(); 784 updateInventory(); 785 } 786 else 787 { 788 present = false; 789 790 // Clear out the now outdated inventory properties 791 updateInventory(); 792 } 793 checkAvailability(); 794 } 795 } 796 797 void PowerSupply::inventoryAdded(sdbusplus::message_t& msg) 798 { 799 sdbusplus::message::object_path path; 800 msg.read(path); 801 // Make sure the signal is for the PSU inventory path 802 if (path == inventoryPath) 803 { 804 std::map<std::string, std::map<std::string, std::variant<bool>>> 805 interfaces; 806 // Get map of interfaces and their properties 807 msg.read(interfaces); 808 809 auto properties = interfaces.find(INVENTORY_IFACE); 810 if (properties != interfaces.end()) 811 { 812 auto property = properties->second.find(PRESENT_PROP); 813 if (property != properties->second.end()) 814 { 815 present = std::get<bool>(property->second); 816 817 log<level::INFO>(fmt::format("Power Supply {} Present {}", 818 inventoryPath, present) 819 .c_str()); 820 821 updateInventory(); 822 checkAvailability(); 823 } 824 } 825 } 826 } 827 828 auto PowerSupply::readVPDValue(const std::string& vpdName, 829 const phosphor::pmbus::Type& type, 830 const std::size_t& vpdSize) 831 { 832 std::string vpdValue; 833 const std::regex illegalVPDRegex = std::regex("[^[:alnum:]]", 834 std::regex::basic); 835 836 try 837 { 838 vpdValue = pmbusIntf->readString(vpdName, type); 839 } 840 catch (const ReadFailure& e) 841 { 842 // Ignore the read failure, let pmbus code indicate failure, 843 // path... 844 // TODO - ibm918 845 // https://github.com/openbmc/docs/blob/master/designs/vpd-collection.md 846 // The BMC must log errors if any of the VPD cannot be properly 847 // parsed or fails ECC checks. 848 } 849 850 if (vpdValue.size() != vpdSize) 851 { 852 log<level::INFO>(fmt::format("{} {} resize needed. size: {}", shortName, 853 vpdName, vpdValue.size()) 854 .c_str()); 855 vpdValue.resize(vpdSize, ' '); 856 } 857 858 // Replace any illegal values with space(s). 859 std::regex_replace(vpdValue.begin(), vpdValue.begin(), vpdValue.end(), 860 illegalVPDRegex, " "); 861 862 return vpdValue; 863 } 864 865 void PowerSupply::updateInventory() 866 { 867 using namespace phosphor::pmbus; 868 869 #if IBM_VPD 870 std::string pn; 871 std::string fn; 872 std::string header; 873 std::string sn; 874 // The IBM power supply splits the full serial number into two parts. 875 // Each part is 6 bytes long, which should match up with SN_KW_SIZE. 876 const auto HEADER_SIZE = 6; 877 const auto SERIAL_SIZE = 6; 878 // The IBM PSU firmware version size is a bit complicated. It was originally 879 // 1-byte, per command. It was later expanded to 2-bytes per command, then 880 // up to 8-bytes per command. The device driver only reads up to 2 bytes per 881 // command, but combines all three of the 2-byte reads, or all 4 of the 882 // 1-byte reads into one string. So, the maximum size expected is 6 bytes. 883 // However, it is formatted by the driver as a hex string with two ASCII 884 // characters per byte. So the maximum ASCII string size is 12. 885 const auto VERSION_SIZE = 12; 886 887 using PropertyMap = 888 std::map<std::string, 889 std::variant<std::string, std::vector<uint8_t>, bool>>; 890 PropertyMap assetProps; 891 PropertyMap operProps; 892 PropertyMap versionProps; 893 PropertyMap ipzvpdDINFProps; 894 PropertyMap ipzvpdVINIProps; 895 using InterfaceMap = std::map<std::string, PropertyMap>; 896 InterfaceMap interfaces; 897 using ObjectMap = std::map<sdbusplus::message::object_path, InterfaceMap>; 898 ObjectMap object; 899 #endif 900 log<level::DEBUG>( 901 fmt::format("updateInventory() inventoryPath: {}", inventoryPath) 902 .c_str()); 903 904 if (present) 905 { 906 // TODO: non-IBM inventory updates? 907 908 #if IBM_VPD 909 if (driverName != IBMCFFPS_DD_NAME) 910 { 911 getPsuVpdFromDbus("CC", modelName); 912 getPsuVpdFromDbus("PN", pn); 913 getPsuVpdFromDbus("FN", fn); 914 getPsuVpdFromDbus("SN", sn); 915 assetProps.emplace(SN_PROP, sn); 916 } 917 else 918 { 919 modelName = readVPDValue(CCIN, Type::HwmonDeviceDebug, CC_KW_SIZE); 920 pn = readVPDValue(PART_NUMBER, Type::Debug, PN_KW_SIZE); 921 fn = readVPDValue(FRU_NUMBER, Type::Debug, FN_KW_SIZE); 922 923 header = readVPDValue(SERIAL_HEADER, Type::Debug, HEADER_SIZE); 924 sn = readVPDValue(SERIAL_NUMBER, Type::Debug, SERIAL_SIZE); 925 assetProps.emplace(SN_PROP, header + sn); 926 } 927 928 assetProps.emplace(MODEL_PROP, modelName); 929 assetProps.emplace(PN_PROP, pn); 930 assetProps.emplace(SPARE_PN_PROP, fn); 931 932 fwVersion = readVPDValue(FW_VERSION, Type::HwmonDeviceDebug, 933 VERSION_SIZE); 934 versionProps.emplace(VERSION_PROP, fwVersion); 935 936 ipzvpdVINIProps.emplace( 937 "CC", std::vector<uint8_t>(modelName.begin(), modelName.end())); 938 ipzvpdVINIProps.emplace("PN", 939 std::vector<uint8_t>(pn.begin(), pn.end())); 940 ipzvpdVINIProps.emplace("FN", 941 std::vector<uint8_t>(fn.begin(), fn.end())); 942 std::string header_sn = header + sn; 943 ipzvpdVINIProps.emplace( 944 "SN", std::vector<uint8_t>(header_sn.begin(), header_sn.end())); 945 std::string description = "IBM PS"; 946 ipzvpdVINIProps.emplace( 947 "DR", std::vector<uint8_t>(description.begin(), description.end())); 948 949 // Populate the VINI Resource Type (RT) keyword 950 ipzvpdVINIProps.emplace("RT", std::vector<uint8_t>{'V', 'I', 'N', 'I'}); 951 952 // Update the Resource Identifier (RI) keyword 953 // 2 byte FRC: 0x0003 954 // 2 byte RID: 0x1000, 0x1001... 955 std::uint8_t num = std::stoul( 956 inventoryPath.substr(inventoryPath.size() - 1, 1), nullptr, 0); 957 std::vector<uint8_t> ri{0x00, 0x03, 0x10, num}; 958 ipzvpdDINFProps.emplace("RI", ri); 959 960 // Fill in the FRU Label (FL) keyword. 961 std::string fl = "E"; 962 fl.push_back(inventoryPath.back()); 963 fl.resize(FL_KW_SIZE, ' '); 964 ipzvpdDINFProps.emplace("FL", 965 std::vector<uint8_t>(fl.begin(), fl.end())); 966 967 // Populate the DINF Resource Type (RT) keyword 968 ipzvpdDINFProps.emplace("RT", std::vector<uint8_t>{'D', 'I', 'N', 'F'}); 969 970 interfaces.emplace(ASSET_IFACE, std::move(assetProps)); 971 interfaces.emplace(VERSION_IFACE, std::move(versionProps)); 972 interfaces.emplace(DINF_IFACE, std::move(ipzvpdDINFProps)); 973 interfaces.emplace(VINI_IFACE, std::move(ipzvpdVINIProps)); 974 975 // Update the Functional 976 operProps.emplace(FUNCTIONAL_PROP, present); 977 interfaces.emplace(OPERATIONAL_STATE_IFACE, std::move(operProps)); 978 979 auto path = inventoryPath.substr(strlen(INVENTORY_OBJ_PATH)); 980 object.emplace(path, std::move(interfaces)); 981 982 try 983 { 984 auto service = util::getService(INVENTORY_OBJ_PATH, 985 INVENTORY_MGR_IFACE, bus); 986 987 if (service.empty()) 988 { 989 log<level::ERR>("Unable to get inventory manager service"); 990 return; 991 } 992 993 auto method = bus.new_method_call(service.c_str(), 994 INVENTORY_OBJ_PATH, 995 INVENTORY_MGR_IFACE, "Notify"); 996 997 method.append(std::move(object)); 998 999 auto reply = bus.call(method); 1000 } 1001 catch (const std::exception& e) 1002 { 1003 log<level::ERR>( 1004 std::string(e.what() + std::string(" PATH=") + inventoryPath) 1005 .c_str()); 1006 } 1007 #endif 1008 } 1009 } 1010 1011 auto PowerSupply::getMaxPowerOut() const 1012 { 1013 using namespace phosphor::pmbus; 1014 1015 auto maxPowerOut = 0; 1016 1017 if (present) 1018 { 1019 try 1020 { 1021 // Read max_power_out, should be direct format 1022 auto maxPowerOutStr = pmbusIntf->readString(MFR_POUT_MAX, 1023 Type::HwmonDeviceDebug); 1024 log<level::INFO>(fmt::format("{} MFR_POUT_MAX read {}", shortName, 1025 maxPowerOutStr) 1026 .c_str()); 1027 maxPowerOut = std::stod(maxPowerOutStr); 1028 } 1029 catch (const std::exception& e) 1030 { 1031 log<level::ERR>(fmt::format("{} MFR_POUT_MAX read error: {}", 1032 shortName, e.what()) 1033 .c_str()); 1034 } 1035 } 1036 1037 return maxPowerOut; 1038 } 1039 1040 void PowerSupply::setupInputHistory() 1041 { 1042 if (bindPath.string().find(IBMCFFPS_DD_NAME) != std::string::npos) 1043 { 1044 auto maxPowerOut = getMaxPowerOut(); 1045 1046 if (maxPowerOut != phosphor::pmbus::IBM_CFFPS_1400W) 1047 { 1048 // Do not enable input history for power supplies that are missing 1049 if (present) 1050 { 1051 inputHistorySupported = true; 1052 log<level::INFO>( 1053 fmt::format("{} INPUT_HISTORY enabled", shortName).c_str()); 1054 1055 std::string name{fmt::format("{}_input_power", shortName)}; 1056 1057 historyObjectPath = std::string{INPUT_HISTORY_SENSOR_ROOT} + 1058 '/' + name; 1059 1060 // If the power supply was present, we created the 1061 // recordManager. If it then went missing, the recordManager is 1062 // still there. If it then is reinserted, we should be able to 1063 // use the recordManager that was allocated when it was 1064 // initially present. 1065 if (!recordManager) 1066 { 1067 recordManager = std::make_unique<history::RecordManager>( 1068 INPUT_HISTORY_MAX_RECORDS); 1069 } 1070 1071 if (!average) 1072 { 1073 auto avgPath = historyObjectPath + '/' + 1074 history::Average::name; 1075 average = std::make_unique<history::Average>(bus, avgPath); 1076 log<level::DEBUG>( 1077 fmt::format("{} avgPath: {}", shortName, avgPath) 1078 .c_str()); 1079 } 1080 1081 if (!maximum) 1082 { 1083 auto maxPath = historyObjectPath + '/' + 1084 history::Maximum::name; 1085 maximum = std::make_unique<history::Maximum>(bus, maxPath); 1086 log<level::DEBUG>( 1087 fmt::format("{} maxPath: {}", shortName, maxPath) 1088 .c_str()); 1089 } 1090 1091 log<level::DEBUG>(fmt::format("{} historyObjectPath: {}", 1092 shortName, historyObjectPath) 1093 .c_str()); 1094 } 1095 } 1096 else 1097 { 1098 log<level::INFO>( 1099 fmt::format("{} INPUT_HISTORY DISABLED. max_power_out: {}", 1100 shortName, maxPowerOut) 1101 .c_str()); 1102 inputHistorySupported = false; 1103 } 1104 } 1105 else 1106 { 1107 inputHistorySupported = false; 1108 } 1109 } 1110 1111 void PowerSupply::updateHistory() 1112 { 1113 if (!recordManager) 1114 { 1115 // Not enabled 1116 return; 1117 } 1118 1119 if (!present) 1120 { 1121 // Cannot read when not present 1122 return; 1123 } 1124 1125 // Read just the most recent average/max record 1126 auto data = pmbusIntf->readBinary(INPUT_HISTORY, 1127 pmbus::Type::HwmonDeviceDebug, 1128 history::RecordManager::RAW_RECORD_SIZE); 1129 1130 // Update D-Bus only if something changed (a new record ID, or cleared 1131 // out) 1132 auto changed = recordManager->add(data); 1133 if (changed) 1134 { 1135 average->values(std::move(recordManager->getAverageRecords())); 1136 maximum->values(std::move(recordManager->getMaximumRecords())); 1137 } 1138 } 1139 1140 void PowerSupply::getInputVoltage(double& actualInputVoltage, 1141 int& inputVoltage) const 1142 { 1143 using namespace phosphor::pmbus; 1144 1145 actualInputVoltage = in_input::VIN_VOLTAGE_0; 1146 inputVoltage = in_input::VIN_VOLTAGE_0; 1147 1148 if (present) 1149 { 1150 try 1151 { 1152 // Read input voltage in millivolts 1153 auto inputVoltageStr = pmbusIntf->readString(READ_VIN, Type::Hwmon); 1154 1155 // Convert to volts 1156 actualInputVoltage = std::stod(inputVoltageStr) / 1000; 1157 1158 // Calculate the voltage based on voltage thresholds 1159 if (actualInputVoltage < in_input::VIN_VOLTAGE_MIN) 1160 { 1161 inputVoltage = in_input::VIN_VOLTAGE_0; 1162 } 1163 else if (actualInputVoltage < in_input::VIN_VOLTAGE_110_THRESHOLD) 1164 { 1165 inputVoltage = in_input::VIN_VOLTAGE_110; 1166 } 1167 else 1168 { 1169 inputVoltage = in_input::VIN_VOLTAGE_220; 1170 } 1171 } 1172 catch (const std::exception& e) 1173 { 1174 log<level::ERR>( 1175 fmt::format("{} READ_VIN read error: {}", shortName, e.what()) 1176 .c_str()); 1177 } 1178 } 1179 } 1180 1181 void PowerSupply::checkAvailability() 1182 { 1183 bool origAvailability = available; 1184 bool faulted = isPowerOn() && (hasPSKillFault() || hasIoutOCFault()); 1185 available = present && !hasInputFault() && !hasVINUVFault() && !faulted; 1186 1187 if (origAvailability != available) 1188 { 1189 auto invpath = inventoryPath.substr(strlen(INVENTORY_OBJ_PATH)); 1190 phosphor::power::psu::setAvailable(bus, invpath, available); 1191 1192 // Check if the health rollup needs to change based on the 1193 // new availability value. 1194 phosphor::power::psu::handleChassisHealthRollup(bus, inventoryPath, 1195 !available); 1196 } 1197 } 1198 1199 void PowerSupply::setInputVoltageRating() 1200 { 1201 if (!present) 1202 { 1203 if (inputVoltageRatingIface) 1204 { 1205 inputVoltageRatingIface->value(0); 1206 inputVoltageRatingIface.reset(); 1207 } 1208 return; 1209 } 1210 1211 double inputVoltageValue{}; 1212 int inputVoltageRating{}; 1213 getInputVoltage(inputVoltageValue, inputVoltageRating); 1214 1215 if (!inputVoltageRatingIface) 1216 { 1217 auto path = fmt::format( 1218 "/xyz/openbmc_project/sensors/voltage/ps{}_input_voltage_rating", 1219 shortName.back()); 1220 1221 inputVoltageRatingIface = std::make_unique<SensorObject>( 1222 bus, path.c_str(), SensorObject::action::defer_emit); 1223 1224 // Leave other properties at their defaults 1225 inputVoltageRatingIface->unit(SensorInterface::Unit::Volts, true); 1226 inputVoltageRatingIface->value(static_cast<double>(inputVoltageRating), 1227 true); 1228 1229 inputVoltageRatingIface->emit_object_added(); 1230 } 1231 else 1232 { 1233 inputVoltageRatingIface->value(static_cast<double>(inputVoltageRating)); 1234 } 1235 } 1236 1237 void PowerSupply::getPsuVpdFromDbus(const std::string& keyword, 1238 std::string& vpdStr) 1239 { 1240 try 1241 { 1242 std::vector<uint8_t> value; 1243 vpdStr.clear(); 1244 util::getProperty(VINI_IFACE, keyword, inventoryPath, 1245 INVENTORY_MGR_IFACE, bus, value); 1246 for (char c : value) 1247 { 1248 vpdStr += c; 1249 } 1250 } 1251 catch (const sdbusplus::exception_t& e) 1252 { 1253 log<level::ERR>( 1254 fmt::format("Failed getProperty error: {}", e.what()).c_str()); 1255 } 1256 } 1257 } // namespace phosphor::power::psu 1258