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