psu-ng: Add peak input power sensor for some PSs

Some models of the ibm-cffps power supplies support an 'input history'
command that reports 30 second maximum and average input power values.
The code was currently putting those on an 'org.open_power' D-Bus
interface so that the history of those values could be captured in a
single D-Bus call.

Now that there is a real telemetry feature in Redfish that can capture
the history of a single sensor value, we can drop the custom D-Bus
interface and just use the normal Sensor.Value interface that contains
the most recent maximum value from the input history command, The sensor
name will be 'psX_input_power_peak' where X is the PS instance number.

The average input power telemetry will now just be obtained from the
psX_input_power sensor provided by phosphor-hwmon so an equivalent
sensor to the peak isn't needed here.

This commit will add support for putting the new sensor on D-Bus, and a
future one will remove the previous input history support.

Like sensors in other daemons, it will be set to not available when a PS
is removed, and not functional when it has an access problem.  There
will be associations to the parent chassis and to the power supply so it
will show up in Redfish output with the other sensors.

This commit did remove one of the input history testcases, as trying to
get the right sequence of EXPECT_CALLs would get tricky when both the
old and new are running together.

Tested:
- New sensor shows up when PS is present and supports it.
 - All interfaces on the object path are correct.
- Sensor value matches what org.open_power Max property had.
- Works correctly when:
 - PS is missing on startup
 - PS is removed
 - Previously present PS is replaced.

Change-Id: Id9c33aa753c9af32880a0cc874b39c113222568f
Signed-off-by: Matt Spinler <spinler@us.ibm.com>
diff --git a/phosphor-power-supply/power_supply.cpp b/phosphor-power-supply/power_supply.cpp
index f100978..6e28ca8 100644
--- a/phosphor-power-supply/power_supply.cpp
+++ b/phosphor-power-supply/power_supply.cpp
@@ -95,6 +95,7 @@
         updatePresence();
         updateInventory();
         setupInputHistory();
+        setupSensors();
     }
 
     setInputVoltageRating();
@@ -200,6 +201,7 @@
 
         setPresence(bus, invpath, present, shortName);
         setupInputHistory();
+        setupSensors();
         updateInventory();
 
         // Need Functional to already be correct before calling this.
@@ -214,6 +216,10 @@
             // synchronized.
             syncHistoryRequired = true;
         }
+        else
+        {
+            setSensorsNotAvailable();
+        }
     }
 }
 
@@ -670,6 +676,8 @@
                         .c_str());
             }
 
+            monitorSensors();
+
             checkAvailability();
 
             if (inputHistorySupported)
@@ -1119,6 +1127,55 @@
     }
 }
 
+void PowerSupply::setupSensors()
+{
+    setupInputPowerPeakSensor();
+}
+
+void PowerSupply::setupInputPowerPeakSensor()
+{
+    if (peakInputPowerSensor || !present ||
+        (bindPath.string().find(IBMCFFPS_DD_NAME) == std::string::npos))
+    {
+        return;
+    }
+
+    // This PSU has problems with the input_history command
+    if (getMaxPowerOut() == phosphor::pmbus::IBM_CFFPS_1400W)
+    {
+        return;
+    }
+
+    auto sensorPath =
+        fmt::format("/xyz/openbmc_project/sensors/power/ps{}_input_power_peak",
+                    shortName.back());
+
+    peakInputPowerSensor = std::make_unique<PowerSensorObject>(
+        bus, sensorPath.c_str(), PowerSensorObject::action::defer_emit);
+
+    // The others can remain at the defaults.
+    peakInputPowerSensor->functional(true, true);
+    peakInputPowerSensor->available(true, true);
+    peakInputPowerSensor->value(0, true);
+    peakInputPowerSensor->unit(
+        sdbusplus::xyz::openbmc_project::Sensor::server::Value::Unit::Watts,
+        true);
+
+    auto associations = getSensorAssociations();
+    peakInputPowerSensor->associations(associations, true);
+
+    peakInputPowerSensor->emit_object_added();
+}
+
+void PowerSupply::setSensorsNotAvailable()
+{
+    if (peakInputPowerSensor)
+    {
+        peakInputPowerSensor->value(std::numeric_limits<double>::quiet_NaN());
+        peakInputPowerSensor->available(false);
+    }
+}
+
 void PowerSupply::updateHistory()
 {
     if (!recordManager)
@@ -1148,6 +1205,59 @@
     }
 }
 
+void PowerSupply::monitorSensors()
+{
+    monitorPeakInputPowerSensor();
+}
+
+void PowerSupply::monitorPeakInputPowerSensor()
+{
+    if (!peakInputPowerSensor)
+    {
+        return;
+    }
+
+    constexpr size_t recordSize = 5;
+    std::vector<uint8_t> data;
+
+    // Get the peak input power with input history command.
+    // New data only shows up every 30s, but just try to read it every 1s
+    // anyway so we always have the most up to date value.
+    try
+    {
+        data = pmbusIntf->readBinary(INPUT_HISTORY,
+                                     pmbus::Type::HwmonDeviceDebug, recordSize);
+    }
+    catch (const ReadFailure& e)
+    {
+        peakInputPowerSensor->value(std::numeric_limits<double>::quiet_NaN());
+        peakInputPowerSensor->functional(false);
+        throw;
+    }
+
+    if (data.size() != recordSize)
+    {
+        log<level::DEBUG>(
+            fmt::format("Input history command returned {} bytes instead of 5",
+                        data.size())
+                .c_str());
+        peakInputPowerSensor->value(std::numeric_limits<double>::quiet_NaN());
+        peakInputPowerSensor->functional(false);
+        return;
+    }
+
+    // The format is SSAAAAPPPP:
+    //   SS = packet sequence number
+    //   AAAA = average power (linear format, little endian)
+    //   PPPP = peak power (linear format, little endian)
+    auto peak = static_cast<uint16_t>(data[4]) << 8 | data[3];
+    auto peakPower = linearToInteger(peak);
+
+    peakInputPowerSensor->value(peakPower);
+    peakInputPowerSensor->functional(true);
+    peakInputPowerSensor->available(true);
+}
+
 void PowerSupply::getInputVoltage(double& actualInputVoltage,
                                   int& inputVoltage) const
 {
@@ -1265,4 +1375,43 @@
             fmt::format("Failed getProperty error: {}", e.what()).c_str());
     }
 }
+
+double PowerSupply::linearToInteger(uint16_t data)
+{
+    // The exponent is the first 5 bits, followed by 11 bits of mantissa.
+    int8_t exponent = (data & 0xF800) >> 11;
+    int16_t mantissa = (data & 0x07FF);
+
+    // If exponent's MSB on, then it's negative.
+    // Convert from two's complement.
+    if (exponent & 0x10)
+    {
+        exponent = (~exponent) & 0x1F;
+        exponent = (exponent + 1) * -1;
+    }
+
+    // If mantissa's MSB on, then it's negative.
+    // Convert from two's complement.
+    if (mantissa & 0x400)
+    {
+        mantissa = (~mantissa) & 0x07FF;
+        mantissa = (mantissa + 1) * -1;
+    }
+
+    auto value = static_cast<double>(mantissa) * pow(2, exponent);
+    return value;
+}
+
+std::vector<AssociationTuple> PowerSupply::getSensorAssociations()
+{
+    std::vector<AssociationTuple> associations;
+
+    associations.emplace_back("inventory", "sensors", inventoryPath);
+
+    auto chassis = getChassis(bus, inventoryPath);
+    associations.emplace_back("chassis", "all_sensors", std::move(chassis));
+
+    return associations;
+}
+
 } // namespace phosphor::power::psu
diff --git a/phosphor-power-supply/power_supply.hpp b/phosphor-power-supply/power_supply.hpp
index 3690bd8..ec1290e 100644
--- a/phosphor-power-supply/power_supply.hpp
+++ b/phosphor-power-supply/power_supply.hpp
@@ -10,7 +10,10 @@
 
 #include <gpiod.hpp>
 #include <sdbusplus/bus/match.hpp>
+#include <xyz/openbmc_project/Association/Definitions/server.hpp>
 #include <xyz/openbmc_project/Sensor/Value/server.hpp>
+#include <xyz/openbmc_project/State/Decorator/Availability/server.hpp>
+#include <xyz/openbmc_project/State/Decorator/OperationalStatus/server.hpp>
 
 #include <filesystem>
 #include <stdexcept>
@@ -53,8 +56,19 @@
 constexpr auto IBMCFFPS_DD_NAME = "ibm-cffps";
 constexpr auto ACBEL_FSG032_DD_NAME = "acbel-fsg032";
 
+using AvailabilityInterface =
+    sdbusplus::xyz::openbmc_project::State::Decorator::server::Availability;
+using OperationalStatusInterface = sdbusplus::xyz::openbmc_project::State::
+    Decorator::server::OperationalStatus;
+using AssocDefInterface =
+    sdbusplus::xyz::openbmc_project::Association::server::Definitions;
 using SensorInterface = sdbusplus::xyz::openbmc_project::Sensor::server::Value;
 using SensorObject = sdbusplus::server::object_t<SensorInterface>;
+using PowerSensorObject =
+    sdbusplus::server::object_t<SensorInterface, OperationalStatusInterface,
+                                AvailabilityInterface, AssocDefInterface>;
+
+using AssociationTuple = std::tuple<std::string, std::string, std::string>;
 
 /**
  * @class PowerSupply
@@ -545,6 +559,33 @@
      */
     void setInputVoltageRating();
 
+    /**
+     * @brief Returns the peak input power value if there is one,
+     *        otherwise std::nullopt.
+     */
+    std::optional<double> getPeakInputPower() const
+    {
+        std::optional<double> value;
+        if (peakInputPowerSensor)
+        {
+            value = peakInputPowerSensor->value();
+        }
+        return value;
+    }
+
+    /**
+     * @brief Converts a Linear Format power number to an integer
+     *
+     * The PMBus spec describes a 2 byte Linear Format
+     * number that is composed of an exponent and mantissa
+     * in two's complement notation.
+     *
+     * Value = Mantissa * 2**Exponent
+     *
+     * @return double - The converted value
+     */
+    static double linearToInteger(uint16_t data);
+
   private:
     /**
      * @brief Examine STATUS_WORD for CML (communication, memory, logic fault).
@@ -751,6 +792,39 @@
     void getPsuVpdFromDbus(const std::string& keyword, std::string& vpdStr);
 
     /**
+     * @brief Creates the appropriate sensor D-Bus objects.
+     */
+    void setupSensors();
+
+    /**
+     * @brief Monitors sensor values and updates D-Bus.
+     *        Called from analyze().
+     */
+    void monitorSensors();
+
+    /**
+     * @brief Creates the peak input power sensor D-Bus object
+     *        if the PS supports it.
+     */
+    void setupInputPowerPeakSensor();
+
+    /**
+     * @brief Monitors the peak input power sensor
+     */
+    void monitorPeakInputPowerSensor();
+
+    /**
+     * @brief Sets any sensor objects to Available = false on D-Bus.
+     */
+    void setSensorsNotAvailable();
+
+    /**
+     * @brief Returns the associations to create for a sensor on this
+     *        power supply.
+     */
+    std::vector<AssociationTuple> getSensorAssociations();
+
+    /**
      * @brief systemd bus member
      */
     sdbusplus::bus_t& bus;
@@ -1053,6 +1127,11 @@
     std::unique_ptr<SensorObject> inputVoltageRatingIface;
 
     /**
+     * @brief The D-Bus object for the peak input power sensor.
+     */
+    std::unique_ptr<PowerSensorObject> peakInputPowerSensor;
+
+    /**
      * @brief The device driver name
      */
     std::string driverName;
diff --git a/phosphor-power-supply/test/mock.hpp b/phosphor-power-supply/test/mock.hpp
index 6e2705e..41db263 100644
--- a/phosphor-power-supply/test/mock.hpp
+++ b/phosphor-power-supply/test/mock.hpp
@@ -61,6 +61,12 @@
                 (sdbusplus::bus_t & bus, const std::string& invpath,
                  bool addRollup),
                 (const, override));
+
+    std::string getChassis(sdbusplus::bus_t& /*bus*/,
+                           const std::string& /*invpath*/) const override
+    {
+        return "/xyz/openbmc_project/inventory/system/chassis";
+    }
 };
 
 class MockedGPIOInterface : public GPIOInterfaceBase
diff --git a/phosphor-power-supply/test/power_supply_tests.cpp b/phosphor-power-supply/test/power_supply_tests.cpp
index 0417b5a..74fc04c 100644
--- a/phosphor-power-supply/test/power_supply_tests.cpp
+++ b/phosphor-power-supply/test/power_supply_tests.cpp
@@ -16,6 +16,7 @@
 using ::testing::Assign;
 using ::testing::DoAll;
 using ::testing::ElementsAre;
+using ::testing::IsNan;
 using ::testing::NotNull;
 using ::testing::Return;
 using ::testing::StrEq;
@@ -75,6 +76,11 @@
             .Times(1)
             .WillOnce(Return(expectations.statusTempValue));
     }
+
+    // Default max/peak is 213W
+    ON_CALL(mockPMBus, readBinary(INPUT_HISTORY, Type::HwmonDeviceDebug, 5))
+        .WillByDefault(
+            Return(std::vector<uint8_t>{0x01, 0x5c, 0xf3, 0x54, 0xf3}));
 }
 
 class PowerSupplyTests : public ::testing::Test
@@ -262,8 +268,7 @@
     // for INPUT_HISTORY will check max_power_out to see if it is
     // old/unsupported power supply. Indicate good value, supported.
     EXPECT_CALL(mockPMBus, readString(MFR_POUT_MAX, _))
-        .Times(1)
-        .WillOnce(Return("2000"));
+        .WillRepeatedly(Return("2000"));
 
     // STATUS_WORD INPUT fault.
     {
@@ -768,8 +773,7 @@
         // for INPUT_HISTORY will check max_power_out to see if it is
         // old/unsupported power supply. Indicate good value, supported.
         EXPECT_CALL(mockPMBus, readString(MFR_POUT_MAX, _))
-            .Times(1)
-            .WillOnce(Return("2000"));
+            .WillRepeatedly(Return("2000"));
         // If I am calling analyze(), I should probably give it good data.
         // STATUS_WORD 0x0000 is powered on, no faults.
         PMBusExpectations expectations;
@@ -804,8 +808,7 @@
     // for INPUT_HISTORY will check max_power_out to see if it is
     // old/unsupported power supply. Indicate good value, supported.
     EXPECT_CALL(mockPMBus, readString(MFR_POUT_MAX, _))
-        .Times(1)
-        .WillOnce(Return("2000"));
+        .WillRepeatedly(Return("2000"));
     // STATUS_WORD 0x0000 is powered on, no faults.
     PMBusExpectations expectations;
     setPMBusExpectations(mockPMBus, expectations);
@@ -1047,8 +1050,7 @@
         // for INPUT_HISTORY will check max_power_out to see if it is
         // old/unsupported power supply. Indicate good value, supported.
         EXPECT_CALL(mockPMBus, readString(MFR_POUT_MAX, _))
-            .Times(1)
-            .WillOnce(Return("2000"));
+            .WillRepeatedly(Return("2000"));
         // STATUS_WORD 0x0000 is powered on, no faults.
         PMBusExpectations expectations;
         setPMBusExpectations(mockPMBus, expectations);
@@ -1099,8 +1101,7 @@
     // for INPUT_HISTORY will check max_power_out to see if it is
     // old/unsupported power supply. Indicate good value, supported.
     EXPECT_CALL(mockPMBus, readString(MFR_POUT_MAX, _))
-        .Times(1)
-        .WillOnce(Return("2000"));
+        .WillRepeatedly(Return("2000"));
     // Call to analyze things will trigger read of STATUS_WORD and READ_VIN.
     // Default expectations will be on, no faults.
     PMBusExpectations expectations;
@@ -1130,8 +1131,7 @@
     // for INPUT_HISTORY will check max_power_out to see if it is
     // old/unsupported power supply. Indicate good value, supported.
     EXPECT_CALL(mockPMBus, readString(MFR_POUT_MAX, _))
-        .Times(1)
-        .WillOnce(Return("2000"));
+        .WillRepeatedly(Return("2000"));
     // Call to analyze things will trigger read of STATUS_WORD and READ_VIN.
     // Default expectations will be on, no faults.
     PMBusExpectations expectations;
@@ -1190,8 +1190,7 @@
     // for INPUT_HISTORY will check max_power_out to see if it is
     // old/unsupported power supply. Indicate good value, supported.
     EXPECT_CALL(mockPMBus, readString(MFR_POUT_MAX, _))
-        .Times(1)
-        .WillOnce(Return("2000"));
+        .WillRepeatedly(Return("2000"));
     // STATUS_WORD 0x0000 is powered on, no faults.
     PMBusExpectations expectations;
     setPMBusExpectations(mockPMBus, expectations);
@@ -1247,8 +1246,7 @@
     // for INPUT_HISTORY will check max_power_out to see if it is
     // old/unsupported power supply. Indicate good value, supported.
     EXPECT_CALL(mockPMBus, readString(MFR_POUT_MAX, _))
-        .Times(1)
-        .WillOnce(Return("2000"));
+        .WillRepeatedly(Return("2000"));
     // First return STATUS_WORD with no bits on.
     // STATUS_WORD 0x0000 is powered on, no faults.
     PMBusExpectations expectations;
@@ -1298,8 +1296,7 @@
     // for INPUT_HISTORY will check max_power_out to see if it is
     // old/unsupported power supply. Indicate good value, supported.
     EXPECT_CALL(mockPMBus, readString(MFR_POUT_MAX, _))
-        .Times(1)
-        .WillOnce(Return("2000"));
+        .WillRepeatedly(Return("2000"));
 
     // Presence change from missing to present will trigger in1_input read in
     // an attempt to get CLEAR_FAULTS called. Return value ignored.
@@ -1369,8 +1366,7 @@
     // for INPUT_HISTORY will check max_power_out to see if it is
     // old/unsupported power supply. Indicate good value, supported.
     EXPECT_CALL(mockPMBus, readString(MFR_POUT_MAX, _))
-        .Times(1)
-        .WillOnce(Return("2000"));
+        .WillRepeatedly(Return("2000"));
     // STATUS_WORD 0x0000 is powered on, no faults.
     PMBusExpectations expectations;
     setPMBusExpectations(mockPMBus, expectations);
@@ -1420,8 +1416,7 @@
     // for INPUT_HISTORY will check max_power_out to see if it is
     // old/unsupported power supply. Indicate good value, supported.
     EXPECT_CALL(mockPMBus, readString(MFR_POUT_MAX, _))
-        .Times(1)
-        .WillOnce(Return("2000"));
+        .WillRepeatedly(Return("2000"));
     // STATUS_WORD 0x0000 is powered on, no faults.
     PMBusExpectations expectations;
     setPMBusExpectations(mockPMBus, expectations);
@@ -1476,8 +1471,7 @@
     // for INPUT_HISTORY will check max_power_out to see if it is
     // old/unsupported power supply. Indicate good value, supported.
     EXPECT_CALL(mockPMBus, readString(MFR_POUT_MAX, _))
-        .Times(1)
-        .WillOnce(Return("2000"));
+        .WillRepeatedly(Return("2000"));
     // STATUS_WORD 0x0000 is powered on, no faults.
     PMBusExpectations expectations;
     setPMBusExpectations(mockPMBus, expectations);
@@ -1530,8 +1524,7 @@
     // for INPUT_HISTORY will check max_power_out to see if it is
     // old/unsupported power supply. Indicate good value, supported.
     EXPECT_CALL(mockPMBus, readString(MFR_POUT_MAX, _))
-        .Times(1)
-        .WillOnce(Return("2000"));
+        .WillRepeatedly(Return("2000"));
     // STATUS_WORD 0x0000 is powered on, no faults.
     PMBusExpectations expectations;
     setPMBusExpectations(mockPMBus, expectations);
@@ -1586,8 +1579,7 @@
     // for INPUT_HISTORY will check max_power_out to see if it is
     // old/unsupported power supply. Indicate good value, supported.
     EXPECT_CALL(mockPMBus, readString(MFR_POUT_MAX, _))
-        .Times(1)
-        .WillOnce(Return("2000"));
+        .WillRepeatedly(Return("2000"));
     // STATUS_WORD 0x0000 is powered on, no faults.
     PMBusExpectations expectations;
     setPMBusExpectations(mockPMBus, expectations);
@@ -1639,8 +1631,7 @@
     // for INPUT_HISTORY will check max_power_out to see if it is
     // old/unsupported power supply. Indicate good value, supported.
     EXPECT_CALL(mockPMBus, readString(MFR_POUT_MAX, _))
-        .Times(1)
-        .WillOnce(Return("2000"));
+        .WillRepeatedly(Return("2000"));
     // STATUS_WORD 0x0000 is powered on, no faults.
     PMBusExpectations expectations;
     setPMBusExpectations(mockPMBus, expectations);
@@ -1774,8 +1765,7 @@
     // for INPUT_HISTORY will check max_power_out to see if it is
     // old/unsupported power supply. Indicate good value, supported.
     EXPECT_CALL(mockPMBus, readString(MFR_POUT_MAX, _))
-        .Times(1)
-        .WillOnce(Return("2000"));
+        .WillRepeatedly(Return("2000"));
     // STATUS_WORD 0x0000 is powered on, no faults.
     PMBusExpectations expectations;
     setPMBusExpectations(mockPMBus, expectations);
@@ -1864,8 +1854,7 @@
     // for INPUT_HISTORY will check max_power_out to see if it is
     // old/unsupported power supply. Indicate good value, supported.
     EXPECT_CALL(mockPMBus, readString(MFR_POUT_MAX, _))
-        .Times(1)
-        .WillOnce(Return("2000"));
+        .WillRepeatedly(Return("2000"));
     // STATUS_WORD 0x0000 is powered on, no faults.
     PMBusExpectations expectations;
     setPMBusExpectations(mockPMBus, expectations);
@@ -1938,8 +1927,7 @@
     // for INPUT_HISTORY will check max_power_out to see if it is
     // old/unsupported power supply. Indicate good value, supported.
     EXPECT_CALL(mockPMBus, readString(MFR_POUT_MAX, _))
-        .Times(1)
-        .WillOnce(Return("2000"));
+        .WillRepeatedly(Return("2000"));
     // STATUS_WORD 0x0000 is powered on, no faults.
     PMBusExpectations expectations;
     setPMBusExpectations(mockPMBus, expectations);
@@ -1996,183 +1984,113 @@
     EXPECT_EQ(psu.hasPSCS12VFault(), false);
 }
 
-TEST_F(PowerSupplyTests, SetupInputHistory)
+TEST_F(PowerSupplyTests, PeakInputPowerSensor)
 {
     auto bus = sdbusplus::bus::new_default();
     {
         PowerSupply psu{bus,         PSUInventoryPath, 6,        0x6f,
                         "ibm-cffps", PSUGPIOLineName,  isPowerOn};
-        // Defaults to not present due to constructor and mock ordering.
-        psu.setupInputHistory();
-        EXPECT_EQ(psu.hasInputHistory(), false);
+        EXPECT_EQ(psu.getPeakInputPower(), std::nullopt);
+
         MockedGPIOInterface* mockPresenceGPIO =
             static_cast<MockedGPIOInterface*>(psu.getPresenceGPIO());
         MockedPMBus& mockPMBus = static_cast<MockedPMBus&>(psu.getPMBus());
-        // Always return 1 to indicate present.
         EXPECT_CALL(*mockPresenceGPIO, read()).WillRepeatedly(Return(1));
+
         setMissingToPresentExpects(mockPMBus, mockedUtil);
         PMBusExpectations expectations;
         setPMBusExpectations(mockPMBus, expectations);
-        // After reading STATUS_WORD, etc., there will be a READ_VIN check.
+
         EXPECT_CALL(mockPMBus, readString(READ_VIN, _))
             .Times(1)
             .WillOnce(Return("206000"));
-        // Missing/present will trigger attempt to setup INPUT_HISTORY. Setup
-        // for INPUT_HISTORY will check max_power_out to see if it is
-        // old/unsupported power supply. Indicate good value, supported.
-        /// Also called when I redo setupInputHistory().
         EXPECT_CALL(mockPMBus, readString(MFR_POUT_MAX, _))
-            .Times(2)
             .WillRepeatedly(Return("2000"));
-        // Call to analyze() and above expectations to get missing/present and
-        // good status.
+
         psu.analyze();
-        psu.setupInputHistory();
-        EXPECT_EQ(psu.hasInputHistory(), true);
+        EXPECT_EQ(psu.getPeakInputPower().value_or(0), 213);
     }
+
+    // Test that there is no peak power sensor on 1400W PSs
     {
-        // Workaround - Disable INPUT_HISTORY collection if 1400W
         PowerSupply psu{bus,         PSUInventoryPath, 3,        0x68,
                         "ibm-cffps", PSUGPIOLineName,  isPowerOn};
-        // Defaults to not present due to constructor and mock ordering.
-        psu.setupInputHistory();
-        EXPECT_EQ(psu.hasInputHistory(), false);
         MockedGPIOInterface* mockPresenceGPIO =
             static_cast<MockedGPIOInterface*>(psu.getPresenceGPIO());
         MockedPMBus& mockPMBus = static_cast<MockedPMBus&>(psu.getPMBus());
-        // Always return 1 to indicate present.
+
         EXPECT_CALL(*mockPresenceGPIO, read()).WillRepeatedly(Return(1));
+
         setMissingToPresentExpects(mockPMBus, mockedUtil);
-        // Missing/present will trigger attempt to setup INPUT_HISTORY. Setup
-        // for INPUT_HISTORY will check max_power_out to see if it is
-        // old/unsupported power supply. Indicate 1400W IBM value, unsupported.
+
         EXPECT_CALL(mockPMBus, readString(MFR_POUT_MAX, _))
-            .Times(2)
             .WillRepeatedly(Return("30725"));
+
         PMBusExpectations expectations;
         setPMBusExpectations(mockPMBus, expectations);
-        // After reading STATUS_WORD, etc., there will be a READ_VIN check.
+
         EXPECT_CALL(mockPMBus, readString(READ_VIN, _))
-            .Times(1)
-            .WillOnce(Return("206000"));
-        // Call to analyze() and above expectations to get missing/present and
-        // good status.
+            .WillRepeatedly(Return("206000"));
         psu.analyze();
-        psu.setupInputHistory();
-        // After updating to present, and retrying setup, expect ibm-cffps with
-        // 1400W to still not support INPUT_HISTORY.
-        EXPECT_EQ(psu.hasInputHistory(), false);
+
+        EXPECT_EQ(psu.getPeakInputPower(), std::nullopt);
     }
+
+    // Test that IPSPS power supplies don't have peak power
     {
         PowerSupply psu{bus,      PSUInventoryPath, 11,
                         0x58,     "inspur-ipsps",   PSUGPIOLineName,
                         isPowerOn};
-        // Defaults to not present due to constructor and mock ordering.
-        psu.setupInputHistory();
-        EXPECT_EQ(psu.hasInputHistory(), false);
+
         MockedGPIOInterface* mockPresenceGPIO =
             static_cast<MockedGPIOInterface*>(psu.getPresenceGPIO());
+
         MockedPMBus& mockPMBus = static_cast<MockedPMBus&>(psu.getPMBus());
-        // Always return 1 to indicate present.
+
         EXPECT_CALL(*mockPresenceGPIO, read()).WillRepeatedly(Return(1));
+
         setMissingToPresentExpects(mockPMBus, mockedUtil);
         PMBusExpectations expectations;
         setPMBusExpectations(mockPMBus, expectations);
-        // After reading STATUS_WORD, etc., there will be a READ_VIN check.
+
+        EXPECT_CALL(mockPMBus, readString(READ_VIN, _))
+            .WillRepeatedly(Return("206000"));
+
+        psu.analyze();
+
+        EXPECT_EQ(psu.getPeakInputPower(), std::nullopt);
+    }
+
+    // Test that a bad response from the input_history command leads
+    // to an NaN value.
+    {
+        PowerSupply psu{bus,         PSUInventoryPath, 6,        0x6f,
+                        "ibm-cffps", PSUGPIOLineName,  isPowerOn};
+
+        MockedGPIOInterface* mockPresenceGPIO =
+            static_cast<MockedGPIOInterface*>(psu.getPresenceGPIO());
+        MockedPMBus& mockPMBus = static_cast<MockedPMBus&>(psu.getPMBus());
+
+        EXPECT_CALL(*mockPresenceGPIO, read()).WillRepeatedly(Return(1));
+
+        setMissingToPresentExpects(mockPMBus, mockedUtil);
+        PMBusExpectations expectations;
+        setPMBusExpectations(mockPMBus, expectations);
+
         EXPECT_CALL(mockPMBus, readString(READ_VIN, _))
             .Times(1)
             .WillOnce(Return("206000"));
-        // Call to analyze() and above expectations to get missing/present and
-        // good status.
-        psu.analyze();
-        psu.setupInputHistory();
-        // After updating to present, and retrying setup, expect inspur-ipsps to
-        // still not support INPUT_HISTORY.
-        EXPECT_EQ(psu.hasInputHistory(), false);
-    }
-}
+        EXPECT_CALL(mockPMBus, readString(MFR_POUT_MAX, _))
+            .WillRepeatedly(Return("2000"));
 
-TEST_F(PowerSupplyTests, UpdateHistory)
-{
-    auto bus = sdbusplus::bus::new_default();
-    PowerSupply psu{bus,         PSUInventoryPath, 7,        0x6e,
-                    "ibm-cffps", PSUGPIOLineName,  isPowerOn};
-    EXPECT_EQ(psu.hasInputHistory(), false);
-    EXPECT_EQ(psu.getNumInputHistoryRecords(), 0);
-    MockedGPIOInterface* mockPresenceGPIO =
-        static_cast<MockedGPIOInterface*>(psu.getPresenceGPIO());
-    // Always return 1 to indicate present.
-    EXPECT_CALL(*mockPresenceGPIO, read()).WillRepeatedly(Return(1));
-    MockedPMBus& mockPMBus = static_cast<MockedPMBus&>(psu.getPMBus());
-    setMissingToPresentExpects(mockPMBus, mockedUtil);
-    // Missing/present will trigger attempt to setup INPUT_HISTORY. Setup
-    // for INPUT_HISTORY will check max_power_out to see if it is
-    // old/unsupported power supply. Indicate good value, supported.
-    EXPECT_CALL(mockPMBus, readString(MFR_POUT_MAX, _))
-        .Times(1)
-        .WillOnce(Return("2000"));
-    PMBusExpectations expectations;
-    setPMBusExpectations(mockPMBus, expectations);
-    EXPECT_CALL(mockPMBus, readString(READ_VIN, _))
-        .Times(6)
-        .WillRepeatedly(Return("205000"));
-    EXPECT_CALL(mockedUtil, setAvailable(_, _, true));
-    // First read after missing/present will have no data.
-    std::vector<uint8_t> emptyHistory{};
-    // Second read, after about 30 seconds, should have a record. 5-bytes.
-    // Sequence Number: 0x00, Average: 0x50 0xf3 (212), Maximum: 0x54 0xf3 (213)
-    std::vector<uint8_t> firstHistory{0x00, 0x50, 0xf3, 0x54, 0xf3};
-    // Third read, after about 60 seconds, should have two records, 10-bytes,
-    // but only reading 5 bytes, so make sure new/next sequence number
-    std::vector<uint8_t> secondHistory{0x01, 0x54, 0xf3, 0x58, 0xf3};
-    // Fourth read, 3rd sequence number (0x02).
-    std::vector<uint8_t> thirdHistory{0x02, 0x54, 0xf3, 0x58, 0xf3};
-    // Fifth read, out of sequence, clear and insert this one?
-    std::vector<uint8_t> outseqHistory{0xff, 0x5c, 0xf3, 0x60, 0xf3};
-    EXPECT_CALL(
-        mockPMBus,
-        readBinary(INPUT_HISTORY, Type::HwmonDeviceDebug,
-                   phosphor::power::history::RecordManager::RAW_RECORD_SIZE))
-        .Times(6)
-        .WillOnce(Return(emptyHistory))
-        .WillOnce(Return(firstHistory))
-        .WillOnce(Return(secondHistory))
-        .WillOnce(Return(thirdHistory))
-        .WillOnce(Return(outseqHistory))
-        .WillOnce(Return(emptyHistory));
-    // Calling analyze will update the presence, which will setup the input
-    // history if the power supply went from missing to present.
-    psu.analyze();
-    // The ibm-cffps power supply should support input history
-    EXPECT_EQ(psu.hasInputHistory(), true);
-    // Usually should have empty buffer right after missing to present.
-    // Faked that out above with mocked readBinary with emptyHistory data.
-    EXPECT_EQ(psu.getNumInputHistoryRecords(), 0);
-    // Second run through...
-    setPMBusExpectations(mockPMBus, expectations);
-    psu.analyze();
-    EXPECT_EQ(psu.hasInputHistory(), true);
-    EXPECT_EQ(psu.getNumInputHistoryRecords(), 1);
-    // Third run through
-    setPMBusExpectations(mockPMBus, expectations);
-    psu.analyze();
-    EXPECT_EQ(psu.hasInputHistory(), true);
-    EXPECT_EQ(psu.getNumInputHistoryRecords(), 2);
-    // Fourth run through. Up to 3 records now?
-    setPMBusExpectations(mockPMBus, expectations);
-    psu.analyze();
-    EXPECT_EQ(psu.hasInputHistory(), true);
-    EXPECT_EQ(psu.getNumInputHistoryRecords(), 3);
-    // Out of sequencer, reset, insert new one.
-    setPMBusExpectations(mockPMBus, expectations);
-    psu.analyze();
-    EXPECT_EQ(psu.hasInputHistory(), true);
-    EXPECT_EQ(psu.getNumInputHistoryRecords(), 1);
-    // Empty one after last one good. Reset/clear.
-    setPMBusExpectations(mockPMBus, expectations);
-    psu.analyze();
-    EXPECT_EQ(psu.hasInputHistory(), true);
-    EXPECT_EQ(psu.getNumInputHistoryRecords(), 0);
+        // Don't return the full 5 bytes.
+        EXPECT_CALL(mockPMBus,
+                    readBinary(INPUT_HISTORY, Type::HwmonDeviceDebug, 5))
+            .WillRepeatedly(Return(std::vector<uint8_t>{0x01, 0x5c}));
+
+        psu.analyze();
+        EXPECT_THAT(psu.getPeakInputPower().value_or(0), IsNan());
+    }
 }
 
 TEST_F(PowerSupplyTests, IsSyncHistoryRequired)
@@ -2192,8 +2110,7 @@
     // for INPUT_HISTORY will check max_power_out to see if it is
     // old/unsupported power supply. Indicate good value, supported.
     EXPECT_CALL(mockPMBus, readString(MFR_POUT_MAX, _))
-        .Times(1)
-        .WillOnce(Return("2000"));
+        .WillRepeatedly(Return("2000"));
     PMBusExpectations expectations;
     setPMBusExpectations(mockPMBus, expectations);
     EXPECT_CALL(mockPMBus, readString(READ_VIN, _))
@@ -2208,3 +2125,43 @@
     psu.clearSyncHistoryRequired();
     EXPECT_EQ(psu.isSyncHistoryRequired(), false);
 }
+
+TEST_F(PowerSupplyTests, TestLinearConversions)
+{
+    // Mantissa > 0, exponent = 0
+    EXPECT_EQ(0, PowerSupply::linearToInteger(0));
+    EXPECT_EQ(1, PowerSupply::linearToInteger(1));
+    EXPECT_EQ(38, PowerSupply::linearToInteger(0x26));
+    EXPECT_EQ(1023, PowerSupply::linearToInteger(0x3FF));
+
+    // Mantissa < 0, exponent = 0
+    EXPECT_EQ(-1, PowerSupply::linearToInteger(0x7FF));
+    EXPECT_EQ(-20, PowerSupply::linearToInteger(0x7EC));
+    EXPECT_EQ(-769, PowerSupply::linearToInteger(0x4FF));
+    EXPECT_EQ(-989, PowerSupply::linearToInteger(0x423));
+    EXPECT_EQ(-1024, PowerSupply::linearToInteger(0x400));
+
+    // Mantissa >= 0, exponent > 0
+    // M = 1, E = 2
+    EXPECT_EQ(4, PowerSupply::linearToInteger(0x1001));
+
+    // M = 1000, E = 10
+    EXPECT_EQ(1024000, PowerSupply::linearToInteger(0x53E8));
+
+    // M = 10, E = 15
+    EXPECT_EQ(327680, PowerSupply::linearToInteger(0x780A));
+
+    // Mantissa >= 0, exponent < 0
+    // M = 0, E = -1
+    EXPECT_EQ(0, PowerSupply::linearToInteger(0xF800));
+
+    // M = 100, E = -2
+    EXPECT_EQ(25, PowerSupply::linearToInteger(0xF064));
+
+    // Mantissa < 0, exponent < 0
+    // M = -100, E = -1
+    EXPECT_EQ(-50, PowerSupply::linearToInteger(0xFF9C));
+
+    // M = -1024, E = -7
+    EXPECT_EQ(-8, PowerSupply::linearToInteger(0xCC00));
+}
diff --git a/phosphor-power-supply/util.hpp b/phosphor-power-supply/util.hpp
index b2b27f5..3302d8d 100644
--- a/phosphor-power-supply/util.hpp
+++ b/phosphor-power-supply/util.hpp
@@ -196,7 +196,7 @@
     }
 
     std::string getChassis(sdbusplus::bus_t& bus,
-                           const std::string& invpath) const
+                           const std::string& invpath) const override
     {
         sdbusplus::message::object_path assocPath = invpath + "/powering";
         sdbusplus::message::object_path basePath{"/"};
diff --git a/phosphor-power-supply/util_base.hpp b/phosphor-power-supply/util_base.hpp
index 5d38a51..b9d5e2e 100644
--- a/phosphor-power-supply/util_base.hpp
+++ b/phosphor-power-supply/util_base.hpp
@@ -31,6 +31,9 @@
     virtual void handleChassisHealthRollup(sdbusplus::bus_t& bus,
                                            const std::string& invpath,
                                            bool addRollup) const = 0;
+
+    virtual std::string getChassis(sdbusplus::bus_t& /*bus*/,
+                                   const std::string& /*invpath*/) const = 0;
 };
 
 const UtilBase& getUtils();
@@ -59,6 +62,11 @@
     getUtils().handleChassisHealthRollup(bus, invpath, addRollup);
 }
 
+inline std::string getChassis(sdbusplus::bus_t& bus, const std::string& invpath)
+{
+    return getUtils().getChassis(bus, invpath);
+}
+
 class GPIOInterfaceBase
 {
   public: