psutils: Partial AEI Updater class
The AeiUpdater class within the aeiUpdater namespace, responsible for
updating AEI PSU firmware via I2C communication. The code focuses on
executing various stages of the firmware update process, ensuring each
step performed and validated correctly. The following partial functions
of the key component of the class:
doUpdate()
This method initiates the firmware update process. It checks and sets
the ISP (In-System Programming) mode using writeIspKey(),
writeIspMode(), and writeIspStatusReset().
writeIspKey()
Unlocks the ISP by writing a specific key to the KEY_REGISTER. Handles
exceptions for any I2C communication errors and logs the failure.
writeIspMode()
Sends a command to enter ISP mode and checks the status register.
Retries on failure, with a maximum retry limit. Checks the status
register to confirm the mode has been set. Logs any I2C errors
encountered.
writeIspStatusReset()
Resets ISP status register, clearing any existing status flags. Verifies
the reset by reading the status register, retrying as needed. Logs any
I2C errors encountered.
Test:
Tested the function listed in the class on a system with AEI PSU:
- Validated the PSU ISP key was accepted and no error logged.
- Validated an error was logged when I used wrong key.
- Validated the PSU entered to ISP mode by reading back status register
and confirmed the bit is set.
- Validated the PSU status reset by reading the back status register and
confirmed the bit is set.
Change-Id: I1e8e594f088e7d66d8fc5b1723c4bd33b08bd3f8
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
new file mode 100644
index 0000000..0567ac9
--- /dev/null
+++ b/tools/power-utils/aei_updater.cpp
@@ -0,0 +1,181 @@
+/**
+ * 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 "config.h"
+
+#include "aei_updater.hpp"
+
+#include "pmbus.hpp"
+#include "types.hpp"
+#include "updater.hpp"
+#include "utility.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+
+namespace aeiUpdater
+{
+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 = 50; // Delay between writes (50ms)
+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
+
+// Register addresses for commands.
+constexpr uint8_t KEY_REGISTER = 0xF6; // Key register
+constexpr uint8_t STATUS_REGISTER = 0xF7; // Status register
+constexpr uint8_t ISP_MEMORY_REGISTER = 0xF9; // ISP memory register
+
+// Define AEI ISP status register commands
+constexpr uint8_t CMD_CLEAR_STATUS = 0x0; // Clear the status register
+constexpr uint8_t CMD_RESET_SEQ = 0x01; // This command will reset ISP OS for
+ // another attempt of a sequential
+ // programming operation.
+constexpr uint8_t CMD_BOOT_ISP = 0x02; // Boot the In-System Programming System.
+constexpr uint8_t CMD_BOOT_PWR = 0x03; // Attempt to boot the Power Management
+ // 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_CHKSUM_GOOD = 0x41; // ISP mode & good checksum.
+constexpr uint8_t B_PRGM_BUSY = 0x80; // Write operation in progress.
+
+using namespace phosphor::logging;
+namespace util = phosphor::power::util;
+
+int AeiUpdater::doUpdate()
+{
+ i2cInterface = Updater::getI2C();
+ if (i2cInterface == nullptr)
+ {
+ 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())
+ {
+ lg2::error("Failed to set ISP key or mode or reset ISP status");
+ cleanFailedIspMode = true;
+ }
+
+ if (cleanFailedIspMode)
+ {
+ return 1;
+ }
+ return 0; // Update successful.
+}
+
+bool AeiUpdater::writeIspKey()
+{
+ // ISP Key to unlock programming mode ( ASCII for "artY").
+ constexpr std::array<uint8_t, 4> unlockData = {0x61, 0x72, 0x74,
+ 0x59}; // ISP Key "artY"
+ try
+ {
+ // Send ISP Key to unlock device for firmware update
+ i2cInterface->write(KEY_REGISTER, unlockData.size(), unlockData.data());
+ return true;
+ }
+ catch (const std::exception& e)
+ {
+ // Log failure if I2C write fails.
+ lg2::error("I2C write failed: {ERROR}", "ERROR", e.what());
+ return false;
+ }
+}
+
+bool AeiUpdater::writeIspMode()
+{
+ // Attempt to set device in ISP mode with retries.
+ uint8_t ispStatus = 0x0;
+ for (int retry = 0; retry < MAX_RETRIES; ++retry)
+ {
+ try
+ {
+ // Write command to enter ISP mode.
+ i2cInterface->write(STATUS_REGISTER, CMD_BOOT_ISP);
+ // Delay to allow status register update.
+ updater::internal::delay(ISP_STATUS_DELAY);
+ // Read back status register to confirm ISP mode is active.
+ i2cInterface->read(STATUS_REGISTER, ispStatus);
+
+ if (ispStatus & B_ISP_MODE)
+ {
+ return true;
+ }
+ }
+ catch (const std::exception& e)
+ {
+ // Log I2C error with each retry attempt.
+ lg2::error("I2C error during ISP mode write/read: {ERROR}", "ERROR",
+ e.what());
+ }
+ }
+ lg2::error("Failed to set ISP Mode");
+ return false; // Failed to set ISP Mode after retries
+}
+
+bool AeiUpdater::writeIspStatusReset()
+{
+ // Reset ISP status register before firmware download.
+ uint8_t ispStatus = 0;
+ try
+ {
+ i2cInterface->write(STATUS_REGISTER,
+ CMD_RESET_SEQ); // Start reset sequence.
+ for (int retry = 0; retry < MAX_RETRIES; ++retry)
+ {
+ i2cInterface->read(STATUS_REGISTER, ispStatus);
+ if (ispStatus == B_ISP_MODE)
+ {
+ return true; // ISP status reset successfully.
+ }
+ i2cInterface->write(STATUS_REGISTER,
+ CMD_CLEAR_STATUS); // Clear status if
+ // not reset.
+ }
+ }
+ catch (const std::exception& e)
+ {
+ // Log any errors encountered during reset sequence.
+ lg2::error("I2C Read/Write error during ISP reset: {ERROR}", "ERROR",
+ e.what());
+ }
+ lg2::error("Failed to reset ISP Status");
+ return false;
+}
+
+} // namespace aeiUpdater
diff --git a/tools/power-utils/aei_updater.hpp b/tools/power-utils/aei_updater.hpp
new file mode 100644
index 0000000..1b872b6
--- /dev/null
+++ b/tools/power-utils/aei_updater.hpp
@@ -0,0 +1,96 @@
+/**
+ * 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 "updater.hpp"
+
+namespace aeiUpdater
+{
+/**
+ * @class AeiUpdater
+ * @brief A class to handle firmware updates for the AEI PSUs.
+ *
+ * This class provides methods to update firmware by writing ISP keys,
+ * validating firmware files, and performing I2C communications. It includes
+ * functions to manage the update process, including downloading firmware blocks
+ * and verifying the update status.
+ */
+class AeiUpdater : public updater::Updater
+{
+ public:
+ AeiUpdater() = delete;
+ AeiUpdater(const AeiUpdater&) = delete;
+ AeiUpdater(AeiUpdater&&) = delete;
+ AeiUpdater& operator=(const AeiUpdater&) = delete;
+ AeiUpdater& operator=(AeiUpdater&&) = delete;
+ ~AeiUpdater() = default;
+
+ /**
+ * @brief Constructor for the AeiUpdater class.
+ *
+ * @param psuInventoryPath The path to the PSU's inventory in the system.
+ * @param devPath The device path for the PSU.
+ * @param imageDir The directory containing the firmware image files.
+ */
+ AeiUpdater(const std::string& psuInventoryPath, const std::string& devPath,
+ const std::string& imageDir) :
+ Updater(psuInventoryPath, devPath, imageDir)
+ {}
+
+ /**
+ * @brief Initiates the firmware update process.
+ *
+ * @return int Status code 0 for success or 1 for failure.
+ */
+ int doUpdate() override;
+
+ private:
+ /**
+ * @brief Writes an ISP (In-System Programming) key to initiate the update.
+ *
+ * @return bool True if successful, false otherwise.
+ */
+ bool writeIspKey();
+
+ /**
+ * @brief Writes the mode required for ISP to start firmware programming.
+ *
+ * @return bool True if successful, false otherwise.
+ */
+ bool writeIspMode();
+
+ /**
+ * @brief Resets the ISP status to prepare for a firmware update.
+ *
+ * @return bool True if successful, false otherwise.
+ */
+ bool writeIspStatusReset();
+
+ /**
+ * @brief Pointer to the I2C interface for communication
+ *
+ * This pointer is not owned by the class. The caller is responsible for
+ * ensuring that 'i2cInterface' remains valid for the lifetime of this
+ * object. Ensure to check this pointer for null before use.
+ */
+ i2c::I2CInterface* i2cInterface;
+
+ /**
+ * @brief Stores byte-swapped indices for command processing
+ */
+ std::vector<uint8_t> byteSwappedIndex;
+};
+} // namespace aeiUpdater
diff --git a/tools/power-utils/meson.build b/tools/power-utils/meson.build
index 542eadb..82e06b6 100644
--- a/tools/power-utils/meson.build
+++ b/tools/power-utils/meson.build
@@ -2,6 +2,7 @@
'psutils',
'version.cpp',
'updater.cpp',
+ 'aei_updater.cpp',
'utils.cpp',
'model.cpp',
'main.cpp',