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