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