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