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         // Remember that this PSU has seen an AC fault
510         acFault = AC_FAULT_LIMIT;
511     }
512     else
513     {
514         if (vinUVFault != 0)
515         {
516             log<level::INFO>(
517                 fmt::format("{} VIN_UV fault cleared: STATUS_WORD = {:#06x}, "
518                             "STATUS_MFR_SPECIFIC = {:#04x}, "
519                             "STATUS_INPUT = {:#04x}",
520                             shortName, statusWord, statusMFR, statusInput)
521                     .c_str());
522             vinUVFault = 0;
523         }
524         // No AC fail, decrement counter
525         if (acFault != 0)
526         {
527             --acFault;
528         }
529     }
530 }
531 
532 void PowerSupply::analyze()
533 {
534     using namespace phosphor::pmbus;
535 
536     if (presenceGPIO)
537     {
538         updatePresenceGPIO();
539     }
540 
541     if (present)
542     {
543         try
544         {
545             statusWordOld = statusWord;
546             statusWord = pmbusIntf->read(STATUS_WORD, Type::Debug,
547                                          (readFail < LOG_LIMIT));
548             // Read worked, reset the fail count.
549             readFail = 0;
550 
551             if (statusWord)
552             {
553                 statusInput = pmbusIntf->read(STATUS_INPUT, Type::Debug);
554                 statusMFR = pmbusIntf->read(STATUS_MFR, Type::Debug);
555                 statusCML = pmbusIntf->read(STATUS_CML, Type::Debug);
556                 auto status0Vout = pmbusIntf->insertPageNum(STATUS_VOUT, 0);
557                 statusVout = pmbusIntf->read(status0Vout, Type::Debug);
558                 statusIout = pmbusIntf->read(STATUS_IOUT, Type::Debug);
559                 statusFans12 = pmbusIntf->read(STATUS_FANS_1_2, Type::Debug);
560                 statusTemperature =
561                     pmbusIntf->read(STATUS_TEMPERATURE, Type::Debug);
562 
563                 analyzeCMLFault();
564 
565                 analyzeInputFault();
566 
567                 analyzeVoutOVFault();
568 
569                 analyzeIoutOCFault();
570 
571                 analyzeVoutUVFault();
572 
573                 analyzeFanFault();
574 
575                 analyzeTemperatureFault();
576 
577                 analyzePgoodFault();
578 
579                 analyzeMFRFault();
580 
581                 analyzeVinUVFault();
582             }
583             else
584             {
585                 if (statusWord != statusWordOld)
586                 {
587                     log<level::INFO>(fmt::format("{} STATUS_WORD = {:#06x}",
588                                                  shortName, statusWord)
589                                          .c_str());
590                 }
591 
592                 // if INPUT/VIN_UV fault was on, it cleared, trace it.
593                 if (inputFault)
594                 {
595                     log<level::INFO>(
596                         fmt::format(
597                             "{} INPUT fault cleared: STATUS_WORD = {:#06x}",
598                             shortName, statusWord)
599                             .c_str());
600                 }
601 
602                 if (vinUVFault)
603                 {
604                     log<level::INFO>(
605                         fmt::format("{} VIN_UV cleared: STATUS_WORD = {:#06x}",
606                                     shortName, statusWord)
607                             .c_str());
608                 }
609 
610                 if (pgoodFault > 0)
611                 {
612                     log<level::INFO>(
613                         fmt::format("{} pgoodFault cleared", shortName)
614                             .c_str());
615                 }
616 
617                 clearFaultFlags();
618                 // No AC fail, decrement counter
619                 if (acFault != 0)
620                 {
621                     --acFault;
622                 }
623             }
624 
625             // Save off old inputVoltage value.
626             // Get latest inputVoltage.
627             // If voltage went from below minimum, and now is not, clear faults.
628             // Note: getInputVoltage() has its own try/catch.
629             int inputVoltageOld = inputVoltage;
630             double actualInputVoltageOld = actualInputVoltage;
631             getInputVoltage(actualInputVoltage, inputVoltage);
632             if ((inputVoltageOld == in_input::VIN_VOLTAGE_0) &&
633                 (inputVoltage != in_input::VIN_VOLTAGE_0))
634             {
635                 log<level::INFO>(
636                     fmt::format(
637                         "{} READ_VIN back in range: actualInputVoltageOld = {} "
638                         "actualInputVoltage = {}",
639                         shortName, actualInputVoltageOld, actualInputVoltage)
640                         .c_str());
641                 clearVinUVFault();
642             }
643             else if (vinUVFault && (inputVoltage != in_input::VIN_VOLTAGE_0))
644             {
645                 log<level::INFO>(
646                     fmt::format(
647                         "{} CLEAR_FAULTS: vinUVFault {} actualInputVoltage {}",
648                         shortName, vinUVFault, actualInputVoltage)
649                         .c_str());
650                 // Do we have a VIN_UV fault latched that can now be cleared
651                 // due to voltage back in range? Attempt to clear the
652                 // fault(s), re-check faults on next call.
653                 clearVinUVFault();
654             }
655             else if (std::abs(actualInputVoltageOld - actualInputVoltage) >
656                      10.0)
657             {
658                 log<level::INFO>(
659                     fmt::format(
660                         "{} actualInputVoltageOld = {} actualInputVoltage = {}",
661                         shortName, actualInputVoltageOld, actualInputVoltage)
662                         .c_str());
663             }
664 
665             checkAvailability();
666 
667             if (inputHistorySupported)
668             {
669                 updateHistory();
670             }
671         }
672         catch (const ReadFailure& e)
673         {
674             if (readFail < SIZE_MAX)
675             {
676                 readFail++;
677             }
678             if (readFail == LOG_LIMIT)
679             {
680                 phosphor::logging::commit<ReadFailure>();
681             }
682         }
683     }
684 }
685 
686 void PowerSupply::onOffConfig(uint8_t data)
687 {
688     using namespace phosphor::pmbus;
689 
690     if (present)
691     {
692         log<level::INFO>("ON_OFF_CONFIG write", entry("DATA=0x%02X", data));
693         try
694         {
695             std::vector<uint8_t> configData{data};
696             pmbusIntf->writeBinary(ON_OFF_CONFIG, configData,
697                                    Type::HwmonDeviceDebug);
698         }
699         catch (...)
700         {
701             // The underlying code in writeBinary will log a message to the
702             // journal if the write fails. If the ON_OFF_CONFIG is not setup
703             // as desired, later fault detection and analysis code should
704             // catch any of the fall out. We should not need to terminate
705             // the application if this write fails.
706         }
707     }
708 }
709 
710 void PowerSupply::clearVinUVFault()
711 {
712     // Read in1_lcrit_alarm to clear bits 3 and 4 of STATUS_INPUT.
713     // The fault bits in STAUTS_INPUT roll-up to STATUS_WORD. Clearing those
714     // bits in STATUS_INPUT should result in the corresponding STATUS_WORD bits
715     // also clearing.
716     //
717     // Do not care about return value. Should be 1 if active, 0 if not.
718     static_cast<void>(
719         pmbusIntf->read("in1_lcrit_alarm", phosphor::pmbus::Type::Hwmon));
720     vinUVFault = 0;
721 }
722 
723 void PowerSupply::clearFaults()
724 {
725     log<level::DEBUG>(
726         fmt::format("clearFaults() inventoryPath: {}", inventoryPath).c_str());
727     faultLogged = false;
728     // The PMBus device driver does not allow for writing CLEAR_FAULTS
729     // directly. However, the pmbus hwmon device driver code will send a
730     // CLEAR_FAULTS after reading from any of the hwmon "files" in sysfs, so
731     // reading in1_input should result in clearing the fault bits in
732     // STATUS_BYTE/STATUS_WORD.
733     // I do not care what the return value is.
734     if (present)
735     {
736         clearFaultFlags();
737         checkAvailability();
738         readFail = 0;
739 
740         try
741         {
742             clearVinUVFault();
743             static_cast<void>(
744                 pmbusIntf->read("in1_input", phosphor::pmbus::Type::Hwmon));
745         }
746         catch (const ReadFailure& e)
747         {
748             // Since I do not care what the return value is, I really do not
749             // care much if it gets a ReadFailure either. However, this
750             // should not prevent the application from continuing to run, so
751             // catching the read failure.
752         }
753     }
754 }
755 
756 void PowerSupply::inventoryChanged(sdbusplus::message_t& msg)
757 {
758     std::string msgSensor;
759     std::map<std::string, std::variant<uint32_t, bool>> msgData;
760     msg.read(msgSensor, msgData);
761 
762     // Check if it was the Present property that changed.
763     auto valPropMap = msgData.find(PRESENT_PROP);
764     if (valPropMap != msgData.end())
765     {
766         if (std::get<bool>(valPropMap->second))
767         {
768             present = true;
769             // TODO: Immediately trying to read or write the "files" causes
770             // read or write failures.
771             using namespace std::chrono_literals;
772             std::this_thread::sleep_for(20ms);
773             pmbusIntf->findHwmonDir();
774             onOffConfig(phosphor::pmbus::ON_OFF_CONFIG_CONTROL_PIN_ONLY);
775             clearFaults();
776             updateInventory();
777         }
778         else
779         {
780             present = false;
781 
782             // Clear out the now outdated inventory properties
783             updateInventory();
784         }
785         checkAvailability();
786     }
787 }
788 
789 void PowerSupply::inventoryAdded(sdbusplus::message_t& msg)
790 {
791     sdbusplus::message::object_path path;
792     msg.read(path);
793     // Make sure the signal is for the PSU inventory path
794     if (path == inventoryPath)
795     {
796         std::map<std::string, std::map<std::string, std::variant<bool>>>
797             interfaces;
798         // Get map of interfaces and their properties
799         msg.read(interfaces);
800 
801         auto properties = interfaces.find(INVENTORY_IFACE);
802         if (properties != interfaces.end())
803         {
804             auto property = properties->second.find(PRESENT_PROP);
805             if (property != properties->second.end())
806             {
807                 present = std::get<bool>(property->second);
808 
809                 log<level::INFO>(fmt::format("Power Supply {} Present {}",
810                                              inventoryPath, present)
811                                      .c_str());
812 
813                 updateInventory();
814                 checkAvailability();
815             }
816         }
817     }
818 }
819 
820 auto PowerSupply::readVPDValue(const std::string& vpdName,
821                                const phosphor::pmbus::Type& type,
822                                const std::size_t& vpdSize)
823 {
824     std::string vpdValue;
825     const std::regex illegalVPDRegex =
826         std::regex("[^[:alnum:]]", std::regex::basic);
827 
828     try
829     {
830         vpdValue = pmbusIntf->readString(vpdName, type);
831     }
832     catch (const ReadFailure& e)
833     {
834         // Ignore the read failure, let pmbus code indicate failure,
835         // path...
836         // TODO - ibm918
837         // https://github.com/openbmc/docs/blob/master/designs/vpd-collection.md
838         // The BMC must log errors if any of the VPD cannot be properly
839         // parsed or fails ECC checks.
840     }
841 
842     if (vpdValue.size() != vpdSize)
843     {
844         log<level::INFO>(fmt::format("{} {} resize needed. size: {}", shortName,
845                                      vpdName, vpdValue.size())
846                              .c_str());
847         vpdValue.resize(vpdSize, ' ');
848     }
849 
850     // Replace any illegal values with space(s).
851     std::regex_replace(vpdValue.begin(), vpdValue.begin(), vpdValue.end(),
852                        illegalVPDRegex, " ");
853 
854     return vpdValue;
855 }
856 
857 void PowerSupply::updateInventory()
858 {
859     using namespace phosphor::pmbus;
860 
861 #if IBM_VPD
862     std::string pn;
863     std::string fn;
864     std::string header;
865     std::string sn;
866     // The IBM power supply splits the full serial number into two parts.
867     // Each part is 6 bytes long, which should match up with SN_KW_SIZE.
868     const auto HEADER_SIZE = 6;
869     const auto SERIAL_SIZE = 6;
870     // The IBM PSU firmware version size is a bit complicated. It was originally
871     // 1-byte, per command. It was later expanded to 2-bytes per command, then
872     // up to 8-bytes per command. The device driver only reads up to 2 bytes per
873     // command, but combines all three of the 2-byte reads, or all 4 of the
874     // 1-byte reads into one string. So, the maximum size expected is 6 bytes.
875     // However, it is formatted by the driver as a hex string with two ASCII
876     // characters per byte.  So the maximum ASCII string size is 12.
877     const auto VERSION_SIZE = 12;
878 
879     using PropertyMap =
880         std::map<std::string,
881                  std::variant<std::string, std::vector<uint8_t>, bool>>;
882     PropertyMap assetProps;
883     PropertyMap operProps;
884     PropertyMap versionProps;
885     PropertyMap ipzvpdDINFProps;
886     PropertyMap ipzvpdVINIProps;
887     using InterfaceMap = std::map<std::string, PropertyMap>;
888     InterfaceMap interfaces;
889     using ObjectMap = std::map<sdbusplus::message::object_path, InterfaceMap>;
890     ObjectMap object;
891 #endif
892     log<level::DEBUG>(
893         fmt::format("updateInventory() inventoryPath: {}", inventoryPath)
894             .c_str());
895 
896     if (present)
897     {
898         // TODO: non-IBM inventory updates?
899 
900 #if IBM_VPD
901         modelName = readVPDValue(CCIN, Type::HwmonDeviceDebug, CC_KW_SIZE);
902         assetProps.emplace(MODEL_PROP, modelName);
903 
904         pn = readVPDValue(PART_NUMBER, Type::HwmonDeviceDebug, PN_KW_SIZE);
905         assetProps.emplace(PN_PROP, pn);
906 
907         fn = readVPDValue(FRU_NUMBER, Type::HwmonDeviceDebug, FN_KW_SIZE);
908         assetProps.emplace(SPARE_PN_PROP, fn);
909 
910         header =
911             readVPDValue(SERIAL_HEADER, Type::HwmonDeviceDebug, HEADER_SIZE);
912         sn = readVPDValue(SERIAL_NUMBER, Type::HwmonDeviceDebug, SERIAL_SIZE);
913         assetProps.emplace(SN_PROP, header + sn);
914 
915         fwVersion =
916             readVPDValue(FW_VERSION, Type::HwmonDeviceDebug, VERSION_SIZE);
917         versionProps.emplace(VERSION_PROP, fwVersion);
918 
919         ipzvpdVINIProps.emplace(
920             "CC", std::vector<uint8_t>(modelName.begin(), modelName.end()));
921         ipzvpdVINIProps.emplace("PN",
922                                 std::vector<uint8_t>(pn.begin(), pn.end()));
923         ipzvpdVINIProps.emplace("FN",
924                                 std::vector<uint8_t>(fn.begin(), fn.end()));
925         std::string header_sn = header + sn;
926         ipzvpdVINIProps.emplace(
927             "SN", std::vector<uint8_t>(header_sn.begin(), header_sn.end()));
928         std::string description = "IBM PS";
929         ipzvpdVINIProps.emplace(
930             "DR", std::vector<uint8_t>(description.begin(), description.end()));
931 
932         // Populate the VINI Resource Type (RT) keyword
933         ipzvpdVINIProps.emplace("RT", std::vector<uint8_t>{'V', 'I', 'N', 'I'});
934 
935         // Update the Resource Identifier (RI) keyword
936         // 2 byte FRC: 0x0003
937         // 2 byte RID: 0x1000, 0x1001...
938         std::uint8_t num = std::stoul(
939             inventoryPath.substr(inventoryPath.size() - 1, 1), nullptr, 0);
940         std::vector<uint8_t> ri{0x00, 0x03, 0x10, num};
941         ipzvpdDINFProps.emplace("RI", ri);
942 
943         // Fill in the FRU Label (FL) keyword.
944         std::string fl = "E";
945         fl.push_back(inventoryPath.back());
946         fl.resize(FL_KW_SIZE, ' ');
947         ipzvpdDINFProps.emplace("FL",
948                                 std::vector<uint8_t>(fl.begin(), fl.end()));
949 
950         // Populate the DINF Resource Type (RT) keyword
951         ipzvpdDINFProps.emplace("RT", std::vector<uint8_t>{'D', 'I', 'N', 'F'});
952 
953         interfaces.emplace(ASSET_IFACE, std::move(assetProps));
954         interfaces.emplace(VERSION_IFACE, std::move(versionProps));
955         interfaces.emplace(DINF_IFACE, std::move(ipzvpdDINFProps));
956         interfaces.emplace(VINI_IFACE, std::move(ipzvpdVINIProps));
957 
958         // Update the Functional
959         operProps.emplace(FUNCTIONAL_PROP, present);
960         interfaces.emplace(OPERATIONAL_STATE_IFACE, std::move(operProps));
961 
962         auto path = inventoryPath.substr(strlen(INVENTORY_OBJ_PATH));
963         object.emplace(path, std::move(interfaces));
964 
965         try
966         {
967             auto service =
968                 util::getService(INVENTORY_OBJ_PATH, INVENTORY_MGR_IFACE, bus);
969 
970             if (service.empty())
971             {
972                 log<level::ERR>("Unable to get inventory manager service");
973                 return;
974             }
975 
976             auto method =
977                 bus.new_method_call(service.c_str(), INVENTORY_OBJ_PATH,
978                                     INVENTORY_MGR_IFACE, "Notify");
979 
980             method.append(std::move(object));
981 
982             auto reply = bus.call(method);
983         }
984         catch (const std::exception& e)
985         {
986             log<level::ERR>(
987                 std::string(e.what() + std::string(" PATH=") + inventoryPath)
988                     .c_str());
989         }
990 #endif
991     }
992 }
993 
994 auto PowerSupply::getMaxPowerOut() const
995 {
996     using namespace phosphor::pmbus;
997 
998     auto maxPowerOut = 0;
999 
1000     if (present)
1001     {
1002         try
1003         {
1004             // Read max_power_out, should be direct format
1005             auto maxPowerOutStr =
1006                 pmbusIntf->readString(MFR_POUT_MAX, Type::HwmonDeviceDebug);
1007             log<level::INFO>(fmt::format("{} MFR_POUT_MAX read {}", shortName,
1008                                          maxPowerOutStr)
1009                                  .c_str());
1010             maxPowerOut = std::stod(maxPowerOutStr);
1011         }
1012         catch (const std::exception& e)
1013         {
1014             log<level::ERR>(fmt::format("{} MFR_POUT_MAX read error: {}",
1015                                         shortName, e.what())
1016                                 .c_str());
1017         }
1018     }
1019 
1020     return maxPowerOut;
1021 }
1022 
1023 void PowerSupply::setupInputHistory()
1024 {
1025     if (bindPath.string().find("ibm-cffps") != std::string::npos)
1026     {
1027         auto maxPowerOut = getMaxPowerOut();
1028 
1029         if (maxPowerOut != phosphor::pmbus::IBM_CFFPS_1400W)
1030         {
1031             // Do not enable input history for power supplies that are missing
1032             if (present)
1033             {
1034                 inputHistorySupported = true;
1035                 log<level::INFO>(
1036                     fmt::format("{} INPUT_HISTORY enabled", shortName).c_str());
1037 
1038                 std::string name{fmt::format("{}_input_power", shortName)};
1039 
1040                 historyObjectPath =
1041                     std::string{INPUT_HISTORY_SENSOR_ROOT} + '/' + name;
1042 
1043                 // If the power supply was present, we created the
1044                 // recordManager. If it then went missing, the recordManager is
1045                 // still there. If it then is reinserted, we should be able to
1046                 // use the recordManager that was allocated when it was
1047                 // initially present.
1048                 if (!recordManager)
1049                 {
1050                     recordManager = std::make_unique<history::RecordManager>(
1051                         INPUT_HISTORY_MAX_RECORDS);
1052                 }
1053 
1054                 if (!average)
1055                 {
1056                     auto avgPath =
1057                         historyObjectPath + '/' + history::Average::name;
1058                     average = std::make_unique<history::Average>(bus, avgPath);
1059                     log<level::DEBUG>(
1060                         fmt::format("{} avgPath: {}", shortName, avgPath)
1061                             .c_str());
1062                 }
1063 
1064                 if (!maximum)
1065                 {
1066                     auto maxPath =
1067                         historyObjectPath + '/' + history::Maximum::name;
1068                     maximum = std::make_unique<history::Maximum>(bus, maxPath);
1069                     log<level::DEBUG>(
1070                         fmt::format("{} maxPath: {}", shortName, maxPath)
1071                             .c_str());
1072                 }
1073 
1074                 log<level::DEBUG>(fmt::format("{} historyObjectPath: {}",
1075                                               shortName, historyObjectPath)
1076                                       .c_str());
1077             }
1078         }
1079         else
1080         {
1081             log<level::INFO>(
1082                 fmt::format("{} INPUT_HISTORY DISABLED. max_power_out: {}",
1083                             shortName, maxPowerOut)
1084                     .c_str());
1085             inputHistorySupported = false;
1086         }
1087     }
1088     else
1089     {
1090         inputHistorySupported = false;
1091     }
1092 }
1093 
1094 void PowerSupply::updateHistory()
1095 {
1096     if (!recordManager)
1097     {
1098         // Not enabled
1099         return;
1100     }
1101 
1102     if (!present)
1103     {
1104         // Cannot read when not present
1105         return;
1106     }
1107 
1108     // Read just the most recent average/max record
1109     auto data =
1110         pmbusIntf->readBinary(INPUT_HISTORY, pmbus::Type::HwmonDeviceDebug,
1111                               history::RecordManager::RAW_RECORD_SIZE);
1112 
1113     // Update D-Bus only if something changed (a new record ID, or cleared
1114     // out)
1115     auto changed = recordManager->add(data);
1116     if (changed)
1117     {
1118         average->values(std::move(recordManager->getAverageRecords()));
1119         maximum->values(std::move(recordManager->getMaximumRecords()));
1120     }
1121 }
1122 
1123 void PowerSupply::getInputVoltage(double& actualInputVoltage,
1124                                   int& inputVoltage) const
1125 {
1126     using namespace phosphor::pmbus;
1127 
1128     actualInputVoltage = in_input::VIN_VOLTAGE_0;
1129     inputVoltage = in_input::VIN_VOLTAGE_0;
1130 
1131     if (present)
1132     {
1133         try
1134         {
1135             // Read input voltage in millivolts
1136             auto inputVoltageStr = pmbusIntf->readString(READ_VIN, Type::Hwmon);
1137 
1138             // Convert to volts
1139             actualInputVoltage = std::stod(inputVoltageStr) / 1000;
1140 
1141             // Calculate the voltage based on voltage thresholds
1142             if (actualInputVoltage < in_input::VIN_VOLTAGE_MIN)
1143             {
1144                 inputVoltage = in_input::VIN_VOLTAGE_0;
1145             }
1146             else if (actualInputVoltage < in_input::VIN_VOLTAGE_110_THRESHOLD)
1147             {
1148                 inputVoltage = in_input::VIN_VOLTAGE_110;
1149             }
1150             else
1151             {
1152                 inputVoltage = in_input::VIN_VOLTAGE_220;
1153             }
1154         }
1155         catch (const std::exception& e)
1156         {
1157             log<level::ERR>(
1158                 fmt::format("{} READ_VIN read error: {}", shortName, e.what())
1159                     .c_str());
1160         }
1161     }
1162 }
1163 
1164 void PowerSupply::checkAvailability()
1165 {
1166     bool origAvailability = available;
1167     available = present && !hasInputFault() && !hasVINUVFault() &&
1168                 !hasPSKillFault() && !hasIoutOCFault();
1169 
1170     if (origAvailability != available)
1171     {
1172         auto invpath = inventoryPath.substr(strlen(INVENTORY_OBJ_PATH));
1173         phosphor::power::psu::setAvailable(bus, invpath, available);
1174 
1175         // Check if the health rollup needs to change based on the
1176         // new availability value.
1177         phosphor::power::psu::handleChassisHealthRollup(bus, inventoryPath,
1178                                                         !available);
1179     }
1180 }
1181 
1182 } // namespace phosphor::power::psu
1183