1 /**
2  * Copyright © 2017 IBM Corporation
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 #include "config.h"
17 
18 #include "power_supply.hpp"
19 
20 #include "elog-errors.hpp"
21 #include "gpio.hpp"
22 #include "names_values.hpp"
23 #include "pmbus.hpp"
24 #include "types.hpp"
25 #include "utility.hpp"
26 
27 #include <org/open_power/Witherspoon/Fault/error.hpp>
28 #include <phosphor-logging/log.hpp>
29 #include <xyz/openbmc_project/Common/Device/error.hpp>
30 #include <xyz/openbmc_project/Software/Version/server.hpp>
31 
32 #include <functional>
33 
34 namespace phosphor
35 {
36 namespace power
37 {
38 namespace psu
39 {
40 
41 using namespace phosphor::logging;
42 using namespace sdbusplus::org::open_power::Witherspoon::Fault::Error;
43 using namespace sdbusplus::xyz::openbmc_project::Common::Device::Error;
44 namespace version = sdbusplus::xyz::openbmc_project::Software::server;
45 
46 PowerSupply::PowerSupply(const std::string& name, size_t inst,
47                          const std::string& objpath, const std::string& invpath,
48                          sdbusplus::bus::bus& bus, const sdeventplus::Event& e,
49                          std::chrono::seconds& t, std::chrono::seconds& p) :
50     Device(name, inst),
51     monitorPath(objpath), pmbusIntf(objpath),
52     inventoryPath(INVENTORY_OBJ_PATH + invpath), bus(bus), presentInterval(p),
53     presentTimer(e, std::bind([this]() {
54                      // The hwmon path may have changed.
55                      pmbusIntf.findHwmonDir();
56                      this->present = true;
57 
58                      // Sync the INPUT_HISTORY data for all PSs
59                      syncHistory();
60 
61                      // Update the inventory for the new device
62                      updateInventory();
63                  })),
64     powerOnInterval(t),
65     powerOnTimer(e, std::bind([this]() { this->powerOn = true; }))
66 {
67     getAccessType();
68 
69     using namespace sdbusplus::bus;
70     using namespace phosphor::pmbus;
71     std::uint16_t statusWord = 0;
72     try
73     {
74         // Read the 2 byte STATUS_WORD value to check for faults.
75         statusWord = pmbusIntf.read(STATUS_WORD, Type::Debug);
76         if (!((statusWord & status_word::INPUT_FAULT_WARN) ||
77               (statusWord & status_word::VIN_UV_FAULT)))
78         {
79             resolveError(inventoryPath,
80                          std::string(PowerSupplyInputFault::errName));
81         }
82     }
83     catch (ReadFailure& e)
84     {
85         log<level::INFO>("Unable to read the 2 byte STATUS_WORD value to check "
86                          "for power-supply input faults.");
87     }
88     presentMatch = std::make_unique<match_t>(
89         bus, match::rules::propertiesChanged(inventoryPath, INVENTORY_IFACE),
90         [this](auto& msg) { this->inventoryChanged(msg); });
91     // Get initial presence state.
92     updatePresence();
93 
94     // Write the SN, PN, etc to the inventory
95     updateInventory();
96 
97     // Subscribe to power state changes
98     powerOnMatch = std::make_unique<match_t>(
99         bus, match::rules::propertiesChanged(POWER_OBJ_PATH, POWER_IFACE),
100         [this](auto& msg) { this->powerStateChanged(msg); });
101     // Get initial power state.
102     updatePowerState();
103 }
104 
105 void PowerSupply::getAccessType()
106 {
107     using namespace phosphor::power::util;
108     fruJson = loadJSONFromFile(PSU_JSON_PATH);
109     if (fruJson == nullptr)
110     {
111         log<level::ERR>("InternalFailure when parsing the JSON file");
112         return;
113     }
114     inventoryPMBusAccessType = getPMBusAccessType(fruJson);
115 }
116 
117 void PowerSupply::captureCmd(util::NamesValues& nv, const std::string& cmd,
118                              phosphor::pmbus::Type type)
119 {
120     if (pmbusIntf.exists(cmd, type))
121     {
122         try
123         {
124             auto val = pmbusIntf.read(cmd, type);
125             nv.add(cmd, val);
126         }
127         catch (std::exception& e)
128         {
129             log<level::INFO>("Unable to capture metadata",
130                              entry("CMD=%s", cmd.c_str()));
131         }
132     }
133 }
134 
135 void PowerSupply::analyze()
136 {
137     using namespace phosphor::pmbus;
138 
139     try
140     {
141         if (present)
142         {
143             std::uint16_t statusWord = 0;
144 
145             // Read the 2 byte STATUS_WORD value to check for faults.
146             statusWord = pmbusIntf.read(STATUS_WORD, Type::Debug);
147             readFail = 0;
148 
149             checkInputFault(statusWord);
150 
151             if (powerOn && (inputFault == 0) && !faultFound)
152             {
153                 checkFanFault(statusWord);
154                 checkTemperatureFault(statusWord);
155                 checkOutputOvervoltageFault(statusWord);
156                 checkCurrentOutOverCurrentFault(statusWord);
157                 checkPGOrUnitOffFault(statusWord);
158             }
159 
160             updateHistory();
161         }
162     }
163     catch (ReadFailure& e)
164     {
165         if (readFail < FAULT_COUNT)
166         {
167             readFail++;
168         }
169 
170         if (!readFailLogged && readFail >= FAULT_COUNT)
171         {
172             commit<ReadFailure>();
173             readFailLogged = true;
174         }
175     }
176 
177     return;
178 }
179 
180 void PowerSupply::inventoryChanged(sdbusplus::message::message& msg)
181 {
182     std::string msgSensor;
183     std::map<std::string, sdbusplus::message::variant<uint32_t, bool>> msgData;
184     msg.read(msgSensor, msgData);
185 
186     // Check if it was the Present property that changed.
187     auto valPropMap = msgData.find(PRESENT_PROP);
188     if (valPropMap != msgData.end())
189     {
190         if (sdbusplus::message::variant_ns::get<bool>(valPropMap->second))
191         {
192             clearFaults();
193             presentTimer.restartOnce(presentInterval);
194         }
195         else
196         {
197             present = false;
198             presentTimer.setEnabled(false);
199 
200             // Clear out the now outdated inventory properties
201             updateInventory();
202         }
203     }
204 
205     return;
206 }
207 
208 void PowerSupply::updatePresence()
209 {
210     // Use getProperty utility function to get presence status.
211     std::string service = "xyz.openbmc_project.Inventory.Manager";
212     util::getProperty(INVENTORY_IFACE, PRESENT_PROP, inventoryPath, service,
213                       bus, this->present);
214 }
215 
216 void PowerSupply::powerStateChanged(sdbusplus::message::message& msg)
217 {
218     int32_t state = 0;
219     std::string msgSensor;
220     std::map<std::string, sdbusplus::message::variant<int32_t>> msgData;
221     msg.read(msgSensor, msgData);
222 
223     // Check if it was the Present property that changed.
224     auto valPropMap = msgData.find("state");
225     if (valPropMap != msgData.end())
226     {
227         state =
228             sdbusplus::message::variant_ns::get<int32_t>(valPropMap->second);
229 
230         // Power is on when state=1. Set the fault logged variables to false
231         // and start the power on timer when the state changes to 1.
232         if (state)
233         {
234             clearFaults();
235             powerOnTimer.restartOnce(powerOnInterval);
236         }
237         else
238         {
239             powerOnTimer.setEnabled(false);
240             powerOn = false;
241         }
242     }
243 }
244 
245 void PowerSupply::updatePowerState()
246 {
247     powerOn = util::isPoweredOn(bus);
248 }
249 
250 void PowerSupply::checkInputFault(const uint16_t statusWord)
251 {
252     using namespace phosphor::pmbus;
253 
254     if ((inputFault < FAULT_COUNT) &&
255         ((statusWord & status_word::INPUT_FAULT_WARN) ||
256          (statusWord & status_word::VIN_UV_FAULT)))
257     {
258         if (inputFault == 0)
259         {
260             log<level::INFO>("INPUT or VIN_UV fault",
261                              entry("STATUS_WORD=0x%04X", statusWord));
262         }
263 
264         inputFault++;
265     }
266     else
267     {
268         if ((inputFault > 0) && !(statusWord & status_word::INPUT_FAULT_WARN) &&
269             !(statusWord & status_word::VIN_UV_FAULT))
270         {
271             inputFault = 0;
272             faultFound = false;
273             // When an input fault occurs, the power supply cannot be on.
274             // However, the check for the case where the power supply should be
275             // on will stop when there is a fault found.
276             // Clear the powerOnFault when the inputFault is cleared to reset
277             // the powerOnFault de-glitching.
278             powerOnFault = 0;
279 
280             log<level::INFO>("INPUT_FAULT_WARN cleared",
281                              entry("POWERSUPPLY=%s", inventoryPath.c_str()));
282 
283             resolveError(inventoryPath,
284                          std::string(PowerSupplyInputFault::errName));
285 
286             if (powerOn)
287             {
288                 // The power supply will not be immediately powered on after
289                 // the input power is restored.
290                 powerOn = false;
291                 // Start up the timer that will set the state to indicate we
292                 // are ready for the powered on fault checks.
293                 powerOnTimer.restartOnce(powerOnInterval);
294             }
295         }
296     }
297 
298     if (!faultFound && (inputFault >= FAULT_COUNT))
299     {
300         // If the power is on, report the fault in an error log entry.
301         if (powerOn)
302         {
303             util::NamesValues nv;
304             nv.add("STATUS_WORD", statusWord);
305             captureCmd(nv, STATUS_INPUT, Type::Debug);
306 
307             using metadata =
308                 org::open_power::Witherspoon::Fault::PowerSupplyInputFault;
309 
310             report<PowerSupplyInputFault>(
311                 metadata::RAW_STATUS(nv.get().c_str()),
312                 metadata::CALLOUT_INVENTORY_PATH(inventoryPath.c_str()));
313 
314             faultFound = true;
315         }
316     }
317 }
318 
319 void PowerSupply::checkPGOrUnitOffFault(const uint16_t statusWord)
320 {
321     using namespace phosphor::pmbus;
322 
323     if (powerOnFault < FAULT_COUNT)
324     {
325         // Check PG# and UNIT_IS_OFF
326         if ((statusWord & status_word::POWER_GOOD_NEGATED) ||
327             (statusWord & status_word::UNIT_IS_OFF))
328         {
329             log<level::INFO>("PGOOD or UNIT_IS_OFF bit bad",
330                              entry("STATUS_WORD=0x%04X", statusWord));
331             powerOnFault++;
332         }
333         else
334         {
335             if (powerOnFault > 0)
336             {
337                 log<level::INFO>("PGOOD and UNIT_IS_OFF bits good");
338                 powerOnFault = 0;
339             }
340         }
341 
342         if (!faultFound && (powerOnFault >= FAULT_COUNT))
343         {
344             faultFound = true;
345 
346             util::NamesValues nv;
347             nv.add("STATUS_WORD", statusWord);
348             captureCmd(nv, STATUS_INPUT, Type::Debug);
349             auto status0Vout = pmbusIntf.insertPageNum(STATUS_VOUT, 0);
350             captureCmd(nv, status0Vout, Type::Debug);
351             captureCmd(nv, STATUS_IOUT, Type::Debug);
352             captureCmd(nv, STATUS_MFR, Type::Debug);
353 
354             using metadata =
355                 org::open_power::Witherspoon::Fault::PowerSupplyShouldBeOn;
356 
357             // A power supply is OFF (or pgood low) but should be on.
358             report<PowerSupplyShouldBeOn>(
359                 metadata::RAW_STATUS(nv.get().c_str()),
360                 metadata::CALLOUT_INVENTORY_PATH(inventoryPath.c_str()));
361         }
362     }
363 }
364 
365 void PowerSupply::checkCurrentOutOverCurrentFault(const uint16_t statusWord)
366 {
367     using namespace phosphor::pmbus;
368 
369     if (outputOCFault < FAULT_COUNT)
370     {
371         // Check for an output overcurrent fault.
372         if ((statusWord & status_word::IOUT_OC_FAULT))
373         {
374             outputOCFault++;
375         }
376         else
377         {
378             if (outputOCFault > 0)
379             {
380                 outputOCFault = 0;
381             }
382         }
383 
384         if (!faultFound && (outputOCFault >= FAULT_COUNT))
385         {
386             util::NamesValues nv;
387             nv.add("STATUS_WORD", statusWord);
388             captureCmd(nv, STATUS_INPUT, Type::Debug);
389             auto status0Vout = pmbusIntf.insertPageNum(STATUS_VOUT, 0);
390             captureCmd(nv, status0Vout, Type::Debug);
391             captureCmd(nv, STATUS_IOUT, Type::Debug);
392             captureCmd(nv, STATUS_MFR, Type::Debug);
393 
394             using metadata = org::open_power::Witherspoon::Fault::
395                 PowerSupplyOutputOvercurrent;
396 
397             report<PowerSupplyOutputOvercurrent>(
398                 metadata::RAW_STATUS(nv.get().c_str()),
399                 metadata::CALLOUT_INVENTORY_PATH(inventoryPath.c_str()));
400 
401             faultFound = true;
402         }
403     }
404 }
405 
406 void PowerSupply::checkOutputOvervoltageFault(const uint16_t statusWord)
407 {
408     using namespace phosphor::pmbus;
409 
410     if (outputOVFault < FAULT_COUNT)
411     {
412         // Check for an output overvoltage fault.
413         if (statusWord & status_word::VOUT_OV_FAULT)
414         {
415             outputOVFault++;
416         }
417         else
418         {
419             if (outputOVFault > 0)
420             {
421                 outputOVFault = 0;
422             }
423         }
424 
425         if (!faultFound && (outputOVFault >= FAULT_COUNT))
426         {
427             util::NamesValues nv;
428             nv.add("STATUS_WORD", statusWord);
429             captureCmd(nv, STATUS_INPUT, Type::Debug);
430             auto status0Vout = pmbusIntf.insertPageNum(STATUS_VOUT, 0);
431             captureCmd(nv, status0Vout, Type::Debug);
432             captureCmd(nv, STATUS_IOUT, Type::Debug);
433             captureCmd(nv, STATUS_MFR, Type::Debug);
434 
435             using metadata = org::open_power::Witherspoon::Fault::
436                 PowerSupplyOutputOvervoltage;
437 
438             report<PowerSupplyOutputOvervoltage>(
439                 metadata::RAW_STATUS(nv.get().c_str()),
440                 metadata::CALLOUT_INVENTORY_PATH(inventoryPath.c_str()));
441 
442             faultFound = true;
443         }
444     }
445 }
446 
447 void PowerSupply::checkFanFault(const uint16_t statusWord)
448 {
449     using namespace phosphor::pmbus;
450 
451     if (fanFault < FAULT_COUNT)
452     {
453         // Check for a fan fault or warning condition
454         if (statusWord & status_word::FAN_FAULT)
455         {
456             fanFault++;
457         }
458         else
459         {
460             if (fanFault > 0)
461             {
462                 fanFault = 0;
463             }
464         }
465 
466         if (!faultFound && (fanFault >= FAULT_COUNT))
467         {
468             util::NamesValues nv;
469             nv.add("STATUS_WORD", statusWord);
470             captureCmd(nv, STATUS_MFR, Type::Debug);
471             captureCmd(nv, STATUS_TEMPERATURE, Type::Debug);
472             captureCmd(nv, STATUS_FANS_1_2, Type::Debug);
473 
474             using metadata =
475                 org::open_power::Witherspoon::Fault::PowerSupplyFanFault;
476 
477             report<PowerSupplyFanFault>(
478                 metadata::RAW_STATUS(nv.get().c_str()),
479                 metadata::CALLOUT_INVENTORY_PATH(inventoryPath.c_str()));
480 
481             faultFound = true;
482         }
483     }
484 }
485 
486 void PowerSupply::checkTemperatureFault(const uint16_t statusWord)
487 {
488     using namespace phosphor::pmbus;
489 
490     // Due to how the PMBus core device driver sends a clear faults command
491     // the bit in STATUS_WORD will likely be cleared when we attempt to examine
492     // it for a Thermal Fault or Warning. So, check the STATUS_WORD and the
493     // STATUS_TEMPERATURE bits. If either indicates a fault, proceed with
494     // logging the over-temperature condition.
495     std::uint8_t statusTemperature = 0;
496     statusTemperature = pmbusIntf.read(STATUS_TEMPERATURE, Type::Debug);
497     if (temperatureFault < FAULT_COUNT)
498     {
499         if ((statusWord & status_word::TEMPERATURE_FAULT_WARN) ||
500             (statusTemperature & status_temperature::OT_FAULT))
501         {
502             temperatureFault++;
503         }
504         else
505         {
506             if (temperatureFault > 0)
507             {
508                 temperatureFault = 0;
509             }
510         }
511 
512         if (!faultFound && (temperatureFault >= FAULT_COUNT))
513         {
514             // The power supply has had an over-temperature condition.
515             // This may not result in a shutdown if experienced for a short
516             // duration.
517             // This should not occur under normal conditions.
518             // The power supply may be faulty, or the paired supply may be
519             // putting out less current.
520             // Capture command responses with potentially relevant information,
521             // and call out the power supply reporting the condition.
522             util::NamesValues nv;
523             nv.add("STATUS_WORD", statusWord);
524             captureCmd(nv, STATUS_MFR, Type::Debug);
525             captureCmd(nv, STATUS_IOUT, Type::Debug);
526             nv.add("STATUS_TEMPERATURE", statusTemperature);
527             captureCmd(nv, STATUS_FANS_1_2, Type::Debug);
528 
529             using metadata = org::open_power::Witherspoon::Fault::
530                 PowerSupplyTemperatureFault;
531 
532             report<PowerSupplyTemperatureFault>(
533                 metadata::RAW_STATUS(nv.get().c_str()),
534                 metadata::CALLOUT_INVENTORY_PATH(inventoryPath.c_str()));
535 
536             faultFound = true;
537         }
538     }
539 }
540 
541 void PowerSupply::clearFaults()
542 {
543     readFail = 0;
544     readFailLogged = false;
545     inputFault = 0;
546     powerOnFault = 0;
547     outputOCFault = 0;
548     outputOVFault = 0;
549     fanFault = 0;
550     temperatureFault = 0;
551     faultFound = false;
552 
553     return;
554 }
555 
556 void PowerSupply::resolveError(const std::string& callout,
557                                const std::string& message)
558 {
559     using EndpointList = std::vector<std::string>;
560 
561     try
562     {
563         auto path = callout + "/fault";
564         // Get the service name from the mapper for the fault callout
565         auto service = util::getService(path, ASSOCIATION_IFACE, bus);
566 
567         // Use getProperty utility function to get log entries (endpoints)
568         EndpointList logEntries;
569         util::getProperty(ASSOCIATION_IFACE, ENDPOINTS_PROP, path, service, bus,
570                           logEntries);
571 
572         // It is possible that all such entries for this callout have since
573         // been deleted.
574         if (logEntries.empty())
575         {
576             return;
577         }
578 
579         auto logEntryService =
580             util::getService(logEntries[0], LOGGING_IFACE, bus);
581         if (logEntryService.empty())
582         {
583             return;
584         }
585 
586         // go through each log entry that matches this callout path
587         std::string logMessage;
588         for (const auto& logEntry : logEntries)
589         {
590             // Check to see if this logEntry has a message that matches.
591             util::getProperty(LOGGING_IFACE, MESSAGE_PROP, logEntry,
592                               logEntryService, bus, logMessage);
593 
594             if (message == logMessage)
595             {
596                 // Log entry matches call out and message, set Resolved to true
597                 bool resolved = true;
598                 util::setProperty(LOGGING_IFACE, RESOLVED_PROP, logEntry,
599                                   logEntryService, bus, resolved);
600             }
601         }
602     }
603     catch (std::exception& e)
604     {
605         log<level::INFO>("Failed to resolve error",
606                          entry("CALLOUT=%s", callout.c_str()),
607                          entry("ERROR=%s", message.c_str()));
608     }
609 }
610 
611 void PowerSupply::updateInventory()
612 {
613     using namespace phosphor::pmbus;
614     using namespace sdbusplus::message;
615 
616     // Build the object map and send it to the inventory
617     using Properties = std::map<std::string, variant<std::string>>;
618     using Interfaces = std::map<std::string, Properties>;
619     using Object = std::map<object_path, Interfaces>;
620     Properties assetProps;
621     Properties versionProps;
622     Interfaces interfaces;
623     Object object;
624 
625     // If any of these accesses fail, the fields will just be
626     // blank in the inventory.  Leave logging ReadFailure errors
627     // to analyze() as it runs continuously and will most
628     // likely hit and threshold them first anyway.  The
629     // readString() function will do the tracing of the failing
630     // path so this code doesn't need to.
631     for (const auto& fru : fruJson.at("fruConfigs"))
632     {
633         if (fru.at("interface") == ASSET_IFACE)
634         {
635             try
636             {
637                 assetProps.emplace(
638                     fru.at("propertyName"),
639                     present ? pmbusIntf.readString(fru.at("fileName"),
640                                                    inventoryPMBusAccessType)
641                             : "");
642             }
643             catch (ReadFailure& e)
644             {
645             }
646         }
647         else if (fru.at("interface") == VERSION_IFACE)
648         {
649             try
650             {
651                 versionProps.emplace(
652                     fru.at("propertyName"),
653                     present ? pmbusIntf.readString(fru.at("fileName"),
654                                                    inventoryPMBusAccessType)
655                             : "");
656             }
657             catch (const std::exception& e)
658             {
659             }
660         }
661     }
662 
663     interfaces.emplace(ASSET_IFACE, std::move(assetProps));
664     interfaces.emplace(VERSION_IFACE, std::move(versionProps));
665 
666     // For Notify(), just send the relative path of the inventory
667     // object so remove the INVENTORY_OBJ_PATH prefix
668     auto path = inventoryPath.substr(strlen(INVENTORY_OBJ_PATH));
669 
670     object.emplace(path, std::move(interfaces));
671 
672     try
673     {
674         auto service =
675             util::getService(INVENTORY_OBJ_PATH, INVENTORY_MGR_IFACE, bus);
676 
677         if (service.empty())
678         {
679             log<level::ERR>("Unable to get inventory manager service");
680             return;
681         }
682 
683         auto method = bus.new_method_call(service.c_str(), INVENTORY_OBJ_PATH,
684                                           INVENTORY_MGR_IFACE, "Notify");
685 
686         method.append(std::move(object));
687 
688         auto reply = bus.call(method);
689 
690         // TODO: openbmc/openbmc#2756
691         // Calling Notify() with an enumerated property crashes inventory
692         // manager, so let it default to Unknown and now set it to the
693         // right value.
694         auto purpose =
695             version::convertForMessage(version::Version::VersionPurpose::Other);
696 
697         util::setProperty(VERSION_IFACE, VERSION_PURPOSE_PROP, inventoryPath,
698                           service, bus, purpose);
699     }
700     catch (std::exception& e)
701     {
702         log<level::ERR>(e.what(), entry("PATH=%s", inventoryPath.c_str()));
703     }
704 }
705 
706 void PowerSupply::syncHistory()
707 {
708     using namespace phosphor::gpio;
709 
710     if (syncGPIODevPath.empty())
711     {
712         // Sync not implemented
713         return;
714     }
715 
716     GPIO gpio{syncGPIODevPath, static_cast<gpioNum_t>(syncGPIONumber),
717               Direction::output};
718 
719     try
720     {
721         gpio.set(Value::low);
722 
723         std::this_thread::sleep_for(std::chrono::milliseconds{5});
724 
725         gpio.set(Value::high);
726 
727         recordManager->clear();
728     }
729     catch (std::exception& e)
730     {
731         // Do nothing.  There would already be a journal entry.
732     }
733 }
734 
735 void PowerSupply::enableHistory(const std::string& objectPath,
736                                 size_t numRecords,
737                                 const std::string& syncGPIOPath,
738                                 size_t syncGPIONum)
739 {
740     historyObjectPath = objectPath;
741     syncGPIODevPath = syncGPIOPath;
742     syncGPIONumber = syncGPIONum;
743 
744     recordManager = std::make_unique<history::RecordManager>(numRecords);
745 
746     auto avgPath = historyObjectPath + '/' + history::Average::name;
747     auto maxPath = historyObjectPath + '/' + history::Maximum::name;
748 
749     average = std::make_unique<history::Average>(bus, avgPath);
750 
751     maximum = std::make_unique<history::Maximum>(bus, maxPath);
752 }
753 
754 void PowerSupply::updateHistory()
755 {
756     if (!recordManager)
757     {
758         // Not enabled
759         return;
760     }
761 
762     // Read just the most recent average/max record
763     auto data =
764         pmbusIntf.readBinary(INPUT_HISTORY, pmbus::Type::HwmonDeviceDebug,
765                              history::RecordManager::RAW_RECORD_SIZE);
766 
767     // Update D-Bus only if something changed (a new record ID, or cleared out)
768     auto changed = recordManager->add(data);
769     if (changed)
770     {
771         average->values(std::move(recordManager->getAverageRecords()));
772         maximum->values(std::move(recordManager->getMaximumRecords()));
773     }
774 }
775 
776 } // namespace psu
777 } // namespace power
778 } // namespace phosphor
779