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 <org/open_power/Witherspoon/Fault/error.hpp>
25 #include <phosphor-logging/elog.hpp>
26 #include <phosphor-logging/log.hpp>
27 #include <xyz/openbmc_project/Common/Device/error.hpp>
28 #include <xyz/openbmc_project/Software/Version/server.hpp>
29 
30 #include <functional>
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 SN_PROP = "SerialNumber";
57 constexpr auto PN_PROP = "PartNumber";
58 constexpr auto MODEL_PROP = "Model";
59 constexpr auto VERSION_PROP = "Version";
60 constexpr auto VERSION_PURPOSE_PROP = "Purpose";
61 
62 constexpr auto INVENTORY_OBJ_PATH = "/xyz/openbmc_project/inventory";
63 constexpr auto POWER_OBJ_PATH = "/org/openbmc/control/power0";
64 
65 constexpr auto SERIAL_NUMBER = "serial_number";
66 constexpr auto PART_NUMBER = "part_number";
67 constexpr auto FW_VERSION = "fw_version";
68 constexpr auto CCIN = "ccin";
69 constexpr auto INPUT_HISTORY = "input_history";
70 
71 PowerSupply::PowerSupply(const std::string& name, size_t inst,
72                          const std::string& objpath, const std::string& invpath,
73                          sdbusplus::bus_t& bus, const sdeventplus::Event& e,
74                          std::chrono::seconds& t, std::chrono::seconds& p) :
75     Device(name, inst),
76     monitorPath(objpath), pmbusIntf(objpath),
77     inventoryPath(INVENTORY_OBJ_PATH + invpath), bus(bus), presentInterval(p),
78     presentTimer(e, std::bind([this]() {
79                      // The hwmon path may have changed.
80                      pmbusIntf.findHwmonDir();
81                      this->present = true;
82 
83                      // Sync the INPUT_HISTORY data for all PSs
84                      syncHistory();
85 
86                      // Update the inventory for the new device
87                      updateInventory();
88                  })),
89     powerOnInterval(t),
90     powerOnTimer(e, std::bind([this]() { this->powerOn = true; }))
91 {
92     using namespace sdbusplus::bus;
93     using namespace witherspoon::pmbus;
94     std::uint16_t statusWord = 0;
95     try
96     {
97         // Read the 2 byte STATUS_WORD value to check for faults.
98         statusWord = pmbusIntf.read(STATUS_WORD, Type::Debug);
99         if (!((statusWord & status_word::INPUT_FAULT_WARN) ||
100               (statusWord & status_word::VIN_UV_FAULT)))
101         {
102             resolveError(inventoryPath,
103                          std::string(PowerSupplyInputFault::errName));
104         }
105     }
106     catch (ReadFailure& e)
107     {
108         log<level::INFO>("Unable to read the 2 byte STATUS_WORD value to check "
109                          "for power-supply input faults.");
110     }
111     presentMatch = std::make_unique<match_t>(
112         bus, match::rules::propertiesChanged(inventoryPath, INVENTORY_IFACE),
113         [this](auto& msg) { this->inventoryChanged(msg); });
114     // Get initial presence state.
115     updatePresence();
116 
117     // Write the SN, PN, etc to the inventory
118     updateInventory();
119 
120     // Subscribe to power state changes
121     powerOnMatch = std::make_unique<match_t>(
122         bus, match::rules::propertiesChanged(POWER_OBJ_PATH, POWER_IFACE),
123         [this](auto& msg) { this->powerStateChanged(msg); });
124     // Get initial power state.
125     updatePowerState();
126 }
127 
128 void PowerSupply::captureCmd(util::NamesValues& nv, const std::string& cmd,
129                              witherspoon::pmbus::Type type)
130 {
131     if (pmbusIntf.exists(cmd, type))
132     {
133         try
134         {
135             auto val = pmbusIntf.read(cmd, type);
136             nv.add(cmd, val);
137         }
138         catch (std::exception& e)
139         {
140             log<level::INFO>("Unable to capture metadata",
141                              entry("CMD=%s", cmd.c_str()));
142         }
143     }
144 }
145 
146 void PowerSupply::analyze()
147 {
148     using namespace witherspoon::pmbus;
149 
150     try
151     {
152         if (present)
153         {
154             std::uint16_t statusWord = 0;
155 
156             // Read the 2 byte STATUS_WORD value to check for faults.
157             statusWord = pmbusIntf.read(STATUS_WORD, Type::Debug);
158             readFail = 0;
159 
160             checkInputFault(statusWord);
161 
162             if (powerOn && (inputFault == 0) && !faultFound)
163             {
164                 checkFanFault(statusWord);
165                 checkTemperatureFault(statusWord);
166                 checkOutputOvervoltageFault(statusWord);
167                 checkCurrentOutOverCurrentFault(statusWord);
168                 checkPGOrUnitOffFault(statusWord);
169             }
170 
171             updateHistory();
172         }
173     }
174     catch (ReadFailure& e)
175     {
176         if (readFail < FAULT_COUNT)
177         {
178             readFail++;
179         }
180 
181         if (!readFailLogged && readFail >= FAULT_COUNT)
182         {
183             commit<ReadFailure>();
184             readFailLogged = true;
185         }
186     }
187 
188     return;
189 }
190 
191 void PowerSupply::inventoryChanged(sdbusplus::message_t& msg)
192 {
193     std::string msgSensor;
194     std::map<std::string, std::variant<uint32_t, bool>> msgData;
195     msg.read(msgSensor, msgData);
196 
197     // Check if it was the Present property that changed.
198     auto valPropMap = msgData.find(PRESENT_PROP);
199     if (valPropMap != msgData.end())
200     {
201         if (std::get<bool>(valPropMap->second))
202         {
203             clearFaults();
204             presentTimer.restartOnce(presentInterval);
205         }
206         else
207         {
208             present = false;
209             presentTimer.setEnabled(false);
210 
211             // Clear out the now outdated inventory properties
212             updateInventory();
213         }
214     }
215 
216     return;
217 }
218 
219 void PowerSupply::updatePresence()
220 {
221     // Use getProperty utility function to get presence status.
222     std::string service = "xyz.openbmc_project.Inventory.Manager";
223     util::getProperty(INVENTORY_IFACE, PRESENT_PROP, inventoryPath, service,
224                       bus, this->present);
225 }
226 
227 void PowerSupply::powerStateChanged(sdbusplus::message_t& msg)
228 {
229     int32_t state = 0;
230     std::string msgSensor;
231     std::map<std::string, std::variant<int32_t>> msgData;
232     msg.read(msgSensor, msgData);
233 
234     // Check if it was the Present property that changed.
235     auto valPropMap = msgData.find("state");
236     if (valPropMap != msgData.end())
237     {
238         state = std::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         try
671         {
672             pn = pmbusIntf.readString(PART_NUMBER, Type::HwmonDeviceDebug);
673         }
674         catch (ReadFailure& e)
675         {}
676 
677         try
678         {
679             ccin = pmbusIntf.readString(CCIN, Type::HwmonDeviceDebug);
680         }
681         catch (ReadFailure& e)
682         {}
683 
684         try
685         {
686             version = pmbusIntf.readString(FW_VERSION, Type::HwmonDeviceDebug);
687         }
688         catch (ReadFailure& e)
689         {}
690     }
691 
692     // Build the object map and send it to the inventory
693     using Properties = std::map<std::string, std::variant<std::string>>;
694     using Interfaces = std::map<std::string, Properties>;
695     using Object = std::map<object_path, Interfaces>;
696     Properties assetProps;
697     Properties versionProps;
698     Interfaces interfaces;
699     Object object;
700 
701     assetProps.emplace(SN_PROP, sn);
702     assetProps.emplace(PN_PROP, pn);
703     assetProps.emplace(MODEL_PROP, ccin);
704     interfaces.emplace(ASSET_IFACE, std::move(assetProps));
705 
706     versionProps.emplace(VERSION_PROP, version);
707     interfaces.emplace(VERSION_IFACE, std::move(versionProps));
708 
709     // For Notify(), just send the relative path of the inventory
710     // object so remove the INVENTORY_OBJ_PATH prefix
711     auto path = inventoryPath.substr(strlen(INVENTORY_OBJ_PATH));
712 
713     object.emplace(path, std::move(interfaces));
714 
715     try
716     {
717         auto service =
718             util::getService(INVENTORY_OBJ_PATH, INVENTORY_MGR_IFACE, bus);
719 
720         if (service.empty())
721         {
722             log<level::ERR>("Unable to get inventory manager service");
723             return;
724         }
725 
726         auto method = bus.new_method_call(service.c_str(), INVENTORY_OBJ_PATH,
727                                           INVENTORY_MGR_IFACE, "Notify");
728 
729         method.append(std::move(object));
730 
731         auto reply = bus.call(method);
732 
733         // TODO: openbmc/openbmc#2756
734         // Calling Notify() with an enumerated property crashes inventory
735         // manager, so let it default to Unknown and now set it to the
736         // right value.
737         auto purpose =
738             version::convertForMessage(version::Version::VersionPurpose::Other);
739 
740         util::setProperty(VERSION_IFACE, VERSION_PURPOSE_PROP, inventoryPath,
741                           service, bus, purpose);
742     }
743     catch (std::exception& e)
744     {
745         log<level::ERR>(e.what(), entry("PATH=%s", inventoryPath.c_str()));
746     }
747 }
748 
749 void PowerSupply::syncHistory()
750 {
751     using namespace witherspoon::gpio;
752 
753     if (syncGPIODevPath.empty())
754     {
755         // Sync not implemented
756         return;
757     }
758 
759     GPIO gpio{syncGPIODevPath, static_cast<gpioNum_t>(syncGPIONumber),
760               Direction::output};
761 
762     try
763     {
764         gpio.set(Value::low);
765 
766         std::this_thread::sleep_for(std::chrono::milliseconds{5});
767 
768         gpio.set(Value::high);
769 
770         recordManager->clear();
771     }
772     catch (std::exception& e)
773     {
774         // Do nothing.  There would already be a journal entry.
775     }
776 }
777 
778 void PowerSupply::enableHistory(const std::string& objectPath,
779                                 size_t numRecords,
780                                 const std::string& syncGPIOPath,
781                                 size_t syncGPIONum)
782 {
783     historyObjectPath = objectPath;
784     syncGPIODevPath = syncGPIOPath;
785     syncGPIONumber = syncGPIONum;
786 
787     recordManager = std::make_unique<history::RecordManager>(numRecords);
788 
789     auto avgPath = historyObjectPath + '/' + history::Average::name;
790     auto maxPath = historyObjectPath + '/' + history::Maximum::name;
791 
792     average = std::make_unique<history::Average>(bus, avgPath);
793 
794     maximum = std::make_unique<history::Maximum>(bus, maxPath);
795 }
796 
797 void PowerSupply::updateHistory()
798 {
799     if (!recordManager)
800     {
801         // Not enabled
802         return;
803     }
804 
805     // Read just the most recent average/max record
806     auto data =
807         pmbusIntf.readBinary(INPUT_HISTORY, pmbus::Type::HwmonDeviceDebug,
808                              history::RecordManager::RAW_RECORD_SIZE);
809 
810     // Update D-Bus only if something changed (a new record ID, or cleared out)
811     auto changed = recordManager->add(data);
812     if (changed)
813     {
814         average->values(std::move(recordManager->getAverageRecords()));
815         maximum->values(std::move(recordManager->getMaximumRecords()));
816     }
817 }
818 
819 } // namespace psu
820 } // namespace power
821 } // namespace witherspoon
822