blob: 2dc4675f3641d674b6f8008f5064c56dad0fc3c9 [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
290 // Workaround libgpiod bulk line maximum by getting values from individual
291 // lines
292 std::vector<int> values;
293 try
294 {
295 for (unsigned int offset = 0; offset < numberLines; ++offset)
296 {
297 gpiod::line line = chip.get_line(offset);
298 line.request({"phosphor-power-control",
299 gpiod::line_request::DIRECTION_INPUT, 0});
300 values.push_back(line.get_value());
301 line.release();
302 }
303 }
304 catch (const std::exception& e)
305 {
306 log<level::ERR>(
307 fmt::format("Error reading device GPIOs, error: {}", e.what())
308 .c_str());
309 additionalData.emplace("GPIO_ERROR", e.what());
310 }
311
312 formatGpioValues(values, numberLines, additionalData);
313
314 // Only check GPIOs if no rail fail was found
315 if (message.empty())
316 {
317 for (size_t pin = 0; pin < pins.size(); ++pin)
318 {
319 unsigned int line = pins[pin].line;
320 if (line < values.size())
321 {
322 int value = values[line];
323
324 if ((value == 0) && isPresent(pins[pin].presence))
325 {
326 additionalData.emplace("INPUT_NUM",
327 fmt::format("{}", line));
328 additionalData.emplace("INPUT_NAME", pins[pin].name);
329 message =
330 "xyz.openbmc_project.Power.Error.PowerSequencerPGOODFault";
331 return;
332 }
333 }
334 }
335 }
336}
337
338void UCD90xMonitor::onFailureCheckRails(
339 std::string& message, std::map<std::string, std::string>& additionalData,
340 const std::string& powerSupplyError)
341{
342 auto statusWord = readStatusWord();
343 additionalData.emplace("STATUS_WORD", fmt::format("{:#06x}", statusWord));
344 try
345 {
346 additionalData.emplace("MFR_STATUS",
347 fmt::format("{:#014x}", readMFRStatus()));
348 }
349 catch (const std::exception& e)
350 {
351 log<level::ERR>(
352 fmt::format("Error when collecting MFR_STATUS, error: {}", e.what())
353 .c_str());
354 additionalData.emplace("ERROR", e.what());
355 }
356
357 // The status_word register has a summary bit to tell us if each page even
358 // needs to be checked
359 if (statusWord & status_word::VOUT_FAULT)
360 {
361 for (size_t page = 0; page < numberPages; page++)
362 {
363 auto statusVout = pmbusInterface.insertPageNum(STATUS_VOUT, page);
364 if (pmbusInterface.exists(statusVout, Type::Debug))
365 {
366 uint8_t vout = pmbusInterface.read(statusVout, Type::Debug);
367
368 if (vout)
369 {
370 // If any bits are on log them, though some are just
371 // warnings so they won't cause errors
372 log<level::INFO>(
373 fmt::format("{}, value: {:#04x}", statusVout, vout)
374 .c_str());
375
376 // Log errors if any non-warning bits on
377 if (vout & ~status_vout::WARNING_MASK)
378 {
379 additionalData.emplace(
380 fmt::format("STATUS{}_VOUT", page),
381 fmt::format("{:#04x}", vout));
382
383 // Base the callouts on the first present vout failure
384 // found
385 if (message.empty() && (page < rails.size()) &&
386 isPresent(rails[page].presence))
387 {
388 additionalData.emplace("RAIL_NAME",
389 rails[page].name);
390
391 // Use power supply error if set and 12v rail has
392 // failed, else use voltage error
393 message =
394 ((page == 0) && !powerSupplyError.empty())
395 ? powerSupplyError
396 : "xyz.openbmc_project.Power.Error.PowerSequencerVoltageFault";
397 }
398 }
399 }
400 }
401 }
402 }
403 // If no vout failure found, but power supply error is set, use power supply
404 // error
405 if (message.empty())
406 {
407 message = powerSupplyError;
408 }
409}
410
411void UCD90xMonitor::parseConfigFile(const std::filesystem::path& pathName)
412{
413 try
414 {
415 log<level::DEBUG>(
416 std::string("Loading configuration file " + pathName.string())
417 .c_str());
418
419 std::ifstream file{pathName};
420 json rootElement = json::parse(file);
421 log<level::DEBUG>(fmt::format("Parsed, root element is_object: {}",
422 rootElement.is_object())
423 .c_str());
424
425 // Parse rail information from config file
426 auto railsIterator = rootElement.find("rails");
427 if (railsIterator != rootElement.end())
428 {
429 for (const auto& railElement : *railsIterator)
430 {
431 log<level::DEBUG>(fmt::format("Rail element is_object: {}",
432 railElement.is_object())
433 .c_str());
434
435 auto nameIterator = railElement.find("name");
436 if (nameIterator != railElement.end())
437 {
438 log<level::DEBUG>(fmt::format("Name element is_string: {}",
439 (*nameIterator).is_string())
440 .c_str());
441 Rail rail;
442 rail.name = (*nameIterator).get<std::string>();
443
444 // Presence element is optional
445 auto presenceIterator = railElement.find("presence");
446 if (presenceIterator != railElement.end())
447 {
448 log<level::DEBUG>(
449 fmt::format("Presence element is_string: {}",
450 (*presenceIterator).is_string())
451 .c_str());
452
453 rail.presence = (*presenceIterator).get<std::string>();
454 }
455
456 log<level::DEBUG>(
457 fmt::format("Adding rail, name: {}, presence: {}",
458 rail.name, rail.presence)
459 .c_str());
460 rails.emplace_back(std::move(rail));
461 }
462 else
463 {
464 log<level::ERR>(
465 fmt::format(
466 "No name found within rail in configuration file: {}",
467 pathName.string())
468 .c_str());
469 }
470 }
471 }
472 else
473 {
474 log<level::ERR>(
475 fmt::format("No rails found in configuration file: {}",
476 pathName.string())
477 .c_str());
478 }
479 log<level::DEBUG>(
480 fmt::format("Found number of rails: {}", rails.size()).c_str());
481
482 // Parse pin information from config file
483 auto pinsIterator = rootElement.find("pins");
484 if (pinsIterator != rootElement.end())
485 {
486 for (const auto& pinElement : *pinsIterator)
487 {
488 log<level::DEBUG>(fmt::format("Pin element is_object: {}",
489 pinElement.is_object())
490 .c_str());
491 auto nameIterator = pinElement.find("name");
492 auto lineIterator = pinElement.find("line");
493 if (nameIterator != pinElement.end() &&
494 lineIterator != pinElement.end())
495 {
496 log<level::DEBUG>(fmt::format("Name element is_string: {}",
497 (*nameIterator).is_string())
498 .c_str());
499 log<level::DEBUG>(
500 fmt::format("Line element is_number_integer: {}",
501 (*lineIterator).is_number_integer())
502 .c_str());
503 Pin pin;
504 pin.name = (*nameIterator).get<std::string>();
505 pin.line = (*lineIterator).get<unsigned int>();
506
507 // Presence element is optional
508 auto presenceIterator = pinElement.find("presence");
509 if (presenceIterator != pinElement.end())
510 {
511 log<level::DEBUG>(
512 fmt::format("Presence element is_string: {}",
513 (*presenceIterator).is_string())
514 .c_str());
515 pin.presence = (*presenceIterator).get<std::string>();
516 }
517
518 log<level::DEBUG>(
519 fmt::format(
520 "Adding pin, name: {}, line: {}, presence: {}",
521 pin.name, pin.line, pin.presence)
522 .c_str());
523 pins.emplace_back(std::move(pin));
524 }
525 else
526 {
527 log<level::ERR>(
528 fmt::format(
529 "No name or line found within pin in configuration file: {}",
530 pathName.string())
531 .c_str());
532 }
533 }
534 }
535 else
536 {
537 log<level::ERR>(
538 fmt::format("No pins found in configuration file: {}",
539 pathName.string())
540 .c_str());
541 }
542 log<level::DEBUG>(
543 fmt::format("Found number of pins: {}", pins.size()).c_str());
544 }
545 catch (const std::exception& e)
546 {
547 log<level::ERR>(
548 fmt::format("Error parsing configuration file, error: {}", e.what())
549 .c_str());
550 }
551}
552
553uint16_t UCD90xMonitor::readStatusWord()
554{
555 return pmbusInterface.read(STATUS_WORD, Type::Debug);
556}
557
558uint64_t UCD90xMonitor::readMFRStatus()
559{
560 const std::string mfrStatus = "mfr_status";
561 return pmbusInterface.read(mfrStatus, Type::HwmonDeviceDebug);
562}
563
564} // namespace phosphor::power::sequencer