Utility functions for download firmware
Added set of utility functions to be used within the classes for
managing the PSU firmware updates. Here is a breakdown of the key
functions:
- getDevicePath():
Construct the PSU device path using I2C bus and address.
- getClassInstance():
Determines the appropriate updater class to use based on PSU model
number.
- getFWFilenamePath():
Searches a directory for a firmware file matching a specified prefix
and file extension (.bin or .hex).
- calculateCRC8():
Computes the CRC-8 checksum for transferred data.
- delay():
Pauses execution for a specified number of milliseconds.
- bigEndianToLittleEndian():
Converts a 32-bit value from big-endian to little-endian.
- validateFWFile():
Checks if a firmware file exists and is non-empty.
- openFirmwareFile():
Opens a firmware file in binary mode, returning a file stream if
successful.
- readFirmwareBytes():
Reads specified number of data bytes from a firmware file into a
buffer. Return data read or null to the caller.
- usePsuJsonFile():
Wrapper to check the existence of the PSU JSON file.
- Class accessors to private data:
getPsuInventoryPath(): Accessor for PSU inventory path.
getDevPath(): Accessor for device path.
getDevName(): Accessor for device name.
getImageDir(): Accessor for image directory.
getI2C(): I2C interface accessor.
Tested every function manually:
- getDevicePath() (using busctl):
- Validate I2C bus and address values through psuInventoryPath
validate expected result
- Modified psuInventoryPath to invalid path
validate returned invalid path.
- getClassInstance():
- Validate with matching model number the function instantiate
appropriate class.
- Validate the default class instantiated.
- getFWFilenamePath():
- Validate return of the correct file name in the PSU FW directory
- Validate null returns when FW files don't exist
- calculaterCRC8():
- Validate single byte 0x0 result 0x0, single byte 0x01 result 0x07
- delay():
- Verified the task suspend execution.
- bigEndianToLittleEndian():
- Verified input 0x12345678 resulted in 0x78563412
- validateFWFile():
- Validate the existence of the file otherwise an error is logged
- Validate the file size is greater than 0 otherwise an error is
logged.
- openFirmwareFile():
- Validate ifstream object is returned and was able to read from.
- Validate error logged if the file name is null
- validate error logged when unable to open the file
- readFirmwareBytes():
- Validate data read from FW file
- Validate number of bytes read.
- usePsuJsonFile():
- Added JSON file to simulator and validated true return.
Change-Id: I0b8b24ae7d37724dab608d2c4977c1b42d4e1632
Signed-off-by: Faisal Awada <faisal@us.ibm.com>
diff --git a/tools/power-utils/main.cpp b/tools/power-utils/main.cpp
index eb81db4..f240ece 100644
--- a/tools/power-utils/main.cpp
+++ b/tools/power-utils/main.cpp
@@ -71,7 +71,7 @@
if (!updateArguments.empty())
{
assert(updateArguments.size() == 2);
- if (updater::update(updateArguments[0], updateArguments[1]))
+ if (updater::update(bus, updateArguments[0], updateArguments[1]))
{
ret = "Update successful";
}
diff --git a/tools/power-utils/test/meson.build b/tools/power-utils/test/meson.build
index 40b2ca6..e48f799 100644
--- a/tools/power-utils/test/meson.build
+++ b/tools/power-utils/test/meson.build
@@ -26,6 +26,7 @@
'test_updater',
'test_updater.cpp',
'../updater.cpp',
+ '../version.cpp',
dependencies: [
gtest,
gmock,
diff --git a/tools/power-utils/test/test_updater.cpp b/tools/power-utils/test/test_updater.cpp
index 6ad3f73..9c34926 100644
--- a/tools/power-utils/test/test_updater.cpp
+++ b/tools/power-utils/test/test_updater.cpp
@@ -14,6 +14,7 @@
* limitations under the License.
*/
#include "../updater.hpp"
+#include "../version.hpp"
#include "mocked_i2c_interface.hpp"
#include <filesystem>
diff --git a/tools/power-utils/updater.cpp b/tools/power-utils/updater.cpp
index 5c4f7f5..951b78c 100644
--- a/tools/power-utils/updater.cpp
+++ b/tools/power-utils/updater.cpp
@@ -21,11 +21,14 @@
#include "types.hpp"
#include "utility.hpp"
+#include <sys/stat.h>
+
#include <phosphor-logging/log.hpp>
#include <chrono>
#include <fstream>
#include <thread>
+#include <vector>
using namespace phosphor::logging;
namespace util = phosphor::power::util;
@@ -36,6 +39,10 @@
namespace internal
{
+// Define the CRC-8 polynomial (CRC-8-CCITT)
+constexpr uint8_t CRC8_POLYNOMIAL = 0x07;
+constexpr uint8_t CRC8_INITIAL = 0x00;
+
/* Get the device name from the device path */
std::string getDeviceName(std::string devPath)
{
@@ -46,27 +53,59 @@
return fs::path(devPath).stem().string();
}
-std::string getDevicePath(const std::string& psuInventoryPath)
+// Construct the device path using the I2C bus and address or read inventory
+// path
+std::string getDevicePath(sdbusplus::bus_t& bus,
+ const std::string& psuInventoryPath)
{
- auto data = util::loadJSONFromFile(PSU_JSON_PATH);
-
- if (data == nullptr)
+ try
{
+ if (internal::usePsuJsonFile())
+ {
+ auto data = util::loadJSONFromFile(PSU_JSON_PATH);
+ if (data == nullptr)
+ {
+ return {};
+ }
+ auto devicePath = data["psuDevices"][psuInventoryPath];
+ if (devicePath.empty())
+ {
+ log<level::WARNING>("Unable to find psu devices or path");
+ }
+ return devicePath;
+ }
+ else
+ {
+ using namespace version;
+ const auto& [i2cbus, i2caddr] =
+ version::utils::getPsuI2c(bus, psuInventoryPath);
+ const auto DevicePath = "/sys/bus/i2c/devices/";
+ std::ostringstream ss;
+ ss << std::hex << std::setw(4) << std::setfill('0') << i2caddr;
+ std::string addrStr = ss.str();
+ std::string busStr = std::to_string(i2cbus);
+ std::string devPath = DevicePath + busStr + "-" + addrStr;
+ return devPath;
+ }
+ }
+ catch (const std::exception& e)
+ {
+ log<level::ERR>(
+ std::format("Error in getDevicePath: {}", e.what()).c_str());
return {};
}
-
- auto devicePath = data["psuDevices"][psuInventoryPath];
- if (devicePath.empty())
+ catch (...)
{
- log<level::WARNING>("Unable to find psu devices or path");
+ log<level::ERR>("Unknown error occurred in getDevicePath");
+ return {};
}
- return devicePath;
}
+// Parse the device name to get I2C bus and address
std::pair<uint8_t, uint8_t> parseDeviceName(const std::string& devName)
{
- // Get I2C bus id and device address, e.g. 3-0068
- // is parsed to bus id 3, device address 0x68
+ // Get I2C bus and device address, e.g. 3-0068
+ // is parsed to bus 3, device address 0x68
auto pos = devName.find('-');
assert(pos != std::string::npos);
uint8_t busId = std::stoi(devName.substr(0, pos));
@@ -74,28 +113,176 @@
return {busId, devAddr};
}
+// Get the appropriate Updater class instance based PSU model number
+std::unique_ptr<updater::Updater> getClassInstance(
+ const std::string& model, const std::string& psuInventoryPath,
+ const std::string& devPath, const std::string& imageDir)
+{
+ if (model == "XXXX")
+ {
+ // XXXX updater class
+ }
+ return std::make_unique<Updater>(psuInventoryPath, devPath, imageDir);
+}
+
+// Function to locate FW file with model and extension bin or hex
+const std::string getFWFilenamePath(const std::string& directory)
+{
+ namespace fs = std::filesystem;
+ // Get the last part of the directory name (model number)
+ std::string model = fs::path(directory).filename().string();
+ for (const auto& entry : fs::directory_iterator(directory))
+ {
+ if (entry.is_regular_file())
+ {
+ std::string filename = entry.path().filename().string();
+
+ if ((filename.rfind(model, 0) == 0) &&
+ (filename.ends_with(".bin") || filename.ends_with(".hex")))
+ {
+ return directory + "/" + filename;
+ }
+ }
+ }
+ return "";
+}
+
+// Compute CRC-8 checksum for a vector of bytes
+uint8_t calculateCRC8(const std::vector<uint8_t>& data)
+{
+ uint8_t crc = CRC8_INITIAL;
+
+ for (const auto& byte : data)
+ {
+ crc ^= byte;
+ for (int i = 0; i < 8; ++i)
+ {
+ if (crc & 0x80)
+ crc = (crc << 1) ^ CRC8_POLYNOMIAL;
+ else
+ crc <<= 1;
+ }
+ }
+ return crc;
+}
+
+// Delay execution for a specified number of milliseconds
+void delay(const int& milliseconds)
+{
+ std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds));
+}
+
+// Convert big endian (32 bit integer) to a vector of little endian.
+std::vector<uint8_t> bigEndianToLittleEndian(const uint32_t bigEndianValue)
+{
+ std::vector<uint8_t> littleEndianBytes(4);
+
+ littleEndianBytes[3] = (bigEndianValue >> 24) & 0xFF;
+ littleEndianBytes[2] = (bigEndianValue >> 16) & 0xFF;
+ littleEndianBytes[1] = (bigEndianValue >> 8) & 0xFF;
+ littleEndianBytes[0] = bigEndianValue & 0xFF;
+ return littleEndianBytes;
+}
+
+// Validate the existence and size of a firmware file.
+bool validateFWFile(const std::string& fileName)
+{
+ // Ensure the file exists and get the file size.
+ if (!std::filesystem::exists(fileName))
+ {
+ log<level::ERR>(
+ std::format("Firmware file not found: {}", fileName).c_str());
+ return false;
+ }
+
+ // Check the file size
+ auto fileSize = std::filesystem::file_size(fileName);
+ if (fileSize == 0)
+ {
+ log<level::ERR>("Firmware file is empty");
+ return false;
+ }
+ return true;
+}
+
+// Open a firmware file for reading in binary mode.
+std::unique_ptr<std::ifstream> openFirmwareFile(const std::string& fileName)
+{
+ if (fileName.empty())
+ {
+ log<level::ERR>("Firmware file path is not provided");
+ return nullptr;
+ }
+ auto inputFile =
+ std::make_unique<std::ifstream>(fileName, std::ios::binary);
+ if (!inputFile->is_open())
+ {
+ log<level::ERR>(
+ std::format("Failed to open firmware file: {}", fileName).c_str());
+ return nullptr;
+ }
+ return inputFile;
+}
+
+// Read firmware bytes from input stream.
+std::vector<uint8_t> readFirmwareBytes(std::ifstream& inputFile,
+ const size_t numberOfBytesToRead)
+{
+ std::vector<uint8_t> readDataBytes(numberOfBytesToRead, 0xFF);
+ try
+ {
+ // Enable exceptions for failbit and badbit
+ inputFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
+ inputFile.read(reinterpret_cast<char*>(readDataBytes.data()),
+ numberOfBytesToRead);
+ size_t bytesRead = inputFile.gcount();
+ if (bytesRead != numberOfBytesToRead)
+ {
+ readDataBytes.resize(bytesRead);
+ }
+ }
+ catch (const std::ios_base::failure& e)
+ {
+ log<level::ERR>(
+ std::format("Error reading firmware: {}", e.what()).c_str());
+ readDataBytes.clear();
+ }
+ return readDataBytes;
+}
+
+// Wrapper to check existence of PSU JSON file.
+bool usePsuJsonFile()
+{
+ return version::utils::checkFileExists(PSU_JSON_PATH);
+}
} // namespace internal
-bool update(const std::string& psuInventoryPath, const std::string& imageDir)
+bool update(sdbusplus::bus_t& bus, const std::string& psuInventoryPath,
+ const std::string& imageDir)
{
- auto devPath = internal::getDevicePath(psuInventoryPath);
+ auto devPath = internal::getDevicePath(bus, psuInventoryPath);
+
if (devPath.empty())
{
return false;
}
- Updater updater(psuInventoryPath, devPath, imageDir);
- if (!updater.isReadyToUpdate())
+ std::filesystem::path fsPath(imageDir);
+
+ std::unique_ptr<updater::Updater> updaterPtr = internal::getClassInstance(
+ fsPath.filename().string(), psuInventoryPath, devPath, imageDir);
+
+ if (!updaterPtr->isReadyToUpdate())
{
log<level::ERR>("PSU not ready to update",
entry("PSU=%s", psuInventoryPath.c_str()));
return false;
}
- updater.bindUnbind(false);
- updater.createI2CDevice();
- int ret = updater.doUpdate();
- updater.bindUnbind(true);
+ updaterPtr->bindUnbind(false);
+ updaterPtr->createI2CDevice();
+ int ret = updaterPtr->doUpdate();
+ updaterPtr->bindUnbind(true);
return ret == 0;
}
@@ -210,7 +397,7 @@
// directly read the debugfs to get the status.
try
{
- auto path = internal::getDevicePath(p);
+ auto path = internal::getDevicePath(bus, p);
PMBus pmbus(path);
uint16_t statusWord = pmbus.read(STATUS_WORD, Type::Debug);
auto status0Vout = pmbus.insertPageNum(STATUS_VOUT, 0);
@@ -273,5 +460,4 @@
auto [id, addr] = internal::parseDeviceName(devName);
i2c = i2c::create(id, addr);
}
-
} // namespace updater
diff --git a/tools/power-utils/updater.hpp b/tools/power-utils/updater.hpp
index 458f651..1732f86 100644
--- a/tools/power-utils/updater.hpp
+++ b/tools/power-utils/updater.hpp
@@ -16,11 +16,15 @@
#pragma once
#include "i2c_interface.hpp"
+#include "version.hpp"
#include <sdbusplus/bus.hpp>
+#include <chrono>
#include <filesystem>
+#include <memory>
#include <string>
+#include <thread>
class TestUpdater;
@@ -32,12 +36,14 @@
/**
* Update PSU firmware
*
+ * @param[in] bus - The sdbusplus DBus bus connection
* @param[in] psuInventoryPath - The inventory path of the PSU
* @param[in] imageDir - The directory containing the PSU image
*
* @return true if successful, otherwise false
*/
-bool update(const std::string& psuInventoryPath, const std::string& imageDir);
+bool update(sdbusplus::bus_t& bus, const std::string& psuInventoryPath,
+ const std::string& imageDir);
class Updater
{
@@ -60,7 +66,7 @@
const std::string& imageDir);
/** @brief Destructor */
- ~Updater() = default;
+ virtual ~Updater() = default;
/** @brief Bind or unbind the driver
*
@@ -84,7 +90,7 @@
*
* @return 0 if success, otherwise non-zero
*/
- int doUpdate();
+ virtual int doUpdate();
/** @brief Create I2C device
*
@@ -93,6 +99,37 @@
*/
void createI2CDevice();
+ protected:
+ /** @brief Accessor for PSU inventory path */
+ const std::string& getPsuInventoryPath() const
+ {
+ return psuInventoryPath;
+ }
+
+ /** @brief Accessor for device path */
+ const std::string& getDevPath() const
+ {
+ return devPath;
+ }
+
+ /** @brief Accessor for device name */
+ const std::string& getDevName() const
+ {
+ return devName;
+ }
+
+ /** @brief Accessor for image directory */
+ const std::string& getImageDir() const
+ {
+ return imageDir;
+ }
+
+ /** @brief I2C interface accessor */
+ i2c::I2CInterface* getI2C()
+ {
+ return i2c.get();
+ }
+
private:
/** @brief The sdbusplus DBus bus connection */
sdbusplus::bus_t bus;
@@ -128,4 +165,122 @@
std::unique_ptr<i2c::I2CInterface> i2c;
};
+namespace internal
+{
+
+/**
+ * @brief Get the device name from the device path
+ *
+ * @param[in] devPath - PSU path
+ *
+ * @return device name e.g. 3-0068
+ */
+std::string getDeviceName(std::string devPath);
+
+/**
+ * @brief Function to get device path using DBus bus and PSU
+ * inventory Path
+ *
+ * @param[in] bus - The sdbusplus DBus bus connection
+ * @param[in] psuInventoryPath - PSU inventory path
+ *
+ * @return device path e.g. /sys/bus/i2c/devices/3-0068
+ */
+std::string getDevicePath(sdbusplus::bus_t& bus,
+ const std::string& psuInventoryPath);
+
+/**
+ * @brief Parse the device name to obtain bus and device address
+ *
+ * @param[in] devName - Device name
+ *
+ * @return bus and device address
+ */
+std::pair<uint8_t, uint8_t> parseDeviceName(const std::string& devName);
+
+/**
+ * @brief Factory function to create an Updater instance based on PSU model
+ * number
+ *
+ * @param[in] model - PSU model number
+ * @param[in] psuInventoryPath - PSU inventory path
+ * @param[in] devPath - Device path
+ * @param[in] imageDir - Image directory
+ *
+ * return pointer class based on the device PSU model.
+ */
+std::unique_ptr<updater::Updater> getClassInstance(
+ const std::string& model, const std::string& psuInventoryPath,
+ const std::string& devPath, const std::string& imageDir);
+
+/**
+ * @brief Retrieve the firmware filename path in the specified directory
+ *
+ * @param[in] directory - Path to FS directory
+ *
+ * @retun filename or null
+ */
+const std::string getFWFilenamePath(const std::string& directory);
+
+/**
+ * @brief Calculate CRC-8 for a data vector
+ *
+ * @param[in] data - Firmware data block
+ *
+ * @return CRC8
+ */
+uint8_t calculateCRC8(const std::vector<uint8_t>& data);
+
+/**
+ * @brief Delay execution in milliseconds
+ *
+ * @param[in] milliseconds - Time in milliseconds
+ */
+void delay(const int& milliseconds);
+
+/**
+ * @brief Convert a big-endian value to little-endian
+ *
+ * @param[in] bigEndianValue - Uint 32 bit value
+ *
+ * @return vector of little endians.
+ */
+std::vector<uint8_t> bigEndianToLittleEndian(const uint32_t bigEndianValue);
+
+/**
+ * @brief Validate the existence and size of a firmware file.
+ *
+ * @param[in] fileName - Firmware file name
+ *
+ * @return true for success or false
+ */
+bool validateFWFile(const std::string& fileName);
+
+/**
+ * @brief Open a firmware file for reading in binary mode.
+ *
+ * @param[in] fileName - Firmware file name
+ *
+ * @return pointer to firmware file stream
+ */
+std::unique_ptr<std::ifstream> openFirmwareFile(const std::string& fileName);
+
+/**
+ * @brief Read firmware bytes from file.
+ *
+ * @param[in] inputFile - Input file stream
+ * @param[in] numberOfBytesToRead - Number of bytes to read from firmware file.
+ *
+ * @return vector of data read
+ */
+std::vector<uint8_t> readFirmwareBytes(std::ifstream& inputFile,
+ const size_t numberOfBytesToRead);
+
+/**
+ * @brief Wrapper to check existence of PSU JSON file
+ *
+ * @return true or false (true if using JSON file)
+ */
+bool usePsuJsonFile();
+} // namespace internal
} // namespace updater