/** * 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 "utils.hpp" #include #include #include 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 = 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 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 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_ISP_MODE = 0x40; // ISP mode constexpr uint8_t B_ISP_MODE_CHKSUM_GOOD = 0x41; // ISP mode & good checksum. constexpr uint8_t SUCCESSFUL_ISP_REBOOT_STATUS = 0x0; // Successful ISP reboot // status namespace util = phosphor::power::util; int AeiUpdater::doUpdate() { i2cInterface = Updater::getI2C(); enableEventLogging(); if (i2cInterface == nullptr) { // Report serviceable error std::map additionalData = { {"I2C_INTERFACE", "I2C interface is null pointer."}}; // Callout PSU & I2C callOutI2CEventLog(additionalData); throw std::runtime_error("I2C interface error"); } if (!getFirmwarePath() || !isFirmwareFileValid()) { return 1; // No firmware file abort download } bool downloadFwFailed = false; // Download Firmware status int retryProcessTwo(0); int retryProcessOne(0); disableEventLogging(); while ((retryProcessTwo < MAX_RETRIES) && (retryProcessOne < MAX_RETRIES)) { // Write AEI PSU ISP key if (!writeIspKey()) { lg2::error("Failed to set ISP Key"); downloadFwFailed = true; // Download Firmware status break; } if (retryProcessTwo == (MAX_RETRIES - 1)) { enableEventLogging(); } retryProcessTwo++; 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 firmware download. if (downloadPsuFirmware()) { if (!verifyDownloadFWStatus()) { downloadFwFailed = true; continue; } } else { // One of the block write commands failed, retry download // procedure one time starting with re-writing initial ISP // mode. If it fails again, log serviceable error. if (retryProcessOne == MAX_RETRIES) { // Callout PSU failed to update FW std::map additionalData = { {"UPDATE_FAILED", "Download firmware failed"}}; callOutPsuEventLog(additionalData); ispReboot(); // Try to set PSU to normal mode } 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 { // Retry the whole download process starting with the key and // if fails again then report event log if ((retryProcessOne < (MAX_RETRIES - 1)) && (retryProcessTwo < (MAX_RETRIES - 1))) { downloadFwFailed = false; break; } } } } if (downloadFwFailed) { return 1; } enableEventLogging(); bindUnbind(true); updater::internal::delay(100); callOutGoodEventLog(); return 0; // Update successful } bool AeiUpdater::writeIspKey() { // ISP Key to unlock programming mode ( ASCII for "artY"). constexpr std::array unlockData = {0x61, 0x72, 0x74, 0x59}; // ISP Key "artY" for (int retry = 0; retry < MAX_RETRIES; ++retry) { try { // Send ISP Key to unlock device for firmware update i2cInterface->write(KEY_REGISTER, unlockData.size(), unlockData.data()); disableEventLogging(); return true; } catch (const i2c::I2CException& e) { // Log failure if I2C write fails. lg2::error("I2C write failed: {ERROR}", "ERROR", e); std::map additionalData = { {"I2C_ISP_KEY", "ISP key failed due to I2C exception"}}; callOutI2CEventLog(additionalData, e.what(), e.errorCode); enableEventLogging(); // enable event logging if fail again call out // PSU & I2C } catch (const std::exception& e) { lg2::error("Exception write failed: {ERROR}", "ERROR", e); std::map additionalData = { {"ISP_KEY", "ISP key failed due to exception"}, {"EXCEPTION", e.what()}}; callOutPsuEventLog(additionalData); enableEventLogging(); // enable Event Logging if fail again call out // PSU } } return false; } bool AeiUpdater::writeIspMode() { // Attempt to set device in ISP mode with retries. uint8_t ispStatus = 0x0; uint8_t exceptionCount = 0; 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) { lg2::info("Set ISP Mode"); disableEventLogging(); return true; } enableEventLogging(); } catch (const i2c::I2CException& e) { exceptionCount++; // Log I2C error with each retry attempt. lg2::error("I2C exception during ISP mode write/read: {ERROR}", "ERROR", e); if (exceptionCount == MAX_RETRIES) { enableEventLogging(); std::map additionalData = { {"I2C_FIRMWARE_STATUS", "Download firmware failed during writeIspMode due to I2C exception"}}; // Callout PSU & I2C callOutI2CEventLog(additionalData, e.what(), e.errorCode); return false; // Failed to set ISP Mode } } catch (const std::exception& e) { exceptionCount++; // Log error with each retry attempt. lg2::error("Exception during ISP mode write/read: {ERROR}", "ERROR", e); if (exceptionCount == MAX_RETRIES) { enableEventLogging(); std::map additionalData = { {"FIRMWARE_STATUS", "Download firmware failed during writeIspMode due to exception"}, {"EXCEPTION", e.what()}}; // Callout PSU callOutPsuEventLog(additionalData); return false; // Failed to set ISP Mode } } } if (exceptionCount != MAX_RETRIES) { // Callout PSU std::map additionalData = { {"FIRMWARE_STATUS", "Download firmware failed during writeIspMode"}}; callOutPsuEventLog(additionalData); } 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; uint8_t exceptionCount = 0; for (int retry = 0; retry < MAX_RETRIES; retry++) { try { i2cInterface->write(STATUS_REGISTER, CMD_RESET_SEQ); // Start reset sequence. retry = MAX_RETRIES; } catch (const i2c::I2CException& e) { exceptionCount++; // Log any errors encountered during reset sequence. lg2::error("I2C Write ISP reset failed: {ERROR}", "ERROR", e); if (exceptionCount == MAX_RETRIES) { enableEventLogging(); std::map additionalData = { {"I2C_ISP_RESET", "I2C exception during ISP status reset"}}; // Callout PSU & I2C callOutI2CEventLog(additionalData, e.what(), e.errorCode); ispReboot(); return false; } } catch (const std::exception& e) { exceptionCount++; // Log any errors encountered during reset sequence. lg2::error("Write ISP reset failed: {ERROR}", "ERROR", e); if (exceptionCount == MAX_RETRIES) { enableEventLogging(); std::map additionalData = { {"ISP_RESET", "Exception during ISP status reset"}, {"EXCEPTION", e.what()}}; // Callout PSU callOutPsuEventLog(additionalData); ispReboot(); return false; } } } exceptionCount = 0; for (int retry = 0; retry < MAX_RETRIES; ++retry) { try { i2cInterface->read(STATUS_REGISTER, ispStatus); if (ispStatus == B_ISP_MODE) { lg2::info("write/read ISP reset"); disableEventLogging(); return true; // ISP status reset successfully. } i2cInterface->write(STATUS_REGISTER, CMD_CLEAR_STATUS); // Clear status if // not reset. lg2::error("Write ISP reset failed"); enableEventLogging(); } catch (const i2c::I2CException& e) { exceptionCount++; // Log any errors encountered during reset sequence. lg2::error( "I2C Write/Read or Write error during ISP reset: {ERROR}", "ERROR", e); if (exceptionCount == MAX_RETRIES) { enableEventLogging(); std::map additionalData = { {"I2C_ISP_READ_STATUS", "I2C exception during read ISP status"}}; // Callout PSU & I2C callOutI2CEventLog(additionalData, e.what(), e.errorCode); } } catch (const std::exception& e) { exceptionCount++; // Log any errors encountered during reset sequence. lg2::error("Write/Read or Write error during ISP reset: {ERROR}", "ERROR", e); if (exceptionCount == MAX_RETRIES) { enableEventLogging(); std::map additionalData = { {"ISP_READ_STATUS", "Exception during read ISP status"}, {"EXCEPTION", e.what()}}; // Callout PSU callOutPsuEventLog(additionalData); } } } if (exceptionCount != MAX_RETRIES) { std::map additionalData = { {"ISP_REST_FAILED", "Failed to read ISP expected status"}}; // Callout PSU callOutPsuEventLog(additionalData); } lg2::error("Failed to reset ISP Status"); ispReboot(); return false; } bool AeiUpdater::getFirmwarePath() { fspath = updater::internal::getFWFilenamePath(getImageDir()); if (fspath.empty()) { std::map additionalData = { {"FILE_PATH", "Firmware file path is null"}}; // Callout BMC0001 procedure callOutSWEventLog(additionalData); lg2::error("Firmware file path not found"); return false; } return true; } bool AeiUpdater::isFirmwareFileValid() { if (!updater::internal::validateFWFile(fspath)) { std::map additionalData = { {"FIRMWARE_VALID", "Firmware validation failed, FW file path = " + fspath}}; // Callout BMC0001 procedure callOutSWEventLog(additionalData); lg2::error("Firmware validation failed, fspath={PATH}", "PATH", fspath); return false; } return true; } std::unique_ptr AeiUpdater::openFirmwareFile() { auto inputFile = updater::internal::openFirmwareFile(fspath); if (!inputFile) { std::map additionalData = { {"FIRMWARE_OPEN", "Firmware file failed to open, FW file path = " + fspath}}; // Callout BMC0001 procedure callOutSWEventLog(additionalData); lg2::error("Failed to open firmware file"); } return inputFile; } std::vector AeiUpdater::readFirmwareBlock(std::ifstream& file, const size_t& bytesToRead) { auto block = updater::internal::readFirmwareBytes(file, bytesToRead); return block; } void AeiUpdater::prepareCommandBlock(const std::vector& dataBlockRead) { 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(), dataBlockRead.end()); // Resize to ensure it matches BLOCK_WRITE_SIZE + 1 and append CRC if (cmdBlockWrite.size() != BLOCK_WRITE_SIZE + 1) { cmdBlockWrite.resize(BLOCK_WRITE_SIZE + 1, 0xFF); } cmdBlockWrite.push_back(updater::internal::calculateCRC8(cmdBlockWrite)); // Remove the F9 and byte count cmdBlockWrite.erase(cmdBlockWrite.begin(), cmdBlockWrite.begin() + 2); } bool AeiUpdater::downloadPsuFirmware() { // Open firmware file auto inputFile = openFirmwareFile(); if (!inputFile) { if (isEventLogEnabled()) { // Callout BMC0001 procedure std::map additionalData = { {"FW_FAILED_TO_OPEN", "Firmware file failed to open"}, {"FW_FILE_PATH", fspath}}; callOutSWEventLog(additionalData); ispReboot(); // Try to set PSU to normal mode } 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) { uint8_t exceptionCount = 0; uint32_t bigEndianValue = 0; for (int i = 0; i < retries; ++i) { uint8_t readReplySize = 0; try { performI2cWriteRead(regAddr, readReplySize, readData, delayTime); if ((readData[STATUS_CML_INDEX] == 0 || // The first firmware data packet sent to the PSU have a // response of 0x80 which indicates firmware update in // progress. If retry to send the first packet again reply will // be 0. (readData[STATUS_CML_INDEX] == 0x80 && delayTime == MEM_WRITE_DELAY)) && (readReplySize == expectedReadSize) && !std::equal(readData, readData + 4, byteSwappedIndex.begin())) { std::copy(readData, readData + 4, byteSwappedIndex.begin()); return true; } else { bigEndianValue = (readData[0] << 24) | (readData[1] << 16) | (readData[2] << 8) | (readData[3]); lg2::error("Write/read block {NUM} failed", "NUM", bigEndianValue); } } catch (const i2c::I2CException& e) { exceptionCount++; if (exceptionCount == MAX_RETRIES) { std::map additionalData = { {"I2C_WRITE_READ", "I2C exception while flashing the firmware."}}; // Callout PSU & I2C callOutI2CEventLog(additionalData, e.what(), e.errorCode); } lg2::error("I2C exception write/read block failed: {ERROR}", "ERROR", e.what()); } catch (const std::exception& e) { exceptionCount++; if (exceptionCount == MAX_RETRIES) { std::map additionalData = { {"WRITE_READ", "Exception while flashing the firmware."}, {"EXCEPTION", e.what()}}; // Callout PSU callOutPsuEventLog(additionalData); } lg2::error("Exception write/read block failed: {ERROR}", "ERROR", e.what()); } } std::map additionalData = { {"WRITE_READ", "Download firmware failed block: " + std::to_string(bigEndianValue)}}; // Callout PSU callOutPsuEventLog(additionalData); 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() { updater::internal::delay( MEM_COMPLETE_DELAY); // Delay before starting the reboot process try { // Write reboot command to the status register i2cInterface->write(STATUS_REGISTER, CMD_BOOT_PWR); updater::internal::delay( REBOOT_DELAY); // Add delay after writing reboot command } catch (const std::exception& e) { lg2::error("I2C write error during reboot: {ERROR}", "ERROR", e); } } bool AeiUpdater::ispReadRebootStatus() { for (int retry = 0; retry < MAX_RETRIES; ++retry) { try { // Read from the status register to verify reboot uint8_t data = 1; // Initialize data to a non-zero value i2cInterface->read(STATUS_REGISTER, data); // If the reboot was successful, the read data should be 0 if (data == SUCCESSFUL_ISP_REBOOT_STATUS) { lg2::info("ISP Status Reboot successful."); return true; } } catch (const i2c::I2CException& e) { if (isEventLogEnabled()) { std::map additionalData = { {"I2C_READ_REBOOT", "I2C exception while reading ISP reboot status"}}; // Callout PSU & I2C callOutI2CEventLog(additionalData, e.what(), e.errorCode); } lg2::error("I2C read error during reboot attempt: {ERROR}", "ERROR", e); } catch (const std::exception& e) { if (isEventLogEnabled()) { std::map additionalData = { {"READ_REBOOT", "Exception while reading ISP reboot status"}, {"EXCEPTION", e.what()}}; // Callout PSU callOutPsuEventLog(additionalData); } lg2::error("Read exception during reboot attempt: {ERROR}", "ERROR", e); } // Reboot the PSU ispReboot(); // Try to set PSU to normal mode } // If we reach here, all retries have failed lg2::error("Failed to reboot ISP status after max retries."); return false; } } // namespace aeiUpdater