blob: 3f431fb3f814edd67302c04430caa7f5bc48384e [file] [log] [blame]
Jim Wright1553cd92021-03-31 16:11:59 -05001/**
2 * Copyright © 2021 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
Jim Wright7945dd22021-04-06 16:55:15 -050017#include "ucd90320_monitor.hpp"
18
Jim Wright56ae78e2021-12-01 14:46:15 -060019#include "utility.hpp"
Jim Wright7945dd22021-04-06 16:55:15 -050020
Jim Wright56ae78e2021-12-01 14:46:15 -060021#include <fmt/format.h>
22#include <fmt/ranges.h>
23
Jim Wrightd8a86172021-12-08 11:38:26 -060024#include <nlohmann/json.hpp>
Jim Wright56ae78e2021-12-01 14:46:15 -060025#include <phosphor-logging/log.hpp>
26#include <sdbusplus/bus.hpp>
Jim Wright71a14132022-01-28 09:46:46 -060027#include <xyz/openbmc_project/Common/Device/error.hpp>
Jim Wright56ae78e2021-12-01 14:46:15 -060028
Jim Wright056bc192022-06-06 17:46:34 -050029#include <chrono>
Jim Wrightd8a86172021-12-08 11:38:26 -060030#include <fstream>
Jim Wright56ae78e2021-12-01 14:46:15 -060031#include <map>
Jim Wright213ffe92022-06-03 08:54:30 -050032#include <span>
Jim Wright7945dd22021-04-06 16:55:15 -050033#include <string>
34
35namespace phosphor::power::sequencer
Jim Wright1553cd92021-03-31 16:11:59 -050036{
Jim Wright7945dd22021-04-06 16:55:15 -050037
Jim Wrightd8a86172021-12-08 11:38:26 -060038using json = nlohmann::json;
Jim Wright71a14132022-01-28 09:46:46 -060039using namespace pmbus;
Jim Wright56ae78e2021-12-01 14:46:15 -060040using namespace phosphor::logging;
41using namespace phosphor::power;
42
43const std::string compatibleInterface =
44 "xyz.openbmc_project.Configuration.IBMCompatibleSystem";
45const std::string compatibleNamesProperty = "Names";
46
Jim Wright71a14132022-01-28 09:46:46 -060047namespace device_error = sdbusplus::xyz::openbmc_project::Common::Device::Error;
48
Jim Wright7945dd22021-04-06 16:55:15 -050049UCD90320Monitor::UCD90320Monitor(sdbusplus::bus::bus& bus, std::uint8_t i2cBus,
50 std::uint16_t i2cAddress) :
Jim Wright930458c2022-01-24 14:37:27 -060051 PowerSequencerMonitor(bus),
52 match{bus,
53 sdbusplus::bus::match::rules::interfacesAdded() +
54 sdbusplus::bus::match::rules::sender(
55 "xyz.openbmc_project.EntityManager"),
56 std::bind(&UCD90320Monitor::interfacesAddedHandler, this,
57 std::placeholders::_1)},
Jim Wright56ae78e2021-12-01 14:46:15 -060058 pmbusInterface{
59 fmt::format("/sys/bus/i2c/devices/{}-{:04x}", i2cBus, i2cAddress)
60 .c_str(),
61 "ucd9000", 0}
62
63{
64 // Use the compatible system types information, if already available, to
65 // load the configuration file
66 findCompatibleSystemTypes();
Jim Wright1553cd92021-03-31 16:11:59 -050067}
Jim Wright56ae78e2021-12-01 14:46:15 -060068
69void UCD90320Monitor::findCompatibleSystemTypes()
70{
71 try
72 {
73 auto subTree = util::getSubTree(bus, "/xyz/openbmc_project/inventory",
74 compatibleInterface, 0);
75
76 auto objectIt = subTree.cbegin();
77 if (objectIt != subTree.cend())
78 {
79 const auto& objPath = objectIt->first;
80
81 // Get the first service name
82 auto serviceIt = objectIt->second.cbegin();
83 if (serviceIt != objectIt->second.cend())
84 {
85 std::string service = serviceIt->first;
86 if (!service.empty())
87 {
88 std::vector<std::string> compatibleSystemTypes;
89
90 // Get compatible system types property value
91 util::getProperty(compatibleInterface,
92 compatibleNamesProperty, objPath, service,
93 bus, compatibleSystemTypes);
94
95 log<level::DEBUG>(
96 fmt::format("Found compatible systems: {}",
97 compatibleSystemTypes)
98 .c_str());
99 // Use compatible systems information to find config file
Jim Wrightd8a86172021-12-08 11:38:26 -0600100 findConfigFile(compatibleSystemTypes);
Jim Wright56ae78e2021-12-01 14:46:15 -0600101 }
102 }
103 }
104 }
105 catch (const std::exception&)
106 {
107 // Compatible system types information is not available.
108 }
109}
110
Jim Wrightd8a86172021-12-08 11:38:26 -0600111void UCD90320Monitor::findConfigFile(
112 const std::vector<std::string>& compatibleSystemTypes)
113{
114 // Expected config file path name:
115 // /usr/share/phosphor-power-sequencer/UCD90320Monitor_<systemType>.json
116
117 // Add possible file names based on compatible system types (if any)
118 for (const std::string& systemType : compatibleSystemTypes)
119 {
120 // Check if file exists
121 std::filesystem::path pathName{
122 "/usr/share/phosphor-power-sequencer/UCD90320Monitor_" +
123 systemType + ".json"};
124 if (std::filesystem::exists(pathName))
125 {
126 log<level::INFO>(
127 fmt::format("Config file path: {}", pathName.string()).c_str());
128 parseConfigFile(pathName);
129 break;
130 }
131 }
132}
133
Jim Wright56ae78e2021-12-01 14:46:15 -0600134void UCD90320Monitor::interfacesAddedHandler(sdbusplus::message::message& msg)
135{
Jim Wrightd8a86172021-12-08 11:38:26 -0600136 // Only continue if message is valid and rails / pins have not already been
137 // found
138 if (!msg || !rails.empty())
Jim Wright56ae78e2021-12-01 14:46:15 -0600139 {
140 return;
141 }
142
143 try
144 {
145 // Read the dbus message
146 sdbusplus::message::object_path objPath;
147 std::map<std::string,
148 std::map<std::string, std::variant<std::vector<std::string>>>>
149 interfaces;
150 msg.read(objPath, interfaces);
151
152 // Find the compatible interface, if present
153 auto itIntf = interfaces.find(compatibleInterface);
154 if (itIntf != interfaces.cend())
155 {
156 // Find the Names property of the compatible interface, if present
157 auto itProp = itIntf->second.find(compatibleNamesProperty);
158 if (itProp != itIntf->second.cend())
159 {
160 // Get value of Names property
161 const auto& propValue = std::get<0>(itProp->second);
162 if (!propValue.empty())
163 {
164 log<level::INFO>(
165 fmt::format(
166 "InterfacesAdded for compatible systems: {}",
167 propValue)
168 .c_str());
169
170 // Use compatible systems information to find config file
Jim Wrightd8a86172021-12-08 11:38:26 -0600171 findConfigFile(propValue);
Jim Wright56ae78e2021-12-01 14:46:15 -0600172 }
173 }
174 }
175 }
176 catch (const std::exception&)
177 {
178 // Error trying to read interfacesAdded message.
179 }
180}
Jim Wright7945dd22021-04-06 16:55:15 -0500181
Jim Wrightd8a86172021-12-08 11:38:26 -0600182void UCD90320Monitor::parseConfigFile(const std::filesystem::path& pathName)
183{
184 try
185 {
186 std::ifstream file{pathName};
187 json rootElement = json::parse(file);
188
189 // Parse rail information from config file
190 auto railsIterator = rootElement.find("rails");
191 if (railsIterator != rootElement.end())
192 {
193 for (const auto& railElement : *railsIterator)
194 {
195 std::string rail = railElement.get<std::string>();
196 rails.emplace_back(std::move(rail));
197 }
198 }
199 else
200 {
201 log<level::ERR>(
202 fmt::format("No rails found in configuration file: {}",
203 pathName.string())
204 .c_str());
205 }
206 log<level::DEBUG>(fmt::format("Found rails: {}", rails).c_str());
207
208 // Parse pin information from config file
209 auto pinsIterator = rootElement.find("pins");
210 if (pinsIterator != rootElement.end())
211 {
212 for (const auto& pinElement : *pinsIterator)
213 {
214 auto nameIterator = pinElement.find("name");
215 auto lineIterator = pinElement.find("line");
216
217 if (nameIterator != pinElement.end() &&
218 lineIterator != pinElement.end())
219 {
220 std::string name = (*nameIterator).get<std::string>();
Jim Wrightd8fc0682022-01-11 15:36:00 -0600221 unsigned int line = (*lineIterator).get<unsigned int>();
Jim Wrightd8a86172021-12-08 11:38:26 -0600222
223 Pin pin;
224 pin.name = name;
225 pin.line = line;
226 pins.emplace_back(std::move(pin));
227 }
228 else
229 {
230 log<level::ERR>(
231 fmt::format(
232 "No name or line found within pin in configuration file: {}",
233 pathName.string())
234 .c_str());
235 }
236 }
237 }
238 else
239 {
240 log<level::ERR>(
241 fmt::format("No pins found in configuration file: {}",
242 pathName.string())
243 .c_str());
244 }
245 log<level::DEBUG>(
Jim Wright213ffe92022-06-03 08:54:30 -0500246 fmt::format("Found number of pins: {}", pins.size()).c_str());
Jim Wrightd8a86172021-12-08 11:38:26 -0600247 }
248 catch (const std::exception& e)
249 {
250 // Log error message in journal
251 log<level::ERR>(std::string("Exception parsing configuration file: " +
252 std::string(e.what()))
253 .c_str());
254 }
255}
256
Jim Wright71a14132022-01-28 09:46:46 -0600257void UCD90320Monitor::onFailure(bool timeout,
258 const std::string& powerSupplyError)
259{
Jim Wright08fcb9f2022-07-21 09:48:29 -0500260 // Wait before reading device data. This is to allow the
Jim Wright056bc192022-06-06 17:46:34 -0500261 // power supplies and other hardware time to complete failure processing.
Jim Wright08fcb9f2022-07-21 09:48:29 -0500262 std::this_thread::sleep_for(std::chrono::seconds(7));
Jim Wright056bc192022-06-06 17:46:34 -0500263
Jim Wrightf6f0da92022-02-28 21:08:33 -0600264 std::string message;
Jim Wright71a14132022-01-28 09:46:46 -0600265 std::map<std::string, std::string> additionalData{};
Jim Wright71a14132022-01-28 09:46:46 -0600266
267 try
268 {
Jim Wright3accffe2022-03-10 17:19:42 -0600269 onFailureCheckRails(message, additionalData, powerSupplyError);
Jim Wright3accffe2022-03-10 17:19:42 -0600270 onFailureCheckPins(message, additionalData);
Jim Wright71a14132022-01-28 09:46:46 -0600271 }
272 catch (device_error::ReadFailure& e)
273 {
Jim Wrightf6f0da92022-02-28 21:08:33 -0600274 log<level::ERR>(
275 fmt::format("ReadFailure when collecting metadata, error {}",
276 e.what())
277 .c_str());
Jim Wright71a14132022-01-28 09:46:46 -0600278 }
Jim Wrightf6f0da92022-02-28 21:08:33 -0600279
280 if (message.empty())
281 {
282 // Could not isolate, but we know something failed, so issue a timeout
283 // or generic power good error
284 message = timeout ? "xyz.openbmc_project.Power.Error.PowerOnTimeout"
285 : "xyz.openbmc_project.Power.Error.Shutdown";
286 }
287 logError(message, additionalData);
Jim Wright213ffe92022-06-03 08:54:30 -0500288 if (!timeout)
289 {
290 createBmcDump();
291 }
Jim Wright71a14132022-01-28 09:46:46 -0600292}
293
Jim Wright3accffe2022-03-10 17:19:42 -0600294void UCD90320Monitor::onFailureCheckPins(
295 std::string& message, std::map<std::string, std::string>& additionalData)
296{
297 // Setup a list of all the GPIOs on the chip
298 gpiod::chip chip{"ucd90320", gpiod::chip::OPEN_BY_LABEL};
299 log<level::INFO>(fmt::format("GPIO chip name: {}", chip.name()).c_str());
300 log<level::INFO>(fmt::format("GPIO chip label: {}", chip.label()).c_str());
301 unsigned int numberLines = chip.num_lines();
302 log<level::INFO>(
303 fmt::format("GPIO chip number of lines: {}", numberLines).c_str());
304
305 // Workaround libgpiod bulk line maximum by getting values from individual
306 // lines
307 std::vector<int> values;
Jim Wright213ffe92022-06-03 08:54:30 -0500308 try
Jim Wright3accffe2022-03-10 17:19:42 -0600309 {
Jim Wright213ffe92022-06-03 08:54:30 -0500310 for (unsigned int offset = 0; offset < numberLines; ++offset)
311 {
312 gpiod::line line = chip.get_line(offset);
313 line.request({"phosphor-power-control",
314 gpiod::line_request::DIRECTION_INPUT, 0});
315 values.push_back(line.get_value());
316 line.release();
317 }
318 }
319 catch (const std::exception& e)
320 {
321 log<level::ERR>(
322 fmt::format("Error reading device GPIOs, error {}", e.what())
323 .c_str());
324 additionalData.emplace("GPIO_ERROR", e.what());
Jim Wright3accffe2022-03-10 17:19:42 -0600325 }
326
Jim Wright213ffe92022-06-03 08:54:30 -0500327 // Add GPIO values to additional data, device has 84 GPIO pins so that value
328 // is expected
329 if (numberLines == 84)
330 {
331 log<level::INFO>(fmt::format("MAR01-24 GPIO values: {}",
332 std::span{values}.subspan(0, 24))
333 .c_str());
334 additionalData.emplace(
335 "MAR01_24_GPIO_VALUES",
336 fmt::format("{}", std::span{values}.subspan(0, 24)));
337 log<level::INFO>(fmt::format("EN1-32 GPIO values: {}",
338 std::span{values}.subspan(24, 32))
339 .c_str());
340 additionalData.emplace(
341 "EN1_32_GPIO_VALUES",
342 fmt::format("{}", std::span{values}.subspan(24, 32)));
343 log<level::INFO>(fmt::format("LGP01-16 GPIO values: {}",
344 std::span{values}.subspan(56, 16))
345 .c_str());
346 additionalData.emplace(
347 "LGP01_16_GPIO_VALUES",
348 fmt::format("{}", std::span{values}.subspan(56, 16)));
349 log<level::INFO>(fmt::format("DMON1-8 GPIO values: {}",
350 std::span{values}.subspan(72, 8))
351 .c_str());
352 additionalData.emplace(
353 "DMON1_8_GPIO_VALUES",
354 fmt::format("{}", std::span{values}.subspan(72, 8)));
355 log<level::INFO>(fmt::format("GPIO1-4 GPIO values: {}",
356 std::span{values}.subspan(80, 4))
357 .c_str());
358 additionalData.emplace(
359 "GPIO1_4_GPIO_VALUES",
360 fmt::format("{}", std::span{values}.subspan(80, 4)));
361 }
362 else
363 {
364 log<level::INFO>(fmt::format("GPIO values: {}", values).c_str());
365 additionalData.emplace("GPIO_VALUES", fmt::format("{}", values));
366 }
Jim Wright3accffe2022-03-10 17:19:42 -0600367
368 // Only check GPIOs if no rail fail was found
369 if (message.empty())
370 {
371 for (size_t pin = 0; pin < pins.size(); ++pin)
372 {
373 unsigned int line = pins[pin].line;
374 if (line < values.size())
375 {
376 int value = values[line];
377 if (value == 0)
378 {
379 additionalData.emplace("INPUT_NUM",
380 fmt::format("{}", line));
381 additionalData.emplace("INPUT_NAME", pins[pin].name);
Jim Wright3accffe2022-03-10 17:19:42 -0600382 message =
383 "xyz.openbmc_project.Power.Error.PowerSequencerPGOODFault";
384 return;
385 }
386 }
387 }
388 }
389}
390
391void UCD90320Monitor::onFailureCheckRails(
392 std::string& message, std::map<std::string, std::string>& additionalData,
393 const std::string& powerSupplyError)
394{
395 auto statusWord = readStatusWord();
396 additionalData.emplace("STATUS_WORD", fmt::format("{:#06x}", statusWord));
397 try
398 {
399 additionalData.emplace("MFR_STATUS",
Jim Wright213ffe92022-06-03 08:54:30 -0500400 fmt::format("{:#014x}", readMFRStatus()));
Jim Wright3accffe2022-03-10 17:19:42 -0600401 }
402 catch (device_error::ReadFailure& e)
403 {
404 log<level::ERR>(
405 fmt::format("ReadFailure when collecting MFR_STATUS, error {}",
406 e.what())
407 .c_str());
408 }
409
410 // The status_word register has a summary bit to tell us if each page even
411 // needs to be checked
412 if (statusWord & status_word::VOUT_FAULT)
413 {
Jim Wright286bc702022-05-02 11:01:46 -0500414 constexpr size_t numberPages = 32;
Jim Wright3accffe2022-03-10 17:19:42 -0600415 for (size_t page = 0; page < numberPages; page++)
416 {
417 auto statusVout = pmbusInterface.insertPageNum(STATUS_VOUT, page);
Jim Wright2a054922022-04-13 09:39:27 -0500418 if (pmbusInterface.exists(statusVout, Type::Debug))
Jim Wright3accffe2022-03-10 17:19:42 -0600419 {
Jim Wright2a054922022-04-13 09:39:27 -0500420 uint8_t vout = pmbusInterface.read(statusVout, Type::Debug);
Jim Wright3accffe2022-03-10 17:19:42 -0600421
Jim Wright2a054922022-04-13 09:39:27 -0500422 if (vout)
423 {
Jim Wright213ffe92022-06-03 08:54:30 -0500424 // If any bits are on log them, though some are just
425 // warnings so they won't cause errors
Jim Wright2a054922022-04-13 09:39:27 -0500426 log<level::INFO>(
Jim Wright213ffe92022-06-03 08:54:30 -0500427 fmt::format("{}, value: {:#04x}", statusVout, vout)
Jim Wright2a054922022-04-13 09:39:27 -0500428 .c_str());
Jim Wright3accffe2022-03-10 17:19:42 -0600429
Jim Wright213ffe92022-06-03 08:54:30 -0500430 // Log errors if any non-warning bits on
431 if (vout & ~status_vout::WARNING_MASK)
Jim Wright2a054922022-04-13 09:39:27 -0500432 {
Jim Wright213ffe92022-06-03 08:54:30 -0500433 additionalData.emplace(
434 fmt::format("STATUS{}_VOUT", page),
435 fmt::format("{:#04x}", vout));
Jim Wright2a054922022-04-13 09:39:27 -0500436
Jim Wright213ffe92022-06-03 08:54:30 -0500437 // Base the callouts on the first vout failure found
438 if (message.empty())
439 {
440 if (page < rails.size())
441 {
442 additionalData.emplace("RAIL_NAME",
443 rails[page]);
444 }
445
446 // Use power supply error if set and 12v rail has
447 // failed, else use voltage error
448 message =
449 ((page == 0) && !powerSupplyError.empty())
450 ? powerSupplyError
451 : "xyz.openbmc_project.Power.Error.PowerSequencerVoltageFault";
452 }
453 }
Jim Wright2a054922022-04-13 09:39:27 -0500454 }
Jim Wright3accffe2022-03-10 17:19:42 -0600455 }
456 }
457 }
Jim Wright056bc192022-06-06 17:46:34 -0500458 // If no vout failure found, but power supply error is set, use power supply
459 // error
460 if (message.empty())
461 {
462 message = powerSupplyError;
463 }
Jim Wright3accffe2022-03-10 17:19:42 -0600464}
465
Jim Wright71a14132022-01-28 09:46:46 -0600466uint16_t UCD90320Monitor::readStatusWord()
467{
468 return pmbusInterface.read(STATUS_WORD, Type::Debug);
469}
470
Jim Wright213ffe92022-06-03 08:54:30 -0500471uint64_t UCD90320Monitor::readMFRStatus()
Jim Wright71a14132022-01-28 09:46:46 -0600472{
473 const std::string mfrStatus = "mfr_status";
474 return pmbusInterface.read(mfrStatus, Type::HwmonDeviceDebug);
475}
476
Jim Wright7945dd22021-04-06 16:55:15 -0500477} // namespace phosphor::power::sequencer