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