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