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