blob: f36d73492848ae42e72879f47a0274337c212e4f [file] [log] [blame]
/**
* Copyright © 2022 IBM Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ucd90x_monitor.hpp"
#include "types.hpp"
#include "utility.hpp"
#include <fmt/format.h>
#include <fmt/ranges.h>
#include <gpiod.hpp>
#include <nlohmann/json.hpp>
#include <phosphor-logging/log.hpp>
#include <algorithm>
#include <chrono>
#include <exception>
#include <fstream>
namespace phosphor::power::sequencer
{
using json = nlohmann::json;
using namespace pmbus;
using namespace phosphor::logging;
const std::string compatibleInterface =
"xyz.openbmc_project.Configuration.IBMCompatibleSystem";
const std::string compatibleNamesProperty = "Names";
UCD90xMonitor::UCD90xMonitor(sdbusplus::bus_t& bus, std::uint8_t i2cBus,
std::uint16_t i2cAddress,
const std::string& deviceName,
size_t numberPages) :
PowerSequencerMonitor(bus),
deviceName{deviceName},
match{bus,
sdbusplus::bus::match::rules::interfacesAdded() +
sdbusplus::bus::match::rules::sender(
"xyz.openbmc_project.EntityManager"),
std::bind(&UCD90xMonitor::interfacesAddedHandler, this,
std::placeholders::_1)},
numberPages{numberPages},
pmbusInterface{
fmt::format("/sys/bus/i2c/devices/{}-{:04x}", i2cBus, i2cAddress)
.c_str(),
"ucd9000", 0}
{
log<level::DEBUG>(
fmt::format("Device path: {}", pmbusInterface.path().string()).c_str());
log<level::DEBUG>(fmt::format("Hwmon path: {}",
pmbusInterface.getPath(Type::Hwmon).string())
.c_str());
log<level::DEBUG>(fmt::format("Debug path: {}",
pmbusInterface.getPath(Type::Debug).string())
.c_str());
log<level::DEBUG>(
fmt::format("Device debug path: {}",
pmbusInterface.getPath(Type::DeviceDebug).string())
.c_str());
log<level::DEBUG>(
fmt::format("Hwmon device debug path: {}",
pmbusInterface.getPath(Type::HwmonDeviceDebug).string())
.c_str());
// Use the compatible system types information, if already available, to
// load the configuration file
findCompatibleSystemTypes();
}
void UCD90xMonitor::findCompatibleSystemTypes()
{
try
{
auto subTree = util::getSubTree(bus, "/xyz/openbmc_project/inventory",
compatibleInterface, 0);
auto objectIt = subTree.cbegin();
if (objectIt != subTree.cend())
{
const auto& objPath = objectIt->first;
// Get the first service name
auto serviceIt = objectIt->second.cbegin();
if (serviceIt != objectIt->second.cend())
{
std::string service = serviceIt->first;
if (!service.empty())
{
std::vector<std::string> compatibleSystemTypes;
// Get compatible system types property value
util::getProperty(compatibleInterface,
compatibleNamesProperty, objPath, service,
bus, compatibleSystemTypes);
log<level::DEBUG>(
fmt::format("Found compatible systems: {}",
compatibleSystemTypes)
.c_str());
// Use compatible systems information to find config file
findConfigFile(compatibleSystemTypes);
}
}
}
}
catch (const std::exception&)
{
// Compatible system types information is not available.
}
}
void UCD90xMonitor::findConfigFile(
const std::vector<std::string>& compatibleSystemTypes)
{
// Expected config file path name:
// /usr/share/phosphor-power-sequencer/<deviceName>Monitor_<systemType>.json
// Add possible file names based on compatible system types (if any)
for (const std::string& systemType : compatibleSystemTypes)
{
// Check if file exists
std::filesystem::path pathName{"/usr/share/phosphor-power-sequencer/" +
deviceName + "Monitor_" + systemType +
".json"};
log<level::DEBUG>(
fmt::format("Attempting config file path: {}", pathName.string())
.c_str());
if (std::filesystem::exists(pathName))
{
log<level::INFO>(
fmt::format("Config file path: {}", pathName.string()).c_str());
parseConfigFile(pathName);
break;
}
}
}
void UCD90xMonitor::interfacesAddedHandler(sdbusplus::message_t& msg)
{
// Only continue if message is valid and rails / pins have not already been
// found
if (!msg || !rails.empty())
{
return;
}
try
{
// Read the dbus message
sdbusplus::message::object_path objPath;
std::map<std::string,
std::map<std::string, std::variant<std::vector<std::string>>>>
interfaces;
msg.read(objPath, interfaces);
// Find the compatible interface, if present
auto itIntf = interfaces.find(compatibleInterface);
if (itIntf != interfaces.cend())
{
// Find the Names property of the compatible interface, if present
auto itProp = itIntf->second.find(compatibleNamesProperty);
if (itProp != itIntf->second.cend())
{
// Get value of Names property
const auto& propValue = std::get<0>(itProp->second);
if (!propValue.empty())
{
log<level::INFO>(
fmt::format(
"InterfacesAdded for compatible systems: {}",
propValue)
.c_str());
// Use compatible systems information to find config file
findConfigFile(propValue);
}
}
}
}
catch (const std::exception&)
{
// Error trying to read interfacesAdded message.
}
}
bool UCD90xMonitor::isPresent(const std::string& inventoryPath)
{
// Empty path indicates no presence check is needed
if (inventoryPath.empty())
{
return true;
}
// Get presence from D-Bus interface/property
try
{
bool present{true};
util::getProperty(INVENTORY_IFACE, PRESENT_PROP, inventoryPath,
INVENTORY_MGR_IFACE, bus, present);
log<level::INFO>(
fmt::format("Presence, path: {}, value: {}", inventoryPath, present)
.c_str());
return present;
}
catch (const std::exception& e)
{
log<level::INFO>(
fmt::format("Error getting presence property, path: {}, error: {}",
inventoryPath, e.what())
.c_str());
return false;
}
}
void UCD90xMonitor::formatGpioValues(
const std::vector<int>& values, unsigned int /*numberLines*/,
std::map<std::string, std::string>& additionalData) const
{
log<level::INFO>(fmt::format("GPIO values: {}", values).c_str());
additionalData.emplace("GPIO_VALUES", fmt::format("{}", values));
}
void UCD90xMonitor::onFailure(bool timeout, const std::string& powerSupplyError)
{
std::string message;
std::map<std::string, std::string> additionalData{};
try
{
onFailureCheckRails(message, additionalData, powerSupplyError);
log<level::DEBUG>(
fmt::format("After onFailureCheckRails, message: {}", message)
.c_str());
onFailureCheckPins(message, additionalData);
log<level::DEBUG>(
fmt::format("After onFailureCheckPins, message: {}", message)
.c_str());
}
catch (const std::exception& e)
{
log<level::ERR>(
fmt::format("Error when collecting metadata, error: {}", e.what())
.c_str());
additionalData.emplace("ERROR", e.what());
}
if (message.empty())
{
// Could not isolate, but we know something failed, so issue a timeout
// or generic power good error
message = timeout ? powerOnTimeoutError : shutdownError;
}
logError(message, additionalData);
if (!timeout)
{
createBmcDump();
}
}
void UCD90xMonitor::onFailureCheckPins(
std::string& message, std::map<std::string, std::string>& additionalData)
{
// Create a lower case version of device name to use as label in libgpiod
std::string label{deviceName};
std::transform(label.begin(), label.end(), label.begin(), ::tolower);
// Setup a list of all the GPIOs on the chip
gpiod::chip chip{label, gpiod::chip::OPEN_BY_LABEL};
log<level::INFO>(fmt::format("GPIO chip name: {}", chip.name()).c_str());
log<level::INFO>(fmt::format("GPIO chip label: {}", chip.label()).c_str());
unsigned int numberLines = chip.num_lines();
log<level::INFO>(
fmt::format("GPIO chip number of lines: {}", numberLines).c_str());
// Read GPIO values. Work around libgpiod bulk line maximum by getting
// values from individual lines. The libgpiod line offsets are the same as
// the Pin IDs defined in the UCD90xxx PMBus interface documentation. These
// Pin IDs are different from the pin numbers on the chip. For example, on
// the UCD90160, "FPWM1/GPIO5" is Pin ID/line offset 0, but it is pin number
// 17 on the chip.
std::vector<int> values;
try
{
for (unsigned int offset = 0; offset < numberLines; ++offset)
{
gpiod::line line = chip.get_line(offset);
line.request({"phosphor-power-control",
gpiod::line_request::DIRECTION_INPUT, 0});
values.push_back(line.get_value());
line.release();
}
}
catch (const std::exception& e)
{
log<level::ERR>(
fmt::format("Error reading device GPIOs, error: {}", e.what())
.c_str());
additionalData.emplace("GPIO_ERROR", e.what());
}
formatGpioValues(values, numberLines, additionalData);
// Only check GPIOs if no rail fail was found
if (message.empty())
{
for (size_t pin = 0; pin < pins.size(); ++pin)
{
unsigned int line = pins[pin].line;
if (line < values.size())
{
int value = values[line];
if ((value == 0) && isPresent(pins[pin].presence))
{
additionalData.emplace("INPUT_NUM",
fmt::format("{}", line));
additionalData.emplace("INPUT_NAME", pins[pin].name);
message =
"xyz.openbmc_project.Power.Error.PowerSequencerPGOODFault";
return;
}
}
}
}
}
void UCD90xMonitor::onFailureCheckRails(
std::string& message, std::map<std::string, std::string>& additionalData,
const std::string& powerSupplyError)
{
auto statusWord = readStatusWord();
additionalData.emplace("STATUS_WORD", fmt::format("{:#06x}", statusWord));
try
{
additionalData.emplace("MFR_STATUS",
fmt::format("{:#014x}", readMFRStatus()));
}
catch (const std::exception& e)
{
log<level::ERR>(
fmt::format("Error when collecting MFR_STATUS, error: {}", e.what())
.c_str());
additionalData.emplace("ERROR", e.what());
}
// The status_word register has a summary bit to tell us if each page even
// needs to be checked
if (statusWord & status_word::VOUT_FAULT)
{
for (size_t page = 0; page < numberPages; page++)
{
auto statusVout = pmbusInterface.insertPageNum(STATUS_VOUT, page);
if (pmbusInterface.exists(statusVout, Type::Debug))
{
uint8_t vout = pmbusInterface.read(statusVout, Type::Debug);
if (vout)
{
// If any bits are on log them, though some are just
// warnings so they won't cause errors
log<level::INFO>(
fmt::format("{}, value: {:#04x}", statusVout, vout)
.c_str());
// Log errors if any non-warning bits on
if (vout & ~status_vout::WARNING_MASK)
{
additionalData.emplace(
fmt::format("STATUS{}_VOUT", page),
fmt::format("{:#04x}", vout));
// Base the callouts on the first present vout failure
// found
if (message.empty() && (page < rails.size()) &&
isPresent(rails[page].presence))
{
additionalData.emplace("RAIL_NAME",
rails[page].name);
// Use power supply error if set and 12v rail has
// failed, else use voltage error
message =
((page == 0) && !powerSupplyError.empty())
? powerSupplyError
: "xyz.openbmc_project.Power.Error.PowerSequencerVoltageFault";
}
}
}
}
}
}
// If no vout failure found, but power supply error is set, use power supply
// error
if (message.empty())
{
message = powerSupplyError;
}
}
void UCD90xMonitor::parseConfigFile(const std::filesystem::path& pathName)
{
try
{
log<level::DEBUG>(
std::string("Loading configuration file " + pathName.string())
.c_str());
std::ifstream file{pathName};
json rootElement = json::parse(file);
log<level::DEBUG>(fmt::format("Parsed, root element is_object: {}",
rootElement.is_object())
.c_str());
// Parse rail information from config file
auto railsIterator = rootElement.find("rails");
if (railsIterator != rootElement.end())
{
for (const auto& railElement : *railsIterator)
{
log<level::DEBUG>(fmt::format("Rail element is_object: {}",
railElement.is_object())
.c_str());
auto nameIterator = railElement.find("name");
if (nameIterator != railElement.end())
{
log<level::DEBUG>(fmt::format("Name element is_string: {}",
(*nameIterator).is_string())
.c_str());
Rail rail;
rail.name = (*nameIterator).get<std::string>();
// Presence element is optional
auto presenceIterator = railElement.find("presence");
if (presenceIterator != railElement.end())
{
log<level::DEBUG>(
fmt::format("Presence element is_string: {}",
(*presenceIterator).is_string())
.c_str());
rail.presence = (*presenceIterator).get<std::string>();
}
log<level::DEBUG>(
fmt::format("Adding rail, name: {}, presence: {}",
rail.name, rail.presence)
.c_str());
rails.emplace_back(std::move(rail));
}
else
{
log<level::ERR>(
fmt::format(
"No name found within rail in configuration file: {}",
pathName.string())
.c_str());
}
}
}
else
{
log<level::ERR>(
fmt::format("No rails found in configuration file: {}",
pathName.string())
.c_str());
}
log<level::DEBUG>(
fmt::format("Found number of rails: {}", rails.size()).c_str());
// Parse pin information from config file
auto pinsIterator = rootElement.find("pins");
if (pinsIterator != rootElement.end())
{
for (const auto& pinElement : *pinsIterator)
{
log<level::DEBUG>(fmt::format("Pin element is_object: {}",
pinElement.is_object())
.c_str());
auto nameIterator = pinElement.find("name");
auto lineIterator = pinElement.find("line");
if (nameIterator != pinElement.end() &&
lineIterator != pinElement.end())
{
log<level::DEBUG>(fmt::format("Name element is_string: {}",
(*nameIterator).is_string())
.c_str());
log<level::DEBUG>(
fmt::format("Line element is_number_integer: {}",
(*lineIterator).is_number_integer())
.c_str());
Pin pin;
pin.name = (*nameIterator).get<std::string>();
pin.line = (*lineIterator).get<unsigned int>();
// Presence element is optional
auto presenceIterator = pinElement.find("presence");
if (presenceIterator != pinElement.end())
{
log<level::DEBUG>(
fmt::format("Presence element is_string: {}",
(*presenceIterator).is_string())
.c_str());
pin.presence = (*presenceIterator).get<std::string>();
}
log<level::DEBUG>(
fmt::format(
"Adding pin, name: {}, line: {}, presence: {}",
pin.name, pin.line, pin.presence)
.c_str());
pins.emplace_back(std::move(pin));
}
else
{
log<level::ERR>(
fmt::format(
"No name or line found within pin in configuration file: {}",
pathName.string())
.c_str());
}
}
}
else
{
log<level::ERR>(
fmt::format("No pins found in configuration file: {}",
pathName.string())
.c_str());
}
log<level::DEBUG>(
fmt::format("Found number of pins: {}", pins.size()).c_str());
}
catch (const std::exception& e)
{
log<level::ERR>(
fmt::format("Error parsing configuration file, error: {}", e.what())
.c_str());
}
}
uint16_t UCD90xMonitor::readStatusWord()
{
return pmbusInterface.read(STATUS_WORD, Type::Debug);
}
uint64_t UCD90xMonitor::readMFRStatus()
{
const std::string mfrStatus = "mfr_status";
return pmbusInterface.read(mfrStatus, Type::HwmonDeviceDebug);
}
} // namespace phosphor::power::sequencer