pseq: Create PMBusDriverDevice class
Create the PMBusDriverDevice class in the phosphor-power-sequencer
application.
This class is used for power sequencer devices that are bound to a PMBus
device driver. It obtains PMBus information from sysfs/hwmon files
created by the driver.
Tested:
* Performed all of the following tests on Rainier and Everest systems
* Correct sysfs/hwmon files and directories found
* GPIO values obtained successfully
* STATUS_WORD values obtained successfully
* STATUS_VOUT values obtained successfully
* READ_VOUT values obtained successfully
* VOUT_UV_FAULT_LIMIT values obtained successfully
* Mapping built from PMBus PAGE numbers to hwmon file numbers
* Verified error paths and exceptions thrown
Change-Id: I2efd3146fa08d3584857c94c2bbbf691b1e2ad7d
Signed-off-by: Shawn McCarney <shawnmm@us.ibm.com>
diff --git a/phosphor-power-sequencer/src/meson.build b/phosphor-power-sequencer/src/meson.build
index 2d14caf..ee6057e 100644
--- a/phosphor-power-sequencer/src/meson.build
+++ b/phosphor-power-sequencer/src/meson.build
@@ -6,6 +6,7 @@
phosphor_power_sequencer_library = static_library(
'phosphor-power-sequencer',
'config_file_parser.cpp',
+ 'pmbus_driver_device.cpp',
'rail.cpp',
'services.cpp',
'standard_device.cpp',
diff --git a/phosphor-power-sequencer/src/pmbus_driver_device.cpp b/phosphor-power-sequencer/src/pmbus_driver_device.cpp
new file mode 100644
index 0000000..e8c0e04
--- /dev/null
+++ b/phosphor-power-sequencer/src/pmbus_driver_device.cpp
@@ -0,0 +1,253 @@
+/**
+ * Copyright © 2024 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 "pmbus_driver_device.hpp"
+
+#include <ctype.h> // for tolower()
+
+#include <algorithm>
+#include <exception>
+#include <filesystem>
+#include <format>
+#include <regex>
+#include <stdexcept>
+
+namespace phosphor::power::sequencer
+{
+
+using namespace pmbus;
+namespace fs = std::filesystem;
+
+std::vector<int> PMBusDriverDevice::getGPIOValues(Services& services)
+{
+ // Get lower case version of device name to use as chip label
+ std::string label{name};
+ std::transform(label.begin(), label.end(), label.begin(), ::tolower);
+
+ // Read the GPIO values by specifying the chip label
+ std::vector<int> values;
+ try
+ {
+ values = services.getGPIOValues(label);
+ }
+ catch (const std::exception& e)
+ {
+ throw std::runtime_error{std::format(
+ "Unable to read GPIO values from device {} using label {}: {}",
+ name, label, e.what())};
+ }
+ return values;
+}
+
+uint16_t PMBusDriverDevice::getStatusWord(uint8_t page)
+{
+ uint16_t value{0};
+ try
+ {
+ std::string fileName = std::format("status{:d}", page);
+ value = pmbusInterface->read(fileName, Type::Debug);
+ }
+ catch (const std::exception& e)
+ {
+ throw std::runtime_error{std::format(
+ "Unable to read STATUS_WORD for PAGE {:d} of device {}: {}", page,
+ name, e.what())};
+ }
+ return value;
+}
+
+uint8_t PMBusDriverDevice::getStatusVout(uint8_t page)
+{
+ uint8_t value{0};
+ try
+ {
+ std::string fileName = std::format("status{:d}_vout", page);
+ value = pmbusInterface->read(fileName, Type::Debug);
+ }
+ catch (const std::exception& e)
+ {
+ throw std::runtime_error{std::format(
+ "Unable to read STATUS_VOUT for PAGE {:d} of device {}: {}", page,
+ name, e.what())};
+ }
+ return value;
+}
+
+double PMBusDriverDevice::getReadVout(uint8_t page)
+{
+ double volts{0.0};
+ try
+ {
+ unsigned int fileNumber = getFileNumber(page);
+ std::string fileName = std::format("in{}_input", fileNumber);
+ std::string millivoltsStr = pmbusInterface->readString(fileName,
+ Type::Hwmon);
+ unsigned long millivolts = std::stoul(millivoltsStr);
+ volts = millivolts / 1000.0;
+ }
+ catch (const std::exception& e)
+ {
+ throw std::runtime_error{std::format(
+ "Unable to read READ_VOUT for PAGE {:d} of device {}: {}", page,
+ name, e.what())};
+ }
+ return volts;
+}
+
+double PMBusDriverDevice::getVoutUVFaultLimit(uint8_t page)
+{
+ double volts{0.0};
+ try
+ {
+ unsigned int fileNumber = getFileNumber(page);
+ std::string fileName = std::format("in{}_lcrit", fileNumber);
+ std::string millivoltsStr = pmbusInterface->readString(fileName,
+ Type::Hwmon);
+ unsigned long millivolts = std::stoul(millivoltsStr);
+ volts = millivolts / 1000.0;
+ }
+ catch (const std::exception& e)
+ {
+ throw std::runtime_error{std::format(
+ "Unable to read VOUT_UV_FAULT_LIMIT for PAGE {:d} of device {}: {}",
+ page, name, e.what())};
+ }
+ return volts;
+}
+
+unsigned int PMBusDriverDevice::getFileNumber(uint8_t page)
+{
+ if (pageToFileNumber.empty())
+ {
+ buildPageToFileNumberMap();
+ }
+
+ auto it = pageToFileNumber.find(page);
+ if (it == pageToFileNumber.end())
+ {
+ throw std::runtime_error{std::format(
+ "Unable to find hwmon file number for PAGE {:d} of device {}", page,
+ name)};
+ }
+
+ return it->second;
+}
+
+void PMBusDriverDevice::buildPageToFileNumberMap()
+{
+ // Clear any existing mappings
+ pageToFileNumber.clear();
+
+ // Build mappings using voltage label files in hwmon directory
+ try
+ {
+ fs::path hwmonDir = pmbusInterface->getPath(Type::Hwmon);
+ if (fs::is_directory(hwmonDir))
+ {
+ // Loop through all files in hwmon directory
+ std::string fileName;
+ unsigned int fileNumber;
+ std::optional<uint8_t> page;
+ for (const auto& f : fs::directory_iterator{hwmonDir})
+ {
+ // If this is a voltage label file
+ fileName = f.path().filename().string();
+ if (isLabelFile(fileName, fileNumber))
+ {
+ // Read PMBus PAGE number from label file contents
+ page = readPageFromLabelFile(fileName);
+ if (page)
+ {
+ // Add mapping from PAGE number to file number
+ pageToFileNumber.emplace(*page, fileNumber);
+ }
+ }
+ }
+ }
+ }
+ catch (const std::exception& e)
+ {
+ throw std::runtime_error{
+ std::format("Unable to map PMBus PAGE numbers to hwmon file "
+ "numbers for device {}: {}",
+ name, e.what())};
+ }
+}
+
+bool PMBusDriverDevice::isLabelFile(const std::string& fileName,
+ unsigned int& fileNumber)
+{
+ bool isLabel{false};
+ try
+ {
+ // Check if file name has expected pattern for voltage label file
+ std::regex regex{"in(\\d+)_label"};
+ std::smatch results;
+ if (std::regex_match(fileName, results, regex))
+ {
+ // Verify 2 match results: entire match and one sub-match
+ if (results.size() == 2)
+ {
+ // Get sub-match that contains the file number
+ std::string fileNumberStr = results.str(1);
+ fileNumber = std::stoul(fileNumberStr);
+ isLabel = true;
+ }
+ }
+ }
+ catch (...)
+ {
+ // Ignore error. If this file is needed for pgood fault detection, an
+ // error will occur later when the necessary mapping is missing. Avoid
+ // logging unnecessary errors for files that may not be required.
+ }
+ return isLabel;
+}
+
+std::optional<uint8_t>
+ PMBusDriverDevice::readPageFromLabelFile(const std::string& fileName)
+{
+ std::optional<uint8_t> page;
+ try
+ {
+ // Read voltage label file contents
+ std::string contents = pmbusInterface->readString(fileName,
+ Type::Hwmon);
+
+ // Check if file contents match the expected pattern
+ std::regex regex{"vout(\\d+)"};
+ std::smatch results;
+ if (std::regex_match(contents, results, regex))
+ {
+ // Verify 2 match results: entire match and one sub-match
+ if (results.size() == 2)
+ {
+ // Get sub-match that contains the page number + 1
+ std::string pageStr = results.str(1);
+ page = std::stoul(pageStr) - 1;
+ }
+ }
+ }
+ catch (...)
+ {
+ // Ignore error. If this file is needed for pgood fault detection, an
+ // error will occur later when the necessary mapping is missing. Avoid
+ // logging unnecessary errors for files that may not be required.
+ }
+ return page;
+}
+
+} // namespace phosphor::power::sequencer
diff --git a/phosphor-power-sequencer/src/pmbus_driver_device.hpp b/phosphor-power-sequencer/src/pmbus_driver_device.hpp
new file mode 100644
index 0000000..e7e85ac
--- /dev/null
+++ b/phosphor-power-sequencer/src/pmbus_driver_device.hpp
@@ -0,0 +1,258 @@
+/**
+ * Copyright © 2024 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.
+ */
+#pragma once
+
+#include "pmbus.hpp"
+#include "rail.hpp"
+#include "services.hpp"
+#include "standard_device.hpp"
+
+#include <stddef.h> // for size_t
+
+#include <cstdint>
+#include <map>
+#include <memory>
+#include <optional>
+#include <string>
+#include <utility>
+#include <vector>
+
+namespace phosphor::power::sequencer
+{
+
+/**
+ * @class PMBusDriverDevice
+ *
+ * StandardDevice sub-class for power sequencer devices that are bound to a
+ * PMBus device driver.
+ */
+class PMBusDriverDevice : public StandardDevice
+{
+ public:
+ // Specify which compiler-generated methods we want
+ PMBusDriverDevice() = delete;
+ PMBusDriverDevice(const PMBusDriverDevice&) = delete;
+ PMBusDriverDevice(PMBusDriverDevice&&) = delete;
+ PMBusDriverDevice& operator=(const PMBusDriverDevice&) = delete;
+ PMBusDriverDevice& operator=(PMBusDriverDevice&&) = delete;
+ virtual ~PMBusDriverDevice() = default;
+
+ /**
+ * Constructor.
+ *
+ * @param name Device name
+ * @param rails Voltage rails that are enabled and monitored by this device
+ * @param services System services like hardware presence and the journal
+ * @param bus I2C bus for the device
+ * @param address I2C address for the device
+ * @param driverName Device driver name
+ * @param instance Chip instance number
+ */
+ explicit PMBusDriverDevice(const std::string& name,
+ std::vector<std::unique_ptr<Rail>> rails,
+ Services& services, uint8_t bus,
+ uint16_t address,
+ const std::string& driverName = "",
+ size_t instance = 0) :
+ StandardDevice(name, std::move(rails)),
+ bus{bus}, address{address}, driverName{driverName}, instance{instance}
+ {
+ pmbusInterface = services.createPMBus(bus, address, driverName,
+ instance);
+ }
+
+ /**
+ * Returns the I2C bus for the device.
+ *
+ * @return I2C bus
+ */
+ uint8_t getBus() const
+ {
+ return bus;
+ }
+
+ /**
+ * Returns the I2C address for the device.
+ *
+ * @return I2C address
+ */
+ uint16_t getAddress() const
+ {
+ return address;
+ }
+
+ /**
+ * Returns the device driver name.
+ *
+ * @return driver name
+ */
+ const std::string& getDriverName() const
+ {
+ return driverName;
+ }
+
+ /**
+ * Returns the chip instance number.
+ *
+ * @return chip instance
+ */
+ size_t getInstance() const
+ {
+ return instance;
+ }
+
+ /**
+ * Returns interface to the PMBus information that is provided by the device
+ * driver in sysfs.
+ *
+ * @return PMBus interface object
+ */
+ pmbus::PMBusBase& getPMBusInterface()
+ {
+ return *pmbusInterface;
+ }
+
+ /** @copydoc PowerSequencerDevice::getGPIOValues() */
+ virtual std::vector<int> getGPIOValues(Services& services) override;
+
+ /** @copydoc PowerSequencerDevice::getStatusWord() */
+ virtual uint16_t getStatusWord(uint8_t page) override;
+
+ /** @copydoc PowerSequencerDevice::getStatusVout() */
+ virtual uint8_t getStatusVout(uint8_t page) override;
+
+ /** @copydoc PowerSequencerDevice::getReadVout() */
+ virtual double getReadVout(uint8_t page) override;
+
+ /** @copydoc PowerSequencerDevice::getVoutUVFaultLimit() */
+ virtual double getVoutUVFaultLimit(uint8_t page) override;
+
+ /**
+ * Returns map from PMBus PAGE numbers to sysfs hwmon file numbers.
+ *
+ * Throws an exception if an error occurs trying to build the map.
+ *
+ * @return page to file number map
+ */
+ const std::map<uint8_t, unsigned int>& getPageToFileNumberMap()
+ {
+ if (pageToFileNumber.empty())
+ {
+ buildPageToFileNumberMap();
+ }
+ return pageToFileNumber;
+ }
+
+ /**
+ * Returns the hwmon file number that corresponds to the specified PMBus
+ * PAGE number.
+ *
+ * Throws an exception if a file number was not found for the specified PAGE
+ * number.
+ *
+ * @param page PMBus PAGE number
+ * @return hwmon file number
+ */
+ unsigned int getFileNumber(uint8_t page);
+
+ protected:
+ /** @copydoc StandardDevice::prepareForPgoodFaultDetection() */
+ virtual void prepareForPgoodFaultDetection(Services& services) override
+ {
+ // Rebuild PMBus PAGE to hwmon file number map
+ buildPageToFileNumberMap();
+
+ // Call parent class method to do any actions defined there
+ StandardDevice::prepareForPgoodFaultDetection(services);
+ }
+
+ /**
+ * Build mapping from PMBus PAGE numbers to the hwmon file numbers in
+ * sysfs.
+ *
+ * hwmon file names have the format:
+ * <type><number>_<item>
+ *
+ * The <number> is not the PMBus PAGE number. The PMBus PAGE is determined
+ * by reading the contents of the <type><number>_label file.
+ *
+ * If the map is not empty, it is cleared and rebuilt. This is necessary
+ * over time because power devices may have been added or removed.
+ *
+ * Throws an exception if an error occurs trying to build the map.
+ */
+ virtual void buildPageToFileNumberMap();
+
+ /**
+ * Returns whether the specified sysfs hwmon file is a voltage label file.
+ *
+ * If it is a label file, the hwmon file number is obtained from the file
+ * name and returned.
+ *
+ * @param fileName file within the sysfs hwmon directory
+ * @param fileNumber the hwmon file number is stored in this output
+ * parameter if this is a label file
+ * @return true if specified file is a voltage label file, false otherwise
+ */
+ virtual bool isLabelFile(const std::string& fileName,
+ unsigned int& fileNumber);
+
+ /**
+ * Reads the specified voltage label file to obtain the associated PMBus
+ * PAGE number.
+ *
+ * The returned optional variable will have no value if the PMBus PAGE
+ * number could not be obtained due to an error.
+ *
+ * @param fileName voltage label file within the sysfs hwmon directory
+ * @return PMBus page number
+ */
+ virtual std::optional<uint8_t>
+ readPageFromLabelFile(const std::string& fileName);
+
+ /**
+ * I2C bus for the device.
+ */
+ uint8_t bus;
+
+ /**
+ * I2C address for the device.
+ */
+ uint16_t address;
+
+ /**
+ * Device driver name.
+ */
+ std::string driverName;
+
+ /**
+ * Chip instance number.
+ */
+ size_t instance;
+
+ /**
+ * Interface to the PMBus information that is provided by the device driver
+ * in sysfs.
+ */
+ std::unique_ptr<pmbus::PMBusBase> pmbusInterface;
+
+ /**
+ * Map from PMBus PAGE numbers to sysfs hwmon file numbers.
+ */
+ std::map<uint8_t, unsigned int> pageToFileNumber;
+};
+
+} // namespace phosphor::power::sequencer