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