| /** |
| * 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 |