blob: c6adcc03d95b0481154c9b34dddb16baaf701d32 [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 Wrightc91eed02022-07-22 11:27:06 -050019#include "types.hpp"
Jim Wright56ae78e2021-12-01 14:46:15 -060020#include "utility.hpp"
Jim Wright7945dd22021-04-06 16:55:15 -050021
Jim Wright56ae78e2021-12-01 14:46:15 -060022#include <fmt/format.h>
23#include <fmt/ranges.h>
24
Jim Wrightd8a86172021-12-08 11:38:26 -060025#include <nlohmann/json.hpp>
Jim Wright56ae78e2021-12-01 14:46:15 -060026#include <phosphor-logging/log.hpp>
27#include <sdbusplus/bus.hpp>
Jim Wright71a14132022-01-28 09:46:46 -060028#include <xyz/openbmc_project/Common/Device/error.hpp>
Jim Wright56ae78e2021-12-01 14:46:15 -060029
Jim Wright056bc192022-06-06 17:46:34 -050030#include <chrono>
Jim Wrightd8a86172021-12-08 11:38:26 -060031#include <fstream>
Jim Wright56ae78e2021-12-01 14:46:15 -060032#include <map>
Jim Wright213ffe92022-06-03 08:54:30 -050033#include <span>
Jim Wright7945dd22021-04-06 16:55:15 -050034#include <string>
35
36namespace phosphor::power::sequencer
Jim Wright1553cd92021-03-31 16:11:59 -050037{
Jim Wright7945dd22021-04-06 16:55:15 -050038
Jim Wrightd8a86172021-12-08 11:38:26 -060039using json = nlohmann::json;
Jim Wright71a14132022-01-28 09:46:46 -060040using namespace pmbus;
Jim Wright56ae78e2021-12-01 14:46:15 -060041using namespace phosphor::logging;
42using namespace phosphor::power;
43
44const std::string compatibleInterface =
45 "xyz.openbmc_project.Configuration.IBMCompatibleSystem";
46const std::string compatibleNamesProperty = "Names";
47
Jim Wright71a14132022-01-28 09:46:46 -060048namespace device_error = sdbusplus::xyz::openbmc_project::Common::Device::Error;
49
Patrick Williams7354ce62022-07-22 19:26:56 -050050UCD90320Monitor::UCD90320Monitor(sdbusplus::bus_t& bus, std::uint8_t i2cBus,
Jim Wright7945dd22021-04-06 16:55:15 -050051 std::uint16_t i2cAddress) :
Jim Wright930458c2022-01-24 14:37:27 -060052 PowerSequencerMonitor(bus),
53 match{bus,
54 sdbusplus::bus::match::rules::interfacesAdded() +
55 sdbusplus::bus::match::rules::sender(
56 "xyz.openbmc_project.EntityManager"),
57 std::bind(&UCD90320Monitor::interfacesAddedHandler, this,
58 std::placeholders::_1)},
Jim Wright56ae78e2021-12-01 14:46:15 -060059 pmbusInterface{
60 fmt::format("/sys/bus/i2c/devices/{}-{:04x}", i2cBus, i2cAddress)
61 .c_str(),
62 "ucd9000", 0}
63
64{
65 // Use the compatible system types information, if already available, to
66 // load the configuration file
67 findCompatibleSystemTypes();
Jim Wright1553cd92021-03-31 16:11:59 -050068}
Jim Wright56ae78e2021-12-01 14:46:15 -060069
70void UCD90320Monitor::findCompatibleSystemTypes()
71{
72 try
73 {
74 auto subTree = util::getSubTree(bus, "/xyz/openbmc_project/inventory",
75 compatibleInterface, 0);
76
77 auto objectIt = subTree.cbegin();
78 if (objectIt != subTree.cend())
79 {
80 const auto& objPath = objectIt->first;
81
82 // Get the first service name
83 auto serviceIt = objectIt->second.cbegin();
84 if (serviceIt != objectIt->second.cend())
85 {
86 std::string service = serviceIt->first;
87 if (!service.empty())
88 {
89 std::vector<std::string> compatibleSystemTypes;
90
91 // Get compatible system types property value
92 util::getProperty(compatibleInterface,
93 compatibleNamesProperty, objPath, service,
94 bus, compatibleSystemTypes);
95
96 log<level::DEBUG>(
97 fmt::format("Found compatible systems: {}",
98 compatibleSystemTypes)
99 .c_str());
100 // Use compatible systems information to find config file
Jim Wrightd8a86172021-12-08 11:38:26 -0600101 findConfigFile(compatibleSystemTypes);
Jim Wright56ae78e2021-12-01 14:46:15 -0600102 }
103 }
104 }
105 }
106 catch (const std::exception&)
107 {
108 // Compatible system types information is not available.
109 }
110}
111
Jim Wrightd8a86172021-12-08 11:38:26 -0600112void UCD90320Monitor::findConfigFile(
113 const std::vector<std::string>& compatibleSystemTypes)
114{
115 // Expected config file path name:
116 // /usr/share/phosphor-power-sequencer/UCD90320Monitor_<systemType>.json
117
118 // Add possible file names based on compatible system types (if any)
119 for (const std::string& systemType : compatibleSystemTypes)
120 {
121 // Check if file exists
122 std::filesystem::path pathName{
123 "/usr/share/phosphor-power-sequencer/UCD90320Monitor_" +
124 systemType + ".json"};
125 if (std::filesystem::exists(pathName))
126 {
127 log<level::INFO>(
128 fmt::format("Config file path: {}", pathName.string()).c_str());
129 parseConfigFile(pathName);
130 break;
131 }
132 }
133}
134
Patrick Williams7354ce62022-07-22 19:26:56 -0500135void UCD90320Monitor::interfacesAddedHandler(sdbusplus::message_t& msg)
Jim Wright56ae78e2021-12-01 14:46:15 -0600136{
Jim Wrightd8a86172021-12-08 11:38:26 -0600137 // Only continue if message is valid and rails / pins have not already been
138 // found
139 if (!msg || !rails.empty())
Jim Wright56ae78e2021-12-01 14:46:15 -0600140 {
141 return;
142 }
143
144 try
145 {
146 // Read the dbus message
147 sdbusplus::message::object_path objPath;
148 std::map<std::string,
149 std::map<std::string, std::variant<std::vector<std::string>>>>
150 interfaces;
151 msg.read(objPath, interfaces);
152
153 // Find the compatible interface, if present
154 auto itIntf = interfaces.find(compatibleInterface);
155 if (itIntf != interfaces.cend())
156 {
157 // Find the Names property of the compatible interface, if present
158 auto itProp = itIntf->second.find(compatibleNamesProperty);
159 if (itProp != itIntf->second.cend())
160 {
161 // Get value of Names property
162 const auto& propValue = std::get<0>(itProp->second);
163 if (!propValue.empty())
164 {
165 log<level::INFO>(
166 fmt::format(
167 "InterfacesAdded for compatible systems: {}",
168 propValue)
169 .c_str());
170
171 // Use compatible systems information to find config file
Jim Wrightd8a86172021-12-08 11:38:26 -0600172 findConfigFile(propValue);
Jim Wright56ae78e2021-12-01 14:46:15 -0600173 }
174 }
175 }
176 }
177 catch (const std::exception&)
178 {
179 // Error trying to read interfacesAdded message.
180 }
181}
Jim Wright7945dd22021-04-06 16:55:15 -0500182
Jim Wrightc91eed02022-07-22 11:27:06 -0500183bool UCD90320Monitor::isPresent(const std::string& inventoryPath)
184{
185 // Empty path indicates no presence check is needed
186 if (inventoryPath.empty())
187 {
188 return true;
189 }
190
191 // Get presence from D-Bus interface/property
192 try
193 {
194 bool present{true};
195 util::getProperty(INVENTORY_IFACE, PRESENT_PROP, inventoryPath,
196 INVENTORY_MGR_IFACE, bus, present);
197 log<level::INFO>(
198 fmt::format("Presence, path: {}, value: {}", inventoryPath, present)
199 .c_str());
200 return present;
201 }
202 catch (const std::exception& e)
203 {
204 log<level::INFO>(
205 fmt::format("Error getting presence property, path: {}, error: {}",
206 inventoryPath, e.what())
207 .c_str());
208 return false;
209 }
210}
211
Jim Wrightd8a86172021-12-08 11:38:26 -0600212void UCD90320Monitor::parseConfigFile(const std::filesystem::path& pathName)
213{
214 try
215 {
216 std::ifstream file{pathName};
217 json rootElement = json::parse(file);
218
219 // Parse rail information from config file
220 auto railsIterator = rootElement.find("rails");
221 if (railsIterator != rootElement.end())
222 {
223 for (const auto& railElement : *railsIterator)
224 {
Jim Wrightc91eed02022-07-22 11:27:06 -0500225 auto nameIterator = railElement.find("name");
226
227 if (nameIterator != railElement.end())
228 {
229 Rail rail;
230 rail.name = (*nameIterator).get<std::string>();
231
232 // Presence element is optional
233 auto presenceIterator = railElement.find("presence");
234 if (presenceIterator != railElement.end())
235 {
236 rail.presence = (*presenceIterator).get<std::string>();
237 }
238
239 log<level::DEBUG>(
240 fmt::format("Adding rail, name: {}, presence: {}",
241 rail.name, rail.presence)
242 .c_str());
243 rails.emplace_back(std::move(rail));
244 }
245 else
246 {
247 log<level::ERR>(
248 fmt::format(
249 "No name found within rail in configuration file: {}",
250 pathName.string())
251 .c_str());
252 }
Jim Wrightd8a86172021-12-08 11:38:26 -0600253 }
254 }
255 else
256 {
257 log<level::ERR>(
258 fmt::format("No rails found in configuration file: {}",
259 pathName.string())
260 .c_str());
261 }
Jim Wrightc91eed02022-07-22 11:27:06 -0500262 log<level::DEBUG>(
263 fmt::format("Found number of rails: {}", rails.size()).c_str());
Jim Wrightd8a86172021-12-08 11:38:26 -0600264
265 // Parse pin information from config file
266 auto pinsIterator = rootElement.find("pins");
267 if (pinsIterator != rootElement.end())
268 {
269 for (const auto& pinElement : *pinsIterator)
270 {
271 auto nameIterator = pinElement.find("name");
272 auto lineIterator = pinElement.find("line");
273
274 if (nameIterator != pinElement.end() &&
275 lineIterator != pinElement.end())
276 {
Jim Wrightd8a86172021-12-08 11:38:26 -0600277 Pin pin;
Jim Wrightc91eed02022-07-22 11:27:06 -0500278 pin.name = (*nameIterator).get<std::string>();
279 pin.line = (*lineIterator).get<unsigned int>();
280
281 // Presence element is optional
282 auto presenceIterator = pinElement.find("presence");
283 if (presenceIterator != pinElement.end())
284 {
285 pin.presence = (*presenceIterator).get<std::string>();
286 }
287
288 log<level::DEBUG>(
289 fmt::format(
290 "Adding pin, name: {}, line: {}, presence: {}",
291 pin.name, pin.line, pin.presence)
292 .c_str());
Jim Wrightd8a86172021-12-08 11:38:26 -0600293 pins.emplace_back(std::move(pin));
294 }
295 else
296 {
297 log<level::ERR>(
298 fmt::format(
299 "No name or line found within pin in configuration file: {}",
300 pathName.string())
301 .c_str());
302 }
303 }
304 }
305 else
306 {
307 log<level::ERR>(
308 fmt::format("No pins found in configuration file: {}",
309 pathName.string())
310 .c_str());
311 }
312 log<level::DEBUG>(
Jim Wright213ffe92022-06-03 08:54:30 -0500313 fmt::format("Found number of pins: {}", pins.size()).c_str());
Jim Wrightd8a86172021-12-08 11:38:26 -0600314 }
315 catch (const std::exception& e)
316 {
Jim Wrightc91eed02022-07-22 11:27:06 -0500317 log<level::ERR>(
318 fmt::format("Error parsing configuration file, error: {}", e.what())
319 .c_str());
Jim Wrightd8a86172021-12-08 11:38:26 -0600320 }
321}
322
Jim Wright71a14132022-01-28 09:46:46 -0600323void UCD90320Monitor::onFailure(bool timeout,
324 const std::string& powerSupplyError)
325{
Jim Wrightf6f0da92022-02-28 21:08:33 -0600326 std::string message;
Jim Wright71a14132022-01-28 09:46:46 -0600327 std::map<std::string, std::string> additionalData{};
Jim Wright71a14132022-01-28 09:46:46 -0600328
329 try
330 {
Jim Wright3accffe2022-03-10 17:19:42 -0600331 onFailureCheckRails(message, additionalData, powerSupplyError);
Jim Wright3accffe2022-03-10 17:19:42 -0600332 onFailureCheckPins(message, additionalData);
Jim Wright71a14132022-01-28 09:46:46 -0600333 }
Jim Wrightc91eed02022-07-22 11:27:06 -0500334 catch (const std::exception& e)
Jim Wright71a14132022-01-28 09:46:46 -0600335 {
Jim Wrightf6f0da92022-02-28 21:08:33 -0600336 log<level::ERR>(
Jim Wrightc91eed02022-07-22 11:27:06 -0500337 fmt::format("Error when collecting metadata, error: {}", e.what())
Jim Wrightf6f0da92022-02-28 21:08:33 -0600338 .c_str());
Jim Wrightc91eed02022-07-22 11:27:06 -0500339 additionalData.emplace("ERROR", e.what());
Jim Wright71a14132022-01-28 09:46:46 -0600340 }
Jim Wrightf6f0da92022-02-28 21:08:33 -0600341
342 if (message.empty())
343 {
344 // Could not isolate, but we know something failed, so issue a timeout
345 // or generic power good error
346 message = timeout ? "xyz.openbmc_project.Power.Error.PowerOnTimeout"
347 : "xyz.openbmc_project.Power.Error.Shutdown";
348 }
349 logError(message, additionalData);
Jim Wright213ffe92022-06-03 08:54:30 -0500350 if (!timeout)
351 {
352 createBmcDump();
353 }
Jim Wright71a14132022-01-28 09:46:46 -0600354}
355
Jim Wright3accffe2022-03-10 17:19:42 -0600356void UCD90320Monitor::onFailureCheckPins(
357 std::string& message, std::map<std::string, std::string>& additionalData)
358{
359 // Setup a list of all the GPIOs on the chip
360 gpiod::chip chip{"ucd90320", gpiod::chip::OPEN_BY_LABEL};
361 log<level::INFO>(fmt::format("GPIO chip name: {}", chip.name()).c_str());
362 log<level::INFO>(fmt::format("GPIO chip label: {}", chip.label()).c_str());
363 unsigned int numberLines = chip.num_lines();
364 log<level::INFO>(
365 fmt::format("GPIO chip number of lines: {}", numberLines).c_str());
366
367 // Workaround libgpiod bulk line maximum by getting values from individual
368 // lines
369 std::vector<int> values;
Jim Wright213ffe92022-06-03 08:54:30 -0500370 try
Jim Wright3accffe2022-03-10 17:19:42 -0600371 {
Jim Wright213ffe92022-06-03 08:54:30 -0500372 for (unsigned int offset = 0; offset < numberLines; ++offset)
373 {
374 gpiod::line line = chip.get_line(offset);
375 line.request({"phosphor-power-control",
376 gpiod::line_request::DIRECTION_INPUT, 0});
377 values.push_back(line.get_value());
378 line.release();
379 }
380 }
381 catch (const std::exception& e)
382 {
383 log<level::ERR>(
Jim Wrightc91eed02022-07-22 11:27:06 -0500384 fmt::format("Error reading device GPIOs, error: {}", e.what())
Jim Wright213ffe92022-06-03 08:54:30 -0500385 .c_str());
386 additionalData.emplace("GPIO_ERROR", e.what());
Jim Wright3accffe2022-03-10 17:19:42 -0600387 }
388
Jim Wright213ffe92022-06-03 08:54:30 -0500389 // Add GPIO values to additional data, device has 84 GPIO pins so that value
390 // is expected
Jim Wrightc91eed02022-07-22 11:27:06 -0500391 if (numberLines == 84 && values.size() >= 84)
Jim Wright213ffe92022-06-03 08:54:30 -0500392 {
393 log<level::INFO>(fmt::format("MAR01-24 GPIO values: {}",
394 std::span{values}.subspan(0, 24))
395 .c_str());
396 additionalData.emplace(
397 "MAR01_24_GPIO_VALUES",
398 fmt::format("{}", std::span{values}.subspan(0, 24)));
399 log<level::INFO>(fmt::format("EN1-32 GPIO values: {}",
400 std::span{values}.subspan(24, 32))
401 .c_str());
402 additionalData.emplace(
403 "EN1_32_GPIO_VALUES",
404 fmt::format("{}", std::span{values}.subspan(24, 32)));
405 log<level::INFO>(fmt::format("LGP01-16 GPIO values: {}",
406 std::span{values}.subspan(56, 16))
407 .c_str());
408 additionalData.emplace(
409 "LGP01_16_GPIO_VALUES",
410 fmt::format("{}", std::span{values}.subspan(56, 16)));
411 log<level::INFO>(fmt::format("DMON1-8 GPIO values: {}",
412 std::span{values}.subspan(72, 8))
413 .c_str());
414 additionalData.emplace(
415 "DMON1_8_GPIO_VALUES",
416 fmt::format("{}", std::span{values}.subspan(72, 8)));
417 log<level::INFO>(fmt::format("GPIO1-4 GPIO values: {}",
418 std::span{values}.subspan(80, 4))
419 .c_str());
420 additionalData.emplace(
421 "GPIO1_4_GPIO_VALUES",
422 fmt::format("{}", std::span{values}.subspan(80, 4)));
423 }
424 else
425 {
426 log<level::INFO>(fmt::format("GPIO values: {}", values).c_str());
427 additionalData.emplace("GPIO_VALUES", fmt::format("{}", values));
428 }
Jim Wright3accffe2022-03-10 17:19:42 -0600429
430 // Only check GPIOs if no rail fail was found
431 if (message.empty())
432 {
433 for (size_t pin = 0; pin < pins.size(); ++pin)
434 {
435 unsigned int line = pins[pin].line;
436 if (line < values.size())
437 {
438 int value = values[line];
Jim Wrightc91eed02022-07-22 11:27:06 -0500439
440 if ((value == 0) && isPresent(pins[pin].presence))
Jim Wright3accffe2022-03-10 17:19:42 -0600441 {
442 additionalData.emplace("INPUT_NUM",
443 fmt::format("{}", line));
444 additionalData.emplace("INPUT_NAME", pins[pin].name);
Jim Wright3accffe2022-03-10 17:19:42 -0600445 message =
446 "xyz.openbmc_project.Power.Error.PowerSequencerPGOODFault";
447 return;
448 }
449 }
450 }
451 }
452}
453
454void UCD90320Monitor::onFailureCheckRails(
455 std::string& message, std::map<std::string, std::string>& additionalData,
456 const std::string& powerSupplyError)
457{
458 auto statusWord = readStatusWord();
459 additionalData.emplace("STATUS_WORD", fmt::format("{:#06x}", statusWord));
460 try
461 {
462 additionalData.emplace("MFR_STATUS",
Jim Wright213ffe92022-06-03 08:54:30 -0500463 fmt::format("{:#014x}", readMFRStatus()));
Jim Wright3accffe2022-03-10 17:19:42 -0600464 }
Jim Wrightc91eed02022-07-22 11:27:06 -0500465 catch (const std::exception& e)
Jim Wright3accffe2022-03-10 17:19:42 -0600466 {
467 log<level::ERR>(
Jim Wrightc91eed02022-07-22 11:27:06 -0500468 fmt::format("Error when collecting MFR_STATUS, error: {}", e.what())
Jim Wright3accffe2022-03-10 17:19:42 -0600469 .c_str());
Jim Wrightc91eed02022-07-22 11:27:06 -0500470 additionalData.emplace("ERROR", e.what());
Jim Wright3accffe2022-03-10 17:19:42 -0600471 }
472
473 // The status_word register has a summary bit to tell us if each page even
474 // needs to be checked
475 if (statusWord & status_word::VOUT_FAULT)
476 {
Jim Wright286bc702022-05-02 11:01:46 -0500477 constexpr size_t numberPages = 32;
Jim Wright3accffe2022-03-10 17:19:42 -0600478 for (size_t page = 0; page < numberPages; page++)
479 {
480 auto statusVout = pmbusInterface.insertPageNum(STATUS_VOUT, page);
Jim Wright2a054922022-04-13 09:39:27 -0500481 if (pmbusInterface.exists(statusVout, Type::Debug))
Jim Wright3accffe2022-03-10 17:19:42 -0600482 {
Jim Wright2a054922022-04-13 09:39:27 -0500483 uint8_t vout = pmbusInterface.read(statusVout, Type::Debug);
Jim Wright3accffe2022-03-10 17:19:42 -0600484
Jim Wright2a054922022-04-13 09:39:27 -0500485 if (vout)
486 {
Jim Wright213ffe92022-06-03 08:54:30 -0500487 // If any bits are on log them, though some are just
488 // warnings so they won't cause errors
Jim Wright2a054922022-04-13 09:39:27 -0500489 log<level::INFO>(
Jim Wright213ffe92022-06-03 08:54:30 -0500490 fmt::format("{}, value: {:#04x}", statusVout, vout)
Jim Wright2a054922022-04-13 09:39:27 -0500491 .c_str());
Jim Wright3accffe2022-03-10 17:19:42 -0600492
Jim Wright213ffe92022-06-03 08:54:30 -0500493 // Log errors if any non-warning bits on
494 if (vout & ~status_vout::WARNING_MASK)
Jim Wright2a054922022-04-13 09:39:27 -0500495 {
Jim Wright213ffe92022-06-03 08:54:30 -0500496 additionalData.emplace(
497 fmt::format("STATUS{}_VOUT", page),
498 fmt::format("{:#04x}", vout));
Jim Wright2a054922022-04-13 09:39:27 -0500499
Jim Wrightc91eed02022-07-22 11:27:06 -0500500 // Base the callouts on the first present vout failure
501 // found
502 if (message.empty() && (page < rails.size()) &&
503 isPresent(rails[page].presence))
Jim Wright213ffe92022-06-03 08:54:30 -0500504 {
Jim Wrightc91eed02022-07-22 11:27:06 -0500505 additionalData.emplace("RAIL_NAME",
506 rails[page].name);
Jim Wright213ffe92022-06-03 08:54:30 -0500507
508 // Use power supply error if set and 12v rail has
509 // failed, else use voltage error
510 message =
511 ((page == 0) && !powerSupplyError.empty())
512 ? powerSupplyError
513 : "xyz.openbmc_project.Power.Error.PowerSequencerVoltageFault";
514 }
515 }
Jim Wright2a054922022-04-13 09:39:27 -0500516 }
Jim Wright3accffe2022-03-10 17:19:42 -0600517 }
518 }
519 }
Jim Wright056bc192022-06-06 17:46:34 -0500520 // If no vout failure found, but power supply error is set, use power supply
521 // error
522 if (message.empty())
523 {
524 message = powerSupplyError;
525 }
Jim Wright3accffe2022-03-10 17:19:42 -0600526}
527
Jim Wright71a14132022-01-28 09:46:46 -0600528uint16_t UCD90320Monitor::readStatusWord()
529{
530 return pmbusInterface.read(STATUS_WORD, Type::Debug);
531}
532
Jim Wright213ffe92022-06-03 08:54:30 -0500533uint64_t UCD90320Monitor::readMFRStatus()
Jim Wright71a14132022-01-28 09:46:46 -0600534{
535 const std::string mfrStatus = "mfr_status";
536 return pmbusInterface.read(mfrStatus, Type::HwmonDeviceDebug);
537}
538
Jim Wright7945dd22021-04-06 16:55:15 -0500539} // namespace phosphor::power::sequencer