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