| /** |
| * 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 "types.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 <chrono> |
| #include <fstream> |
| #include <map> |
| #include <span> |
| #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_t& 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(); |
| } |
| |
| 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_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 UCD90320Monitor::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 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) |
| { |
| auto nameIterator = railElement.find("name"); |
| |
| if (nameIterator != railElement.end()) |
| { |
| Rail rail; |
| rail.name = (*nameIterator).get<std::string>(); |
| |
| // Presence element is optional |
| auto presenceIterator = railElement.find("presence"); |
| if (presenceIterator != railElement.end()) |
| { |
| 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) |
| { |
| auto nameIterator = pinElement.find("name"); |
| auto lineIterator = pinElement.find("line"); |
| |
| if (nameIterator != pinElement.end() && |
| lineIterator != pinElement.end()) |
| { |
| 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()) |
| { |
| 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()); |
| } |
| } |
| |
| void UCD90320Monitor::onFailure(bool timeout, |
| const std::string& powerSupplyError) |
| { |
| std::string message; |
| std::map<std::string, std::string> additionalData{}; |
| |
| try |
| { |
| onFailureCheckRails(message, additionalData, powerSupplyError); |
| onFailureCheckPins(message, additionalData); |
| } |
| 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 ? "xyz.openbmc_project.Power.Error.PowerOnTimeout" |
| : "xyz.openbmc_project.Power.Error.Shutdown"; |
| } |
| logError(message, additionalData); |
| if (!timeout) |
| { |
| createBmcDump(); |
| } |
| } |
| |
| void UCD90320Monitor::onFailureCheckPins( |
| std::string& message, std::map<std::string, std::string>& additionalData) |
| { |
| // Setup a list of all the GPIOs on the chip |
| gpiod::chip chip{"ucd90320", 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()); |
| |
| // Workaround libgpiod bulk line maximum by getting values from individual |
| // lines |
| 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()); |
| } |
| |
| // Add GPIO values to additional data, device has 84 GPIO pins so that value |
| // is expected |
| if (numberLines == 84 && values.size() >= 84) |
| { |
| log<level::INFO>(fmt::format("MAR01-24 GPIO values: {}", |
| std::span{values}.subspan(0, 24)) |
| .c_str()); |
| additionalData.emplace( |
| "MAR01_24_GPIO_VALUES", |
| fmt::format("{}", std::span{values}.subspan(0, 24))); |
| log<level::INFO>(fmt::format("EN1-32 GPIO values: {}", |
| std::span{values}.subspan(24, 32)) |
| .c_str()); |
| additionalData.emplace( |
| "EN1_32_GPIO_VALUES", |
| fmt::format("{}", std::span{values}.subspan(24, 32))); |
| log<level::INFO>(fmt::format("LGP01-16 GPIO values: {}", |
| std::span{values}.subspan(56, 16)) |
| .c_str()); |
| additionalData.emplace( |
| "LGP01_16_GPIO_VALUES", |
| fmt::format("{}", std::span{values}.subspan(56, 16))); |
| log<level::INFO>(fmt::format("DMON1-8 GPIO values: {}", |
| std::span{values}.subspan(72, 8)) |
| .c_str()); |
| additionalData.emplace( |
| "DMON1_8_GPIO_VALUES", |
| fmt::format("{}", std::span{values}.subspan(72, 8))); |
| log<level::INFO>(fmt::format("GPIO1-4 GPIO values: {}", |
| std::span{values}.subspan(80, 4)) |
| .c_str()); |
| additionalData.emplace( |
| "GPIO1_4_GPIO_VALUES", |
| fmt::format("{}", std::span{values}.subspan(80, 4))); |
| } |
| else |
| { |
| log<level::INFO>(fmt::format("GPIO values: {}", values).c_str()); |
| additionalData.emplace("GPIO_VALUES", fmt::format("{}", values)); |
| } |
| |
| // 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 UCD90320Monitor::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) |
| { |
| constexpr size_t numberPages = 32; |
| 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; |
| } |
| } |
| |
| uint16_t UCD90320Monitor::readStatusWord() |
| { |
| return pmbusInterface.read(STATUS_WORD, Type::Debug); |
| } |
| |
| uint64_t UCD90320Monitor::readMFRStatus() |
| { |
| const std::string mfrStatus = "mfr_status"; |
| return pmbusInterface.read(mfrStatus, Type::HwmonDeviceDebug); |
| } |
| |
| } // namespace phosphor::power::sequencer |