1 /**
2  * Copyright © 2022 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 
17 #include "ucd90x_monitor.hpp"
18 
19 #include "types.hpp"
20 #include "utility.hpp"
21 
22 #include <fmt/format.h>
23 #include <fmt/ranges.h>
24 
25 #include <gpiod.hpp>
26 #include <nlohmann/json.hpp>
27 #include <phosphor-logging/log.hpp>
28 
29 #include <algorithm>
30 #include <chrono>
31 #include <exception>
32 #include <fstream>
33 
34 namespace phosphor::power::sequencer
35 {
36 
37 using json = nlohmann::json;
38 using namespace pmbus;
39 using namespace phosphor::logging;
40 
41 const std::string compatibleInterface =
42     "xyz.openbmc_project.Configuration.IBMCompatibleSystem";
43 const std::string compatibleNamesProperty = "Names";
44 
UCD90xMonitor(sdbusplus::bus_t & bus,std::uint8_t i2cBus,std::uint16_t i2cAddress,const std::string & deviceName,size_t numberPages)45 UCD90xMonitor::UCD90xMonitor(sdbusplus::bus_t& bus, std::uint8_t i2cBus,
46                              std::uint16_t i2cAddress,
47                              const std::string& deviceName,
48                              size_t numberPages) :
49     PowerSequencerMonitor(bus),
50     deviceName{deviceName},
51     match{bus,
52           sdbusplus::bus::match::rules::interfacesAdded() +
53               sdbusplus::bus::match::rules::sender(
54                   "xyz.openbmc_project.EntityManager"),
55           std::bind(&UCD90xMonitor::interfacesAddedHandler, this,
56                     std::placeholders::_1)},
57     numberPages{numberPages},
58     pmbusInterface{
59         fmt::format("/sys/bus/i2c/devices/{}-{:04x}", i2cBus, i2cAddress)
60             .c_str(),
61         "ucd9000", 0}
62 {
63     log<level::DEBUG>(
64         fmt::format("Device path: {}", pmbusInterface.path().string()).c_str());
65     log<level::DEBUG>(fmt::format("Hwmon path: {}",
66                                   pmbusInterface.getPath(Type::Hwmon).string())
67                           .c_str());
68     log<level::DEBUG>(fmt::format("Debug path: {}",
69                                   pmbusInterface.getPath(Type::Debug).string())
70                           .c_str());
71     log<level::DEBUG>(
72         fmt::format("Device debug path: {}",
73                     pmbusInterface.getPath(Type::DeviceDebug).string())
74             .c_str());
75     log<level::DEBUG>(
76         fmt::format("Hwmon device debug path: {}",
77                     pmbusInterface.getPath(Type::HwmonDeviceDebug).string())
78             .c_str());
79 
80     // Use the compatible system types information, if already available, to
81     // load the configuration file
82     findCompatibleSystemTypes();
83 }
84 
findCompatibleSystemTypes()85 void UCD90xMonitor::findCompatibleSystemTypes()
86 {
87     try
88     {
89         auto subTree = util::getSubTree(bus, "/xyz/openbmc_project/inventory",
90                                         compatibleInterface, 0);
91 
92         auto objectIt = subTree.cbegin();
93         if (objectIt != subTree.cend())
94         {
95             const auto& objPath = objectIt->first;
96 
97             // Get the first service name
98             auto serviceIt = objectIt->second.cbegin();
99             if (serviceIt != objectIt->second.cend())
100             {
101                 std::string service = serviceIt->first;
102                 if (!service.empty())
103                 {
104                     std::vector<std::string> compatibleSystemTypes;
105 
106                     // Get compatible system types property value
107                     util::getProperty(compatibleInterface,
108                                       compatibleNamesProperty, objPath, service,
109                                       bus, compatibleSystemTypes);
110 
111                     log<level::DEBUG>(
112                         fmt::format("Found compatible systems: {}",
113                                     compatibleSystemTypes)
114                             .c_str());
115                     // Use compatible systems information to find config file
116                     findConfigFile(compatibleSystemTypes);
117                 }
118             }
119         }
120     }
121     catch (const std::exception&)
122     {
123         // Compatible system types information is not available.
124     }
125 }
126 
findConfigFile(const std::vector<std::string> & compatibleSystemTypes)127 void UCD90xMonitor::findConfigFile(
128     const std::vector<std::string>& compatibleSystemTypes)
129 {
130     // Expected config file path name:
131     // /usr/share/phosphor-power-sequencer/<deviceName>Monitor_<systemType>.json
132 
133     // Add possible file names based on compatible system types (if any)
134     for (const std::string& systemType : compatibleSystemTypes)
135     {
136         // Check if file exists
137         std::filesystem::path pathName{"/usr/share/phosphor-power-sequencer/" +
138                                        deviceName + "Monitor_" + systemType +
139                                        ".json"};
140         log<level::DEBUG>(
141             fmt::format("Attempting config file path: {}", pathName.string())
142                 .c_str());
143         if (std::filesystem::exists(pathName))
144         {
145             log<level::INFO>(
146                 fmt::format("Config file path: {}", pathName.string()).c_str());
147             parseConfigFile(pathName);
148             break;
149         }
150     }
151 }
152 
interfacesAddedHandler(sdbusplus::message_t & msg)153 void UCD90xMonitor::interfacesAddedHandler(sdbusplus::message_t& msg)
154 {
155     // Only continue if message is valid and rails / pins have not already been
156     // found
157     if (!msg || !rails.empty())
158     {
159         return;
160     }
161 
162     try
163     {
164         // Read the dbus message
165         sdbusplus::message::object_path objPath;
166         std::map<std::string,
167                  std::map<std::string, std::variant<std::vector<std::string>>>>
168             interfaces;
169         msg.read(objPath, interfaces);
170 
171         // Find the compatible interface, if present
172         auto itIntf = interfaces.find(compatibleInterface);
173         if (itIntf != interfaces.cend())
174         {
175             // Find the Names property of the compatible interface, if present
176             auto itProp = itIntf->second.find(compatibleNamesProperty);
177             if (itProp != itIntf->second.cend())
178             {
179                 // Get value of Names property
180                 const auto& propValue = std::get<0>(itProp->second);
181                 if (!propValue.empty())
182                 {
183                     log<level::INFO>(
184                         fmt::format(
185                             "InterfacesAdded for compatible systems: {}",
186                             propValue)
187                             .c_str());
188 
189                     // Use compatible systems information to find config file
190                     findConfigFile(propValue);
191                 }
192             }
193         }
194     }
195     catch (const std::exception&)
196     {
197         // Error trying to read interfacesAdded message.
198     }
199 }
200 
isPresent(const std::string & inventoryPath)201 bool UCD90xMonitor::isPresent(const std::string& inventoryPath)
202 {
203     // Empty path indicates no presence check is needed
204     if (inventoryPath.empty())
205     {
206         return true;
207     }
208 
209     // Get presence from D-Bus interface/property
210     try
211     {
212         bool present{true};
213         util::getProperty(INVENTORY_IFACE, PRESENT_PROP, inventoryPath,
214                           INVENTORY_MGR_IFACE, bus, present);
215         log<level::INFO>(
216             fmt::format("Presence, path: {}, value: {}", inventoryPath, present)
217                 .c_str());
218         return present;
219     }
220     catch (const std::exception& e)
221     {
222         log<level::INFO>(
223             fmt::format("Error getting presence property, path: {}, error: {}",
224                         inventoryPath, e.what())
225                 .c_str());
226         return false;
227     }
228 }
229 
formatGpioValues(const std::vector<int> & values,unsigned int,std::map<std::string,std::string> & additionalData) const230 void UCD90xMonitor::formatGpioValues(
231     const std::vector<int>& values, unsigned int /*numberLines*/,
232     std::map<std::string, std::string>& additionalData) const
233 {
234     log<level::INFO>(fmt::format("GPIO values: {}", values).c_str());
235     additionalData.emplace("GPIO_VALUES", fmt::format("{}", values));
236 }
237 
onFailure(bool timeout,const std::string & powerSupplyError)238 void UCD90xMonitor::onFailure(bool timeout, const std::string& powerSupplyError)
239 {
240     std::string message;
241     std::map<std::string, std::string> additionalData{};
242 
243     try
244     {
245         onFailureCheckRails(message, additionalData, powerSupplyError);
246         log<level::DEBUG>(
247             fmt::format("After onFailureCheckRails, message: {}", message)
248                 .c_str());
249         onFailureCheckPins(message, additionalData);
250         log<level::DEBUG>(
251             fmt::format("After onFailureCheckPins, message: {}", message)
252                 .c_str());
253     }
254     catch (const std::exception& e)
255     {
256         log<level::ERR>(
257             fmt::format("Error when collecting metadata, error: {}", e.what())
258                 .c_str());
259         additionalData.emplace("ERROR", e.what());
260     }
261 
262     if (message.empty())
263     {
264         // Could not isolate, but we know something failed, so issue a timeout
265         // or generic power good error
266         message = timeout ? powerOnTimeoutError : shutdownError;
267     }
268     logError(message, additionalData);
269     if (!timeout)
270     {
271         createBmcDump();
272     }
273 }
274 
onFailureCheckPins(std::string & message,std::map<std::string,std::string> & additionalData)275 void UCD90xMonitor::onFailureCheckPins(
276     std::string& message, std::map<std::string, std::string>& additionalData)
277 {
278     // Create a lower case version of device name to use as label in libgpiod
279     std::string label{deviceName};
280     std::transform(label.begin(), label.end(), label.begin(), ::tolower);
281 
282     // Setup a list of all the GPIOs on the chip
283     gpiod::chip chip{label, gpiod::chip::OPEN_BY_LABEL};
284     log<level::INFO>(fmt::format("GPIO chip name: {}", chip.name()).c_str());
285     log<level::INFO>(fmt::format("GPIO chip label: {}", chip.label()).c_str());
286     unsigned int numberLines = chip.num_lines();
287     log<level::INFO>(
288         fmt::format("GPIO chip number of lines: {}", numberLines).c_str());
289 
290     // Read GPIO values.  Work around libgpiod bulk line maximum by getting
291     // values from individual lines.  The libgpiod line offsets are the same as
292     // the Pin IDs defined in the UCD90xxx PMBus interface documentation.  These
293     // Pin IDs are different from the pin numbers on the chip.  For example, on
294     // the UCD90160, "FPWM1/GPIO5" is Pin ID/line offset 0, but it is pin number
295     // 17 on the chip.
296     std::vector<int> values;
297     try
298     {
299         for (unsigned int offset = 0; offset < numberLines; ++offset)
300         {
301             gpiod::line line = chip.get_line(offset);
302             line.request({"phosphor-power-control",
303                           gpiod::line_request::DIRECTION_INPUT, 0});
304             values.push_back(line.get_value());
305             line.release();
306         }
307     }
308     catch (const std::exception& e)
309     {
310         log<level::ERR>(
311             fmt::format("Error reading device GPIOs, error: {}", e.what())
312                 .c_str());
313         additionalData.emplace("GPIO_ERROR", e.what());
314     }
315 
316     formatGpioValues(values, numberLines, additionalData);
317 
318     // Only check GPIOs if no rail fail was found
319     if (message.empty())
320     {
321         for (size_t pin = 0; pin < pins.size(); ++pin)
322         {
323             unsigned int line = pins[pin].line;
324             if (line < values.size())
325             {
326                 int value = values[line];
327 
328                 if ((value == 0) && isPresent(pins[pin].presence))
329                 {
330                     additionalData.emplace("INPUT_NUM",
331                                            fmt::format("{}", line));
332                     additionalData.emplace("INPUT_NAME", pins[pin].name);
333                     message =
334                         "xyz.openbmc_project.Power.Error.PowerSequencerPGOODFault";
335                     return;
336                 }
337             }
338         }
339     }
340 }
341 
onFailureCheckRails(std::string & message,std::map<std::string,std::string> & additionalData,const std::string & powerSupplyError)342 void UCD90xMonitor::onFailureCheckRails(
343     std::string& message, std::map<std::string, std::string>& additionalData,
344     const std::string& powerSupplyError)
345 {
346     auto statusWord = readStatusWord();
347     additionalData.emplace("STATUS_WORD", fmt::format("{:#06x}", statusWord));
348     try
349     {
350         additionalData.emplace("MFR_STATUS",
351                                fmt::format("{:#014x}", readMFRStatus()));
352     }
353     catch (const std::exception& e)
354     {
355         log<level::ERR>(
356             fmt::format("Error when collecting MFR_STATUS, error: {}", e.what())
357                 .c_str());
358         additionalData.emplace("ERROR", e.what());
359     }
360 
361     // The status_word register has a summary bit to tell us if each page even
362     // needs to be checked
363     if (statusWord & status_word::VOUT_FAULT)
364     {
365         for (size_t page = 0; page < numberPages; page++)
366         {
367             auto statusVout = pmbusInterface.insertPageNum(STATUS_VOUT, page);
368             if (pmbusInterface.exists(statusVout, Type::Debug))
369             {
370                 uint8_t vout = pmbusInterface.read(statusVout, Type::Debug);
371 
372                 if (vout)
373                 {
374                     // If any bits are on log them, though some are just
375                     // warnings so they won't cause errors
376                     log<level::INFO>(
377                         fmt::format("{}, value: {:#04x}", statusVout, vout)
378                             .c_str());
379 
380                     // Log errors if any non-warning bits on
381                     if (vout & ~status_vout::WARNING_MASK)
382                     {
383                         additionalData.emplace(
384                             fmt::format("STATUS{}_VOUT", page),
385                             fmt::format("{:#04x}", vout));
386 
387                         // Base the callouts on the first present vout failure
388                         // found
389                         if (message.empty() && (page < rails.size()) &&
390                             isPresent(rails[page].presence))
391                         {
392                             additionalData.emplace("RAIL_NAME",
393                                                    rails[page].name);
394 
395                             // Use power supply error if set and 12v rail has
396                             // failed, else use voltage error
397                             message =
398                                 ((page == 0) && !powerSupplyError.empty())
399                                     ? powerSupplyError
400                                     : "xyz.openbmc_project.Power.Error.PowerSequencerVoltageFault";
401                         }
402                     }
403                 }
404             }
405         }
406     }
407     // If no vout failure found, but power supply error is set, use power supply
408     // error
409     if (message.empty())
410     {
411         message = powerSupplyError;
412     }
413 }
414 
parseConfigFile(const std::filesystem::path & pathName)415 void UCD90xMonitor::parseConfigFile(const std::filesystem::path& pathName)
416 {
417     try
418     {
419         log<level::DEBUG>(
420             std::string("Loading configuration file " + pathName.string())
421                 .c_str());
422 
423         std::ifstream file{pathName};
424         json rootElement = json::parse(file);
425         log<level::DEBUG>(fmt::format("Parsed, root element is_object: {}",
426                                       rootElement.is_object())
427                               .c_str());
428 
429         // Parse rail information from config file
430         auto railsIterator = rootElement.find("rails");
431         if (railsIterator != rootElement.end())
432         {
433             for (const auto& railElement : *railsIterator)
434             {
435                 log<level::DEBUG>(fmt::format("Rail element is_object: {}",
436                                               railElement.is_object())
437                                       .c_str());
438 
439                 auto nameIterator = railElement.find("name");
440                 if (nameIterator != railElement.end())
441                 {
442                     log<level::DEBUG>(fmt::format("Name element is_string: {}",
443                                                   (*nameIterator).is_string())
444                                           .c_str());
445                     Rail rail;
446                     rail.name = (*nameIterator).get<std::string>();
447 
448                     // Presence element is optional
449                     auto presenceIterator = railElement.find("presence");
450                     if (presenceIterator != railElement.end())
451                     {
452                         log<level::DEBUG>(
453                             fmt::format("Presence element is_string: {}",
454                                         (*presenceIterator).is_string())
455                                 .c_str());
456 
457                         rail.presence = (*presenceIterator).get<std::string>();
458                     }
459 
460                     log<level::DEBUG>(
461                         fmt::format("Adding rail, name: {}, presence: {}",
462                                     rail.name, rail.presence)
463                             .c_str());
464                     rails.emplace_back(std::move(rail));
465                 }
466                 else
467                 {
468                     log<level::ERR>(
469                         fmt::format(
470                             "No name found within rail in configuration file: {}",
471                             pathName.string())
472                             .c_str());
473                 }
474             }
475         }
476         else
477         {
478             log<level::ERR>(
479                 fmt::format("No rails found in configuration file: {}",
480                             pathName.string())
481                     .c_str());
482         }
483         log<level::DEBUG>(
484             fmt::format("Found number of rails: {}", rails.size()).c_str());
485 
486         // Parse pin information from config file
487         auto pinsIterator = rootElement.find("pins");
488         if (pinsIterator != rootElement.end())
489         {
490             for (const auto& pinElement : *pinsIterator)
491             {
492                 log<level::DEBUG>(fmt::format("Pin element is_object: {}",
493                                               pinElement.is_object())
494                                       .c_str());
495                 auto nameIterator = pinElement.find("name");
496                 auto lineIterator = pinElement.find("line");
497                 if (nameIterator != pinElement.end() &&
498                     lineIterator != pinElement.end())
499                 {
500                     log<level::DEBUG>(fmt::format("Name element is_string: {}",
501                                                   (*nameIterator).is_string())
502                                           .c_str());
503                     log<level::DEBUG>(
504                         fmt::format("Line element is_number_integer: {}",
505                                     (*lineIterator).is_number_integer())
506                             .c_str());
507                     Pin pin;
508                     pin.name = (*nameIterator).get<std::string>();
509                     pin.line = (*lineIterator).get<unsigned int>();
510 
511                     // Presence element is optional
512                     auto presenceIterator = pinElement.find("presence");
513                     if (presenceIterator != pinElement.end())
514                     {
515                         log<level::DEBUG>(
516                             fmt::format("Presence element is_string: {}",
517                                         (*presenceIterator).is_string())
518                                 .c_str());
519                         pin.presence = (*presenceIterator).get<std::string>();
520                     }
521 
522                     log<level::DEBUG>(
523                         fmt::format(
524                             "Adding pin, name: {}, line: {}, presence: {}",
525                             pin.name, pin.line, pin.presence)
526                             .c_str());
527                     pins.emplace_back(std::move(pin));
528                 }
529                 else
530                 {
531                     log<level::ERR>(
532                         fmt::format(
533                             "No name or line found within pin in configuration file: {}",
534                             pathName.string())
535                             .c_str());
536                 }
537             }
538         }
539         else
540         {
541             log<level::ERR>(
542                 fmt::format("No pins found in configuration file: {}",
543                             pathName.string())
544                     .c_str());
545         }
546         log<level::DEBUG>(
547             fmt::format("Found number of pins: {}", pins.size()).c_str());
548     }
549     catch (const std::exception& e)
550     {
551         log<level::ERR>(
552             fmt::format("Error parsing configuration file, error: {}", e.what())
553                 .c_str());
554     }
555 }
556 
readStatusWord()557 uint16_t UCD90xMonitor::readStatusWord()
558 {
559     return pmbusInterface.read(STATUS_WORD, Type::Debug);
560 }
561 
readMFRStatus()562 uint64_t UCD90xMonitor::readMFRStatus()
563 {
564     const std::string mfrStatus = "mfr_status";
565     return pmbusInterface.read(mfrStatus, Type::HwmonDeviceDebug);
566 }
567 
568 } // namespace phosphor::power::sequencer
569