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 <phosphor-logging/log.hpp>
17 #include <phosphor-logging/elog.hpp>
18 #include <xyz/openbmc_project/Sensor/Device/error.hpp>
19 #include <xyz/openbmc_project/Control/Device/error.hpp>
20 #include <xyz/openbmc_project/Power/Fault/error.hpp>
21 #include "elog-errors.hpp"
22 #include "names_values.hpp"
23 #include "power_supply.hpp"
24 #include "pmbus.hpp"
25 #include "utility.hpp"
26 
27 using namespace phosphor::logging;
28 using namespace sdbusplus::xyz::openbmc_project::Control::Device::Error;
29 using namespace sdbusplus::xyz::openbmc_project::Sensor::Device::Error;
30 using namespace sdbusplus::xyz::openbmc_project::Power::Fault::Error;
31 
32 namespace witherspoon
33 {
34 namespace power
35 {
36 namespace psu
37 {
38 
39 constexpr auto INVENTORY_OBJ_PATH = "/xyz/openbmc_project/inventory";
40 constexpr auto INVENTORY_INTERFACE = "xyz.openbmc_project.Inventory.Item";
41 constexpr auto PRESENT_PROP = "Present";
42 constexpr auto POWER_OBJ_PATH = "/org/openbmc/control/power0";
43 constexpr auto POWER_INTERFACE = "org.openbmc.control.Power";
44 
45 PowerSupply::PowerSupply(const std::string& name, size_t inst,
46                          const std::string& objpath,
47                          const std::string& invpath,
48                          sdbusplus::bus::bus& bus,
49                          event::Event& e,
50                          std::chrono::seconds& t)
51     : Device(name, inst), monitorPath(objpath), pmbusIntf(objpath),
52       inventoryPath(invpath), bus(bus), event(e), powerOnInterval(t),
53       powerOnTimer(e, [this]()
54                    {
55                        this->powerOn = true;
56                    })
57 {
58     using namespace sdbusplus::bus;
59     auto present_obj_path = INVENTORY_OBJ_PATH + inventoryPath;
60     presentMatch = std::make_unique<match_t>(bus,
61                                              match::rules::propertiesChanged(
62                                                      present_obj_path,
63                                                      INVENTORY_INTERFACE),
64                                              [this](auto& msg)
65                                              {
66                                                  this->inventoryChanged(msg);
67                                              });
68     // Get initial presence state.
69     updatePresence();
70 
71     // Subscribe to power state changes
72     powerOnMatch = std::make_unique<match_t>(bus,
73                                              match::rules::propertiesChanged(
74                                                      POWER_OBJ_PATH,
75                                                      POWER_INTERFACE),
76                                              [this](auto& msg)
77                                              {
78                                                  this->powerStateChanged(msg);
79                                              });
80     // Get initial power state.
81     updatePowerState();
82 }
83 
84 
85 void PowerSupply::analyze()
86 {
87     using namespace witherspoon::pmbus;
88 
89     try
90     {
91         if (present)
92         {
93             std::uint16_t statusWord = 0;
94 
95             // Read the 2 byte STATUS_WORD value to check for faults.
96             statusWord = pmbusIntf.read(STATUS_WORD, Type::Debug);
97 
98             //TODO: 3 consecutive reads should be performed.
99             // If 3 consecutive reads are seen, log the fault.
100             // Driver gives cached value, read once a second.
101             // increment for fault on, decrement for fault off, to deglitch.
102             // If count reaches 3, we have fault. If count reaches 0, fault is
103             // cleared.
104 
105             checkInputFault(statusWord);
106 
107             if (powerOn)
108             {
109                 checkPGOrUnitOffFault(statusWord);
110                 checkCurrentOutOverCurrentFault(statusWord);
111                 checkOutputOvervoltageFault(statusWord);
112                 checkFanFault(statusWord);
113                 checkTemperatureFault(statusWord);
114             }
115         }
116     }
117     catch (ReadFailure& e)
118     {
119         if (!readFailLogged)
120         {
121             commit<ReadFailure>();
122             readFailLogged = true;
123         }
124     }
125 
126     return;
127 }
128 
129 void PowerSupply::inventoryChanged(sdbusplus::message::message& msg)
130 {
131     std::string msgSensor;
132     std::map<std::string, sdbusplus::message::variant<uint32_t, bool>> msgData;
133     msg.read(msgSensor, msgData);
134 
135     // Check if it was the Present property that changed.
136     auto valPropMap = msgData.find(PRESENT_PROP);
137     if (valPropMap != msgData.end())
138     {
139         present = sdbusplus::message::variant_ns::get<bool>(valPropMap->second);
140 
141         if (present)
142         {
143             readFailLogged = false;
144             vinUVFault = false;
145             inputFault = false;
146             outputOCFault = false;
147             outputOVFault = false;
148             fanFault = false;
149             temperatureFault = false;
150         }
151     }
152 
153     return;
154 }
155 
156 void PowerSupply::updatePresence()
157 {
158     // Use getProperty utility function to get presence status.
159     std::string path = INVENTORY_OBJ_PATH + inventoryPath;
160     std::string service = "xyz.openbmc_project.Inventory.Manager";
161 
162     try
163     {
164         util::getProperty(INVENTORY_INTERFACE, PRESENT_PROP, path,
165                           service, bus, this->present);
166     }
167     catch (std::exception& e)
168     {
169         // If we happen to be trying to update presence just as it is being
170         // updated, we may encounter a runtime_error. Just catch that for
171         // now, let the inventoryChanged signal handler update presence later.
172         present = false;
173     }
174 
175 }
176 
177 void PowerSupply::powerStateChanged(sdbusplus::message::message& msg)
178 {
179     int32_t state = 0;
180     std::string msgSensor;
181     std::map<std::string, sdbusplus::message::variant<int32_t, int32_t>>
182             msgData;
183     msg.read(msgSensor, msgData);
184 
185     // Check if it was the Present property that changed.
186     auto valPropMap = msgData.find("state");
187     if (valPropMap != msgData.end())
188     {
189         state = sdbusplus::message::variant_ns::get<int32_t>(valPropMap->second);
190 
191         // Power is on when state=1. Set the fault logged variables to false
192         // and start the power on timer when the state changes to 1.
193         if (state)
194         {
195             readFailLogged = false;
196             vinUVFault = false;
197             inputFault = false;
198             powerOnFault = false;
199             outputOCFault = false;
200             outputOVFault = false;
201             fanFault = false;
202             temperatureFault = false;
203             powerOnTimer.start(powerOnInterval, Timer::TimerType::oneshot);
204         }
205         else
206         {
207             powerOnTimer.stop();
208             powerOn = false;
209         }
210     }
211 
212 }
213 
214 void PowerSupply::updatePowerState()
215 {
216     // When state = 1, system is powered on
217     int32_t state = 0;
218 
219     try
220     {
221         auto service = util::getService(POWER_OBJ_PATH,
222                                         POWER_INTERFACE,
223                                         bus);
224 
225         // Use getProperty utility function to get power state.
226         util::getProperty<int32_t>(POWER_INTERFACE,
227                                    "state",
228                                    POWER_OBJ_PATH,
229                                    service,
230                                    bus,
231                                    state);
232 
233         if (state)
234         {
235             powerOn = true;
236         }
237         else
238         {
239             powerOn = false;
240         }
241     }
242     catch (std::exception& e)
243     {
244         log<level::INFO>("Failed to get power state. Assuming it is off.");
245         powerOn = false;
246     }
247 
248 }
249 
250 void PowerSupply::checkInputFault(const uint16_t statusWord)
251 {
252     using namespace witherspoon::pmbus;
253 
254     std::uint8_t  statusInput = 0;
255 
256     if ((statusWord & status_word::VIN_UV_FAULT) && !vinUVFault)
257     {
258         vinUVFault = true;
259 
260         util::NamesValues nv;
261         nv.add("STATUS_WORD", statusWord);
262 
263         using metadata = xyz::openbmc_project::Power::Fault::
264                 PowerSupplyUnderVoltageFault;
265 
266         report<PowerSupplyUnderVoltageFault>(metadata::RAW_STATUS(
267                                                      nv.get().c_str()));
268     }
269     else
270     {
271         if (vinUVFault)
272         {
273             vinUVFault = false;
274             log<level::INFO>("VIN_UV_FAULT cleared",
275                              entry("POWERSUPPLY=%s",
276                                      inventoryPath.c_str()));
277         }
278     }
279 
280     if ((statusWord & status_word::INPUT_FAULT_WARN) && !inputFault)
281     {
282         inputFault = true;
283 
284         statusInput = pmbusIntf.read(STATUS_INPUT, Type::Debug);
285 
286         util::NamesValues nv;
287         nv.add("STATUS_WORD", statusWord);
288         nv.add("STATUS_INPUT", statusInput);
289 
290         using metadata = xyz::openbmc_project::Power::Fault::
291                 PowerSupplyInputFault;
292 
293         report<PowerSupplyInputFault>(
294                 metadata::RAW_STATUS(nv.get().c_str()));
295     }
296     else
297     {
298         if ((inputFault) &&
299             !(statusWord & status_word::INPUT_FAULT_WARN))
300         {
301             inputFault = false;
302             statusInput = pmbusIntf.read(STATUS_INPUT, Type::Debug);
303 
304             log<level::INFO>("INPUT_FAULT_WARN cleared",
305                              entry("POWERSUPPLY=%s", inventoryPath.c_str()),
306                              entry("STATUS_WORD=0x%04X", statusWord),
307                              entry("STATUS_INPUT=0x%02X", statusInput));
308         }
309     }
310 }
311 
312 void PowerSupply::checkPGOrUnitOffFault(const uint16_t statusWord)
313 {
314     using namespace witherspoon::pmbus;
315 
316     std::uint8_t  statusInput = 0;
317     std::uint8_t  statusVout = 0;
318     std::uint8_t  statusIout = 0;
319     std::uint8_t  statusMFR  = 0;
320 
321     // Check PG# and UNIT_IS_OFF
322     if (((statusWord & status_word::POWER_GOOD_NEGATED) ||
323          (statusWord & status_word::UNIT_IS_OFF)) &&
324         !powerOnFault)
325     {
326         statusInput = pmbusIntf.read(STATUS_INPUT, Type::Debug);
327         auto status0Vout = pmbusIntf.insertPageNum(STATUS_VOUT, 0);
328         statusVout = pmbusIntf.read(status0Vout, Type::Debug);
329         statusIout = pmbusIntf.read(STATUS_IOUT, Type::Debug);
330         statusMFR = pmbusIntf.read(STATUS_MFR, Type::Debug);
331 
332         util::NamesValues nv;
333         nv.add("STATUS_WORD", statusWord);
334         nv.add("STATUS_INPUT", statusInput);
335         nv.add("STATUS_VOUT", statusVout);
336         nv.add("STATUS_IOUT", statusIout);
337         nv.add("MFR_SPECIFIC", statusMFR);
338 
339         using metadata = xyz::openbmc_project::Power::Fault::
340                 PowerSupplyShouldBeOn;
341 
342         // A power supply is OFF (or pgood low) but should be on.
343         report<PowerSupplyShouldBeOn>(metadata::RAW_STATUS(nv.get().c_str()),
344                                       metadata::CALLOUT_INVENTORY_PATH(
345                                               inventoryPath.c_str()));
346 
347         powerOnFault = true;
348     }
349 
350 }
351 
352 void PowerSupply::checkCurrentOutOverCurrentFault(const uint16_t statusWord)
353 {
354     using namespace witherspoon::pmbus;
355 
356     std::uint8_t  statusInput = 0;
357     std::uint8_t  statusVout = 0;
358     std::uint8_t  statusIout = 0;
359     std::uint8_t  statusMFR  = 0;
360 
361     // Check for an output overcurrent fault.
362     if ((statusWord & status_word::IOUT_OC_FAULT) &&
363         !outputOCFault)
364     {
365         statusInput = pmbusIntf.read(STATUS_INPUT, Type::Debug);
366         auto status0Vout = pmbusIntf.insertPageNum(STATUS_VOUT, 0);
367         statusVout = pmbusIntf.read(status0Vout, Type::Debug);
368         statusIout = pmbusIntf.read(STATUS_IOUT, Type::Debug);
369         statusMFR = pmbusIntf.read(STATUS_MFR, Type::Debug);
370 
371         util::NamesValues nv;
372         nv.add("STATUS_WORD", statusWord);
373         nv.add("STATUS_INPUT", statusInput);
374         nv.add("STATUS_VOUT", statusVout);
375         nv.add("STATUS_IOUT", statusIout);
376         nv.add("MFR_SPECIFIC", statusMFR);
377 
378         using metadata = xyz::openbmc_project::Power::Fault::
379                 PowerSupplyOutputOvercurrent;
380 
381         report<PowerSupplyOutputOvercurrent>(metadata::RAW_STATUS(
382                                                      nv.get().c_str()),
383                                              metadata::CALLOUT_INVENTORY_PATH(
384                                                      inventoryPath.c_str()));
385 
386         outputOCFault = true;
387     }
388 }
389 
390 void PowerSupply::checkOutputOvervoltageFault(const uint16_t statusWord)
391 {
392     using namespace witherspoon::pmbus;
393 
394     std::uint8_t  statusInput = 0;
395     std::uint8_t  statusVout = 0;
396     std::uint8_t  statusIout = 0;
397     std::uint8_t  statusMFR  = 0;
398 
399     // Check for an output overvoltage fault.
400     if ((statusWord & status_word::VOUT_OV_FAULT) &&
401         !outputOVFault)
402     {
403         statusInput = pmbusIntf.read(STATUS_INPUT, Type::Debug);
404         auto status0Vout = pmbusIntf.insertPageNum(STATUS_VOUT, 0);
405         statusVout = pmbusIntf.read(status0Vout, Type::Debug);
406         statusIout = pmbusIntf.read(STATUS_IOUT, Type::Debug);
407         statusMFR = pmbusIntf.read(STATUS_MFR, Type::Debug);
408 
409         util::NamesValues nv;
410         nv.add("STATUS_WORD", statusWord);
411         nv.add("STATUS_INPUT", statusInput);
412         nv.add("STATUS_VOUT", statusVout);
413         nv.add("STATUS_IOUT", statusIout);
414         nv.add("MFR_SPECIFIC", statusMFR);
415 
416         using metadata = xyz::openbmc_project::Power::Fault::
417                 PowerSupplyOutputOvervoltage;
418 
419         report<PowerSupplyOutputOvervoltage>(metadata::RAW_STATUS(
420                                                      nv.get().c_str()),
421                                              metadata::CALLOUT_INVENTORY_PATH(
422                                                      inventoryPath.c_str()));
423 
424         outputOVFault = true;
425     }
426 }
427 
428 void PowerSupply::checkFanFault(const uint16_t statusWord)
429 {
430     using namespace witherspoon::pmbus;
431 
432     std::uint8_t statusMFR  = 0;
433     std::uint8_t statusTemperature = 0;
434     std::uint8_t statusFans12 = 0;
435 
436     // Check for a fan fault or warning condition
437     if ((statusWord & status_word::FAN_FAULT) &&
438         !fanFault)
439     {
440         statusMFR = pmbusIntf.read(STATUS_MFR, Type::Debug);
441         statusTemperature = pmbusIntf.read(STATUS_TEMPERATURE, Type::Debug);
442         statusFans12 = pmbusIntf.read(STATUS_FANS_1_2, Type::Debug);
443 
444         util::NamesValues nv;
445         nv.add("STATUS_WORD", statusWord);
446         nv.add("MFR_SPECIFIC", statusMFR);
447         nv.add("STATUS_TEMPERATURE", statusTemperature);
448         nv.add("STATUS_FANS_1_2", statusFans12);
449 
450         using metadata = xyz::openbmc_project::Power::Fault::
451                 PowerSupplyFanFault;
452 
453         report<PowerSupplyFanFault>(
454                 metadata::RAW_STATUS(nv.get().c_str()),
455                 metadata::CALLOUT_INVENTORY_PATH(inventoryPath.c_str()));
456 
457         fanFault = true;
458     }
459 }
460 
461 void PowerSupply::checkTemperatureFault(const uint16_t statusWord)
462 {
463     using namespace witherspoon::pmbus;
464 
465     // Due to how the PMBus core device driver sends a clear faults command
466     // the bit in STATUS_WORD will likely be cleared when we attempt to examine
467     // it for a Thermal Fault or Warning. So, check the STATUS_WORD and the
468     // STATUS_TEMPERATURE bits. If either indicates a fault, proceed with
469     // logging the over-temperature condition.
470     std::uint8_t statusTemperature = 0;
471     statusTemperature = pmbusIntf.read(STATUS_TEMPERATURE, Type::Debug);
472     if (((statusWord & status_word::TEMPERATURE_FAULT_WARN) ||
473          (statusTemperature & status_temperature::OT_FAULT)) &&
474         !temperatureFault)
475     {
476         // The power supply has had an over-temperature condition.
477         // This may not result in a shutdown if experienced for a short
478         // duration.
479         // This should not occur under normal conditions.
480         // The power supply may be faulty, or the paired supply may be putting
481         // out less current.
482         // Capture command responses with potentially relevant information,
483         // and call out the power supply reporting the condition.
484         std::uint8_t statusMFR = 0;
485         std::uint8_t statusIout = 0;
486         std::uint8_t statusFans12 = 0;
487 
488         statusMFR = pmbusIntf.read(STATUS_MFR, Type::Debug);
489         statusIout = pmbusIntf.read(STATUS_IOUT, Type::Debug);
490         statusFans12 = pmbusIntf.read(STATUS_FANS_1_2, Type::Debug);
491 
492         util::NamesValues nv;
493         nv.add("STATUS_WORD", statusWord);
494         nv.add("MFR_SPECIFIC", statusMFR);
495         nv.add("STATUS_IOUT", statusIout);
496         nv.add("STATUS_TEMPERATURE", statusTemperature);
497         nv.add("STATUS_FANS_1_2", statusFans12);
498 
499         using metadata = xyz::openbmc_project::Power::Fault::
500                 PowerSupplyTemperatureFault;
501 
502         report<PowerSupplyTemperatureFault>(
503                 metadata::RAW_STATUS(nv.get().c_str()),
504                 metadata::CALLOUT_INVENTORY_PATH(inventoryPath.c_str()));
505 
506         temperatureFault = true;
507     }
508 }
509 
510 void PowerSupply::clearFaults()
511 {
512     //TODO - Clear faults at pre-poweron. openbmc/openbmc#1736
513     return;
514 }
515 
516 }
517 }
518 }
519