psutils: Add necessary firmware update functions

doUpdate: Adds the main logic for orchestrating the PSU firmware update
process, including ISP setup, firmware download, and verification.

performI2cWriteReadWithRetries: Introduces functionality to perform I2C
write-read operations with configurable retries, and error handling for
robustness.

performI2cReadWrite: Provides the implementation for direct I2C
write-read operations with a customizable delay to ensure timing
requirements are met.

downloadPsuFirmware: Implements the functionality to read and process
PSU firmware in blocks, ensuring data integrity during transfer.

verifyDownloadFwStatus: Adds a mechanism to validate the success of
firmware downloads by checking the PSU's checksum status register.

getClassInstance: Add logic to dynamically instantiate updater class
based on PSU model.

Test:
 Placed latest firmware and MANIFEST files in "/usr/share/obmc/51E9"
 then retrieved the current PSU FW level. Ran "psutils" on the command
 line as follow:
 /usr/bin/psutils --update
  /xyz/openbmc_project/inventory/system/chassis/motherboard/powersupply1
  /usr/share/obmc/51E9

 Retrieved the FW level and verified the PSU FW was updated as
 expected.

    Signed-off-by: Faisal Awada <faisal@us.ibm.com>
Change-Id: I65d0c015eab0322110e85b954a38590332aaa67a
Signed-off-by: Faisal Awada <faisal@us.ibm.com>
diff --git a/tools/power-utils/aei_updater.cpp b/tools/power-utils/aei_updater.cpp
index 1e8cc96..8bddea4 100644
--- a/tools/power-utils/aei_updater.cpp
+++ b/tools/power-utils/aei_updater.cpp
@@ -30,28 +30,22 @@
 namespace aeiUpdater
 {
 
-// Suppress clang-tidy errors for unused variables that are intended
-// for future use
-#ifdef __clang__
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wunused-variable"
-#endif
-
 constexpr uint8_t MAX_RETRIES = 0x02;    // Constants for retry limits
 
 constexpr int ISP_STATUS_DELAY = 1200;   // Delay for ISP status check (1.2s)
 constexpr int MEM_WRITE_DELAY = 5000;    // Memory write delay (5s)
-constexpr int MEM_STRETCH_DELAY = 10;    // Delay between writes (10ms)
+constexpr int MEM_STRETCH_DELAY = 1;     // Delay between writes (1ms)
 constexpr int MEM_COMPLETE_DELAY = 2000; // Delay before completion (2s)
 constexpr int REBOOT_DELAY = 8000;       // Delay for reboot (8s)
 
-constexpr uint8_t I2C_SMBUS_BLOCK_MAX = 0x20; // Max Read bytes from PSU
-constexpr uint8_t FW_READ_BLOCK_SIZE = 0x20;  // Read bytes from FW file
-constexpr uint8_t BLOCK_WRITE_SIZE = 0x25;    // I2C block write size
-constexpr uint8_t READ_SEQ_ST_CML_SIZE = 0x6; // Read sequence and status CML
-                                              // size
-constexpr uint8_t START_SEQUENCE_INDEX = 0x1; // Starting sequence index
-constexpr uint8_t STATUS_CML_INDEX = 0x5;     // Status CML read index
+constexpr uint8_t I2C_SMBUS_BLOCK_MAX = 0x20;    // Max Read bytes from PSU
+constexpr uint8_t FW_READ_BLOCK_SIZE = 0x20;     // Read bytes from FW file
+constexpr uint8_t BLOCK_WRITE_SIZE = 0x25;       // I2C block write size
+
+constexpr uint8_t START_SEQUENCE_INDEX = 0x1;    // Starting sequence index
+constexpr uint8_t STATUS_CML_INDEX = 0x4;        // Status CML read index
+constexpr uint8_t EXPECTED_MEM_READ_REPLY = 0x5; //  Expected memory read reply
+                                                 //  size after write data
 
 // Register addresses for commands.
 constexpr uint8_t KEY_REGISTER = 0xF6;        // Key register
@@ -68,26 +62,10 @@
                                        // OS.
 
 // Define AEI ISP response status bit
-constexpr uint8_t B_CHKSUM_ERR = 0x0;     // The checksum verification
-                                          // unsuccessful
-constexpr uint8_t B_CHKSUM_SUCCESS = 0x1; // The checksum
-// verification successful.
-constexpr uint8_t B_MEM_ERR = 0x2;    // Memory boundry error indication.
-constexpr uint8_t B_ALIGN_ERR = 0x4;  // Address error indication.
-constexpr uint8_t B_KEY_ERR = 0x8;    // Invalid Key
-constexpr uint8_t B_START_ERR = 0x10; // Error indicator set at startup.
-constexpr uint8_t B_IMG_MISSMATCH_ERR = 0x20; // Firmware image does not  match
-                                              // PSU
-constexpr uint8_t B_ISP_MODE = 0x40;          // ISP mode
+constexpr uint8_t B_ISP_MODE = 0x40;             // ISP mode
 constexpr uint8_t B_ISP_MODE_CHKSUM_GOOD = 0x41; // ISP mode  & good checksum.
-constexpr uint8_t B_PRGM_BUSY = 0x80;            // Write operation in progress.
 constexpr uint8_t SUCCESSFUL_ISP_REBOOT_STATUS = 0x0; // Successful ISP reboot
                                                       // status
-
-#ifdef __clang__
-#pragma clang diagnostic pop
-#endif
-
 using namespace phosphor::logging;
 namespace util = phosphor::power::util;
 
@@ -98,21 +76,82 @@
     {
         throw std::runtime_error("I2C interface error");
     }
-    bool cleanFailedIspMode = false; // Flag to prevent download and continue to
-                                     // restore the PSU to it's original state.
-
-    // Set ISP mode by writing necessary keys and resetting ISP status
-    if (!writeIspKey() || !writeIspMode() || !writeIspStatusReset())
+    bool downloadFwFailed = false; // Download Firmware status
+    int retryProcessTwo(0);
+    int retryProcessOne(0);
+    while ((retryProcessTwo < MAX_RETRIES) && (retryProcessOne < MAX_RETRIES))
     {
-        lg2::error("Failed to set ISP key or mode or reset ISP status");
-        cleanFailedIspMode = true;
-    }
+        retryProcessTwo++;
+        // Write AEI PSU ISP key
+        if (!writeIspKey())
+        {
+            lg2::error("Failed to set ISP Key");
+            downloadFwFailed = true; // Download Firmware status
+            continue;
+        }
 
-    if (cleanFailedIspMode)
+        while (retryProcessOne < MAX_RETRIES)
+        {
+            downloadFwFailed = false; // Download Firmware status
+            retryProcessOne++;
+            // Set ISP mode
+            if (!writeIspMode())
+            {
+                // Write ISP Mode failed MAX_RETRIES times
+                retryProcessTwo = MAX_RETRIES;
+                downloadFwFailed = true; // Download Firmware Failed
+                break;
+            }
+
+            // Reset ISP status
+            if (writeIspStatusReset())
+            {
+                // Start PSU frimware download.
+                if (downloadPsuFirmware())
+                {
+                    if (!verifyDownloadFWStatus())
+                    {
+                        downloadFwFailed = true;
+                        continue;
+                    }
+                }
+                else
+                {
+                    downloadFwFailed = true;
+                    continue;
+                }
+            }
+            else
+            {
+                // ISP Status Reset failed MAX_RETRIES times
+                retryProcessTwo = MAX_RETRIES;
+                downloadFwFailed = true;
+                break;
+            }
+
+            ispReboot();
+            if (ispReadRebootStatus() && !downloadFwFailed)
+            {
+                // Download completed successful
+                retryProcessTwo = MAX_RETRIES;
+                break;
+            }
+            else
+            {
+                if ((retryProcessOne < (MAX_RETRIES - 1)) &&
+                    (retryProcessTwo < (MAX_RETRIES - 1)))
+                {
+                    downloadFwFailed = false;
+                    break;
+                }
+            }
+        }
+    }
+    if (downloadFwFailed)
     {
         return 1;
     }
-    return 0; // Update successful.
+    return 0; // Update successful
 }
 
 bool AeiUpdater::writeIspKey()
@@ -151,6 +190,7 @@
 
             if (ispStatus & B_ISP_MODE)
             {
+                lg2::info("Set ISP Mode");
                 return true;
             }
         }
@@ -169,29 +209,45 @@
 {
     // Reset ISP status register before firmware download.
     uint8_t ispStatus = 0;
-    try
+    for (int retry = 0; retry < MAX_RETRIES; retry++)
     {
-        i2cInterface->write(STATUS_REGISTER,
-                            CMD_RESET_SEQ); // Start reset sequence.
-        for (int retry = 0; retry < MAX_RETRIES; ++retry)
+        try
+        {
+            i2cInterface->write(STATUS_REGISTER,
+                                CMD_RESET_SEQ); // Start reset sequence.
+            retry = MAX_RETRIES;
+        }
+        catch (const std::exception& e)
+        {
+            // Log any errors encountered during reset sequence.
+            lg2::error("I2C Write ISP reset failed: {ERROR}", "ERROR", e);
+        }
+    }
+
+    for (int retry = 0; retry < MAX_RETRIES; ++retry)
+    {
+        try
         {
             i2cInterface->read(STATUS_REGISTER, ispStatus);
             if (ispStatus == B_ISP_MODE)
             {
+                lg2::info("Read/Write ISP reset");
                 return true; // ISP status reset successfully.
             }
             i2cInterface->write(STATUS_REGISTER,
                                 CMD_CLEAR_STATUS); // Clear status if
                                                    // not reset.
+            lg2::error("Read/Write ISP reset failed");
+        }
+        catch (const std::exception& e)
+        {
+            // Log any errors encountered during reset sequence.
+            lg2::error("I2C Read/Write error during ISP reset: {ERROR}",
+                       "ERROR", e);
         }
     }
-    catch (const std::exception& e)
-    {
-        // Log any errors encountered during reset sequence.
-        lg2::error("I2C Read/Write error during ISP reset: {ERROR}", "ERROR",
-                   e);
-    }
     lg2::error("Failed to reset ISP Status");
+    ispReboot();
     return false;
 }
 
@@ -234,12 +290,12 @@
     return block;
 }
 
-std::vector<uint8_t>
-    AeiUpdater::prepareCommandBlock(const std::vector<uint8_t>& dataBlockRead)
+void AeiUpdater::prepareCommandBlock(const std::vector<uint8_t>& dataBlockRead)
 {
-    std::vector<uint8_t> cmdBlockWrite = {ISP_MEMORY_REGISTER,
-                                          BLOCK_WRITE_SIZE};
-
+    cmdBlockWrite.clear(); // Clear cmdBlockWrite before use
+    // Assign new values to cmdBlockWrite
+    cmdBlockWrite.push_back(ISP_MEMORY_REGISTER);
+    cmdBlockWrite.push_back(BLOCK_WRITE_SIZE);
     cmdBlockWrite.insert(cmdBlockWrite.end(), byteSwappedIndex.begin(),
                          byteSwappedIndex.end());
     cmdBlockWrite.insert(cmdBlockWrite.end(), dataBlockRead.begin(),
@@ -253,8 +309,136 @@
     cmdBlockWrite.push_back(updater::internal::calculateCRC8(cmdBlockWrite));
     // Remove the F9 and byte count
     cmdBlockWrite.erase(cmdBlockWrite.begin(), cmdBlockWrite.begin() + 2);
+}
 
-    return cmdBlockWrite;
+bool AeiUpdater::downloadPsuFirmware()
+{
+    // Get firmware path
+    const std::string fspath = getFirmwarePath();
+    if (fspath.empty())
+    {
+        lg2::error("Unable to find path");
+        return false;
+    }
+    // Validate firmware file
+    if (!isFirmwareFileValid(fspath))
+    {
+        lg2::error("Invalid file path");
+        return false;
+    }
+
+    // Open firmware file
+    auto inputFile = openFirmwareFile(fspath);
+    if (!inputFile)
+    {
+        lg2::error("Unable to open firmware file {FILE}", "FILE", fspath);
+        return false;
+    }
+
+    // Read and process firmware file in blocks
+    size_t bytesRead = 0;
+    const auto fileSize = std::filesystem::file_size(fspath);
+    bool downloadFailed = false;
+    byteSwappedIndex =
+        updater::internal::bigEndianToLittleEndian(START_SEQUENCE_INDEX);
+    int writeBlockDelay = MEM_WRITE_DELAY;
+
+    while ((bytesRead < fileSize) && !downloadFailed)
+    {
+        // Read a block of firmware data
+        auto dataRead = readFirmwareBlock(*inputFile, FW_READ_BLOCK_SIZE);
+        bytesRead += dataRead.size();
+
+        // Prepare command block with the current index and data
+        prepareCommandBlock(dataRead);
+
+        // Perform I2C write/read with retries
+        uint8_t readData[I2C_SMBUS_BLOCK_MAX] = {};
+        downloadFailed = !performI2cWriteReadWithRetries(
+            ISP_MEMORY_REGISTER, EXPECTED_MEM_READ_REPLY, readData, MAX_RETRIES,
+            writeBlockDelay);
+
+        // Adjust delay after first write block
+        writeBlockDelay = MEM_STRETCH_DELAY;
+    }
+
+    inputFile->close();
+
+    // Log final download status
+    if (downloadFailed)
+    {
+        lg2::error(
+            "Firmware download failed after retries at FW block {BYTESREAD}",
+            "BYTESREAD", bytesRead);
+        return false; // Failed
+    }
+
+    return true;
+}
+
+bool AeiUpdater::performI2cWriteReadWithRetries(
+    uint8_t regAddr, const uint8_t expectedReadSize, uint8_t* readData,
+    const int retries, const int delayTime)
+{
+    for (int i = 0; i < retries; ++i)
+    {
+        uint8_t readReplySize = 0;
+        try
+        {
+            performI2cWriteRead(regAddr, readReplySize, readData, delayTime);
+            if ((readData[STATUS_CML_INDEX] == 0) &&
+                (readReplySize == expectedReadSize) &&
+                !std::equal(readData, readData + 4, byteSwappedIndex.begin()))
+            {
+                std::copy(readData, readData + 4, byteSwappedIndex.begin());
+                return true;
+            }
+            else
+            {
+                lg2::error("I2C write/read block failed");
+            }
+        }
+        catch (const std::exception& e)
+        {
+            lg2::error("I2C write/read block failed: {ERROR}", "ERROR", e);
+        }
+    }
+    return false;
+}
+
+void AeiUpdater::performI2cWriteRead(uint8_t regAddr, uint8_t& readReplySize,
+                                     uint8_t* readData, const int& delayTime)
+{
+    i2cInterface->processCall(regAddr, cmdBlockWrite.size(),
+                              cmdBlockWrite.data(), readReplySize, readData);
+
+    if (delayTime != 0)
+    {
+        updater::internal::delay(delayTime);
+    }
+}
+
+bool AeiUpdater::verifyDownloadFWStatus()
+{
+    try
+    {
+        // Read and verify firmware download status.
+        uint8_t status = 0;
+        i2cInterface->read(STATUS_REGISTER, status);
+        if (status != B_ISP_MODE_CHKSUM_GOOD)
+        {
+            lg2::error("Firmware download failed - status: {ERR}", "ERR",
+                       status);
+
+            return false; // Failed checksum
+        }
+        return true;
+    }
+    catch (const std::exception& e)
+    {
+        lg2::error("I2C read status register failed: {ERROR}", "ERROR", e);
+    }
+    return false; // Failed
 }
 
 void AeiUpdater::ispReboot()
@@ -284,9 +468,8 @@
         uint8_t data = 1; // Initialize data to a non-zero value
         i2cInterface->read(STATUS_REGISTER, data);
 
-        uint8_t status = SUCCESSFUL_ISP_REBOOT_STATUS;
         // If the reboot was successful, the read data should be 0
-        if (data == status)
+        if (data == SUCCESSFUL_ISP_REBOOT_STATUS)
         {
             lg2::info("ISP Status Reboot successful.");
             return true;
@@ -296,6 +479,9 @@
     {
         lg2::error("I2C read error during reboot attempt: {ERROR}", "ERROR", e);
     }
+
+    // If we reach here, all retries have failed
+    lg2::error("Failed to reboot ISP status after max retries.");
     return false;
 }
 
diff --git a/tools/power-utils/aei_updater.hpp b/tools/power-utils/aei_updater.hpp
index de5ca5c..99127fd 100644
--- a/tools/power-utils/aei_updater.hpp
+++ b/tools/power-utils/aei_updater.hpp
@@ -114,14 +114,61 @@
                                            const size_t& bytesToRead);
 
     /**
-     * @brief Prepares an ISP_MEMORY  command block by processing the firmware
+     * @brief Prepares an ISP_MEMORY command block by processing the firmware
      * data block.
      *
      * @param dataBlockRead The firmware data block read from the file.
-     * @return The prepared command block.
      */
-    std::vector<uint8_t>
-        prepareCommandBlock(const std::vector<uint8_t>& dataBlockRead);
+    void prepareCommandBlock(const std::vector<uint8_t>& dataBlockRead);
+
+    /**
+     * @brief Performs firmware update for the power supply unit (PSU)
+     *
+     * This function retrieves the firmware file from appropriate path,
+     * validate existence of the file and initiate the update process.
+     * The process includes processing the data into blocks, and writes
+     * these blocks to the PSU.
+     *
+     * @return True if firmware download was successful, false otherwise.
+     */
+    bool downloadPsuFirmware();
+
+    /**
+     * @brief Performs an I2C write and read with retry logic.
+     *
+     * This function attempts to write a command block to PSU register
+     * and read next block sequence and CML write status. If the block
+     * sequence number the same as written block, then retry to write
+     * same block again.
+     *
+     * @param regAddr The register address to write to.
+     * @param expectedReadSize The size of data read from i2c device.
+     * @param readData The buffer to store read data.
+     * @param retries The number of retry attempts allowed.
+     * @param delayTime The delay time between retries.
+     * @return True if the operation is successful, false otherwise.
+     */
+    bool performI2cWriteReadWithRetries(
+        uint8_t regAddr, const uint8_t expectedReadSize, uint8_t* readData,
+        const int retries, const int delayTime);
+
+    /**
+     * @brief Performs a single I2C write and read without retry logic.
+     *
+     * @param regAddr The register address to write to.
+     * @param readReplySize The size of data read from i2c device.
+     * @param readData The buffer to store read data.
+     * @param delayTime The delay time between write and read operations.
+     */
+    void performI2cWriteRead(uint8_t regAddr, uint8_t& readReplySize,
+                             uint8_t* readData, const int& delayTime);
+    /**
+     * @brief Verifies the status of the firmware download.
+     *
+     * @return True if the download status is verified as successful, false
+     * otherwise.
+     */
+    bool verifyDownloadFWStatus();
 
     /**
      * @brief Initiates a reboot of the ISP to apply new firmware.
@@ -149,5 +196,10 @@
      * @brief Stores byte-swapped indices for command processing
      */
     std::vector<uint8_t> byteSwappedIndex;
+
+    /**
+     * @brief Command block used for writing data to the device
+     */
+    std::vector<uint8_t> cmdBlockWrite;
 };
 } // namespace aeiUpdater
diff --git a/tools/power-utils/test/meson.build b/tools/power-utils/test/meson.build
index 33d3df1..06f483b 100644
--- a/tools/power-utils/test/meson.build
+++ b/tools/power-utils/test/meson.build
@@ -6,6 +6,7 @@
         'test_utils.cpp',
         'test_version.cpp',
         '../updater.cpp',
+        '../aei_updater.cpp',
         '../utils.cpp',
         '../version.cpp',
         dependencies: [
diff --git a/tools/power-utils/updater.cpp b/tools/power-utils/updater.cpp
index c48decb..1bf729c 100644
--- a/tools/power-utils/updater.cpp
+++ b/tools/power-utils/updater.cpp
@@ -15,6 +15,7 @@
  */
 #include "updater.hpp"
 
+#include "aei_updater.hpp"
 #include "pmbus.hpp"
 #include "types.hpp"
 #include "utility.hpp"
@@ -45,9 +46,10 @@
     const std::string& model, const std::string& psuInventoryPath,
     const std::string& devPath, const std::string& imageDir)
 {
-    if (model == "XXXX")
+    if (model == "51E9" || model == "51DA")
     {
-        // XXXX updater class
+        return std::make_unique<aeiUpdater::AeiUpdater>(psuInventoryPath,
+                                                        devPath, imageDir);
     }
     return std::make_unique<Updater>(psuInventoryPath, devPath, imageDir);
 }
@@ -64,8 +66,7 @@
         {
             std::string filename = entry.path().filename().string();
 
-            if ((filename.rfind(model, 0) == 0) &&
-                (filename.ends_with(".bin") || filename.ends_with(".hex")))
+            if ((filename.rfind(model, 0) == 0) && (filename.ends_with(".bin")))
             {
                 return directory + "/" + filename;
             }
diff --git a/tools/power-utils/utils.cpp b/tools/power-utils/utils.cpp
index e70004d..9517680 100644
--- a/tools/power-utils/utils.cpp
+++ b/tools/power-utils/utils.cpp
@@ -176,6 +176,10 @@
 
 std::string getDeviceName(std::string devPath)
 {
+    if (devPath.empty())
+    {
+        return devPath;
+    }
     if (devPath.back() == '/')
     {
         devPath.pop_back();