blob: 4b1865b178060953e9e89b28bde280d1563ac666 [file] [log] [blame]
/**
* 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 <phosphor-logging/lg2.hpp>
#include <fstream>
#include <system_error>
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<std::string, std::string> 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<std::string, std::string> 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<uint8_t, 4> 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<std::string, std::string> 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<std::string, std::string> 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<std::string, std::string> 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<std::string, std::string> 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<std::string, std::string> 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<std::string, std::string> 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<std::string, std::string> 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<std::string, std::string> 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<std::string, std::string> additionalData = {
{"ISP_READ_STATUS", "Exception during read ISP status"},
{"EXCEPTION", e.what()}};
// Callout PSU
callOutPsuEventLog(additionalData);
}
}
}
if (exceptionCount != MAX_RETRIES)
{
std::map<std::string, std::string> 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<std::string, std::string> 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<std::string, std::string> 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<std::ifstream> AeiUpdater::openFirmwareFile()
{
auto inputFile = updater::internal::openFirmwareFile(fspath);
if (!inputFile)
{
std::map<std::string, std::string> 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<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()
{
// Open firmware file
auto inputFile = openFirmwareFile();
if (!inputFile)
{
if (isEventLogEnabled())
{
// Callout BMC0001 procedure
std::map<std::string, std::string> 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<std::string, std::string> 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<std::string, std::string> 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<std::string, std::string> 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<std::string, std::string> 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<std::string, std::string> 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