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