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