blob: f2bb0575f7a04721fd3df20a6932155ee00990b3 [file] [log] [blame]
/**
* Copyright © 2021 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 "ucd90320_monitor.hpp"
#include "utility.hpp"
#include <fmt/format.h>
#include <fmt/ranges.h>
#include <nlohmann/json.hpp>
#include <phosphor-logging/log.hpp>
#include <sdbusplus/bus.hpp>
#include <xyz/openbmc_project/Common/Device/error.hpp>
#include <fstream>
#include <map>
#include <string>
namespace phosphor::power::sequencer
{
using json = nlohmann::json;
using namespace pmbus;
using namespace phosphor::logging;
using namespace phosphor::power;
const std::string compatibleInterface =
"xyz.openbmc_project.Configuration.IBMCompatibleSystem";
const std::string compatibleNamesProperty = "Names";
namespace device_error = sdbusplus::xyz::openbmc_project::Common::Device::Error;
UCD90320Monitor::UCD90320Monitor(sdbusplus::bus::bus& bus, std::uint8_t i2cBus,
std::uint16_t i2cAddress) :
PowerSequencerMonitor(bus),
match{bus,
sdbusplus::bus::match::rules::interfacesAdded() +
sdbusplus::bus::match::rules::sender(
"xyz.openbmc_project.EntityManager"),
std::bind(&UCD90320Monitor::interfacesAddedHandler, this,
std::placeholders::_1)},
pmbusInterface{
fmt::format("/sys/bus/i2c/devices/{}-{:04x}", i2cBus, i2cAddress)
.c_str(),
"ucd9000", 0}
{
// Use the compatible system types information, if already available, to
// load the configuration file
findCompatibleSystemTypes();
}
bool UCD90320Monitor::checkPGOODFaults(
std::map<std::string, std::string>& additionalData)
{
// Check only the GPIs configured on this system.
std::vector<int> values = lines.get_values();
bool errorCreated = false;
for (size_t pin = 0; pin < pins.size(); ++pin)
{
if (pin < values.size() && !values[pin])
{
try
{
additionalData.emplace(
"STATUS_WORD", fmt::format("{:#04x}", readStatusWord()));
additionalData.emplace("MFR_STATUS",
fmt::format("{:#04x}", readMFRStatus()));
}
catch (device_error::ReadFailure& e)
{
log<level::ERR>("ReadFailure when collecting metadata");
}
additionalData.emplace("INPUT_NUM",
fmt::format("{}", pins[pin].line));
additionalData.emplace("INPUT_NAME", pins[pin].name);
additionalData.emplace("INPUT_STATUS",
fmt::format("{}", values[pin]));
logError("xyz.openbmc_project.Power.Error.PowerSequencerPGOODFault",
additionalData);
errorCreated = true;
break;
}
}
return errorCreated;
}
bool UCD90320Monitor::checkVOUTFaults(
std::map<std::string, std::string>& additionalData)
{
// The status_word register has a summary bit to tell us
// if each page even needs to be checked
auto statusWord = readStatusWord();
if (!(statusWord & status_word::VOUT_FAULT))
{
return false;
}
constexpr size_t numberPages = 24;
bool errorCreated = false;
for (size_t page = 0; page < numberPages; page++)
{
auto statusVout = pmbusInterface.insertPageNum(STATUS_VOUT, page);
uint8_t vout = pmbusInterface.read(statusVout, Type::Debug);
// If any bits are on log them, though some are just
// warnings so they won't cause errors
if (vout)
{
log<level::INFO>("A voltage rail has bits on in STATUS_VOUT",
entry("STATUS_VOUT=0x%X", vout),
entry("PAGE=%d", page));
}
// Log errors if any non-warning bits on
if (vout & ~status_vout::WARNING_MASK)
{
auto railName = rails[page];
additionalData.emplace("STATUS_WORD",
fmt::format("{:#04x}", statusWord));
additionalData.emplace("STATUS_VOUT", fmt::format("{:#02x}", vout));
try
{
additionalData.emplace("MFR_STATUS",
fmt::format("{:#04x}", readMFRStatus()));
}
catch (device_error::ReadFailure& e)
{
log<level::ERR>("ReadFailure when collecting MFR_STATUS");
}
additionalData.emplace("RAIL", fmt::format("{}", page));
additionalData.emplace("RAIL_NAME", railName);
logError(
"xyz.openbmc_project.Power.Error.PowerSequencerVoltageFault",
additionalData);
errorCreated = true;
break;
}
}
return errorCreated;
}
void UCD90320Monitor::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 UCD90320Monitor::findConfigFile(
const std::vector<std::string>& compatibleSystemTypes)
{
// Expected config file path name:
// /usr/share/phosphor-power-sequencer/UCD90320Monitor_<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/UCD90320Monitor_" +
systemType + ".json"};
if (std::filesystem::exists(pathName))
{
log<level::INFO>(
fmt::format("Config file path: {}", pathName.string()).c_str());
parseConfigFile(pathName);
break;
}
}
}
void UCD90320Monitor::interfacesAddedHandler(sdbusplus::message::message& 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.
}
}
void UCD90320Monitor::parseConfigFile(const std::filesystem::path& pathName)
{
try
{
std::ifstream file{pathName};
json rootElement = json::parse(file);
// Parse rail information from config file
auto railsIterator = rootElement.find("rails");
if (railsIterator != rootElement.end())
{
for (const auto& railElement : *railsIterator)
{
std::string rail = railElement.get<std::string>();
rails.emplace_back(std::move(rail));
}
}
else
{
log<level::ERR>(
fmt::format("No rails found in configuration file: {}",
pathName.string())
.c_str());
}
log<level::DEBUG>(fmt::format("Found rails: {}", rails).c_str());
// List of line offsets for libgpiod
std::vector<unsigned int> offsets;
// Parse pin information from config file
auto pinsIterator = rootElement.find("pins");
if (pinsIterator != rootElement.end())
{
for (const auto& pinElement : *pinsIterator)
{
auto nameIterator = pinElement.find("name");
auto lineIterator = pinElement.find("line");
if (nameIterator != pinElement.end() &&
lineIterator != pinElement.end())
{
std::string name = (*nameIterator).get<std::string>();
unsigned int line = (*lineIterator).get<unsigned int>();
Pin pin;
pin.name = name;
pin.line = line;
pins.emplace_back(std::move(pin));
offsets.emplace_back(line);
}
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: {}", rails.size()).c_str());
setUpGpio(offsets);
}
catch (const std::exception& e)
{
// Log error message in journal
log<level::ERR>(std::string("Exception parsing configuration file: " +
std::string(e.what()))
.c_str());
}
}
void UCD90320Monitor::onFailure(bool timeout,
const std::string& powerSupplyError)
{
std::map<std::string, std::string> additionalData{};
if (!powerSupplyError.empty())
{
logError(powerSupplyError, additionalData);
return;
}
try
{
bool voutError = checkVOUTFaults(additionalData);
bool pgoodError = checkPGOODFaults(additionalData);
// Not a voltage or PGOOD fault, but we know something
// failed so still create an error log.
if (!voutError && !pgoodError)
{
// Default to generic pgood error
logError("xyz.openbmc_project.Power.Error.Shutdown",
additionalData);
}
}
catch (device_error::ReadFailure& e)
{
log<level::ERR>("ReadFailure when collecting metadata");
if (timeout)
{
// Default to timeout error
logError("xyz.openbmc_project.Power.Error.PowerOnTimeout",
additionalData);
}
else
{
// Default to generic pgood error
logError("xyz.openbmc_project.Power.Error.Shutdown",
additionalData);
}
}
}
uint16_t UCD90320Monitor::readStatusWord()
{
return pmbusInterface.read(STATUS_WORD, Type::Debug);
}
uint32_t UCD90320Monitor::readMFRStatus()
{
const std::string mfrStatus = "mfr_status";
return pmbusInterface.read(mfrStatus, Type::HwmonDeviceDebug);
}
void UCD90320Monitor::setUpGpio(const std::vector<unsigned int>& offsets)
{
gpiod::chip chip{"ucd90320", gpiod::chip::OPEN_BY_LABEL};
lines = chip.get_lines(offsets);
lines.request(
{"phosphor-power-control", gpiod::line_request::DIRECTION_INPUT, 0});
}
} // namespace phosphor::power::sequencer