| /** |
| * 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> |
| |
| #include <fstream> |
| |
| 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 |
| 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 downloadFwFailed = false; // Download Firmware status |
| int retryProcessTwo(0); |
| int retryProcessOne(0); |
| while ((retryProcessTwo < MAX_RETRIES) && (retryProcessOne < MAX_RETRIES)) |
| { |
| retryProcessTwo++; |
| // Write AEI PSU ISP key |
| if (!writeIspKey()) |
| { |
| lg2::error("Failed to set ISP Key"); |
| downloadFwFailed = true; // Download Firmware status |
| continue; |
| } |
| |
| 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 |
| } |
| |
| 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); |
| 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) |
| { |
| lg2::info("Set 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); |
| } |
| } |
| 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; |
| 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); |
| } |
| } |
| lg2::error("Failed to reset ISP Status"); |
| ispReboot(); |
| return false; |
| } |
| |
| std::string AeiUpdater::getFirmwarePath() |
| { |
| const std::string fspath = |
| updater::internal::getFWFilenamePath(getImageDir()); |
| if (fspath.empty()) |
| { |
| lg2::error("Firmware file path not found"); |
| } |
| return fspath; |
| } |
| |
| bool AeiUpdater::isFirmwareFileValid(const std::string& fspath) |
| { |
| if (!updater::internal::validateFWFile(fspath)) |
| { |
| lg2::error("Firmware validation failed"); |
| return false; |
| } |
| return true; |
| } |
| |
| std::unique_ptr<std::ifstream> AeiUpdater::openFirmwareFile( |
| const std::string& fspath) |
| { |
| auto inputFile = updater::internal::openFirmwareFile(fspath); |
| if (!inputFile) |
| { |
| lg2::error("Failed to open firmware file"); |
| } |
| return inputFile; |
| } |
| |
| std::vector<uint8_t> AeiUpdater::readFirmwareBlock(std::ifstream& file, |
| const size_t& bytesToRead) |
| { |
| auto block = updater::internal::readFirmwareBytes(file, bytesToRead); |
| return block; |
| } |
| |
| void AeiUpdater::prepareCommandBlock(const std::vector<uint8_t>& 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() |
| { |
| // 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() |
| { |
| 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() |
| { |
| 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 std::exception& e) |
| { |
| 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; |
| } |
| |
| } // namespace aeiUpdater |