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