blob: f36d73492848ae42e72879f47a0274337c212e4f [file] [log] [blame]
Jim Wrightc48551a2022-12-22 15:43:14 -06001/**
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
34namespace phosphor::power::sequencer
35{
36
37using json = nlohmann::json;
38using namespace pmbus;
39using namespace phosphor::logging;
40
41const std::string compatibleInterface =
42 "xyz.openbmc_project.Configuration.IBMCompatibleSystem";
43const std::string compatibleNamesProperty = "Names";
44
45UCD90xMonitor::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
85void 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
127void 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
153void 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
201bool 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
230void 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
238void 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
275void 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
Shawn McCarney328ca312023-07-11 08:18:41 -0500290 // 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.
Jim Wrightc48551a2022-12-22 15:43:14 -0600296 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
342void 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
415void 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
557uint16_t UCD90xMonitor::readStatusWord()
558{
559 return pmbusInterface.read(STATUS_WORD, Type::Debug);
560}
561
562uint64_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