i2c-vr: mps: extend MPS image parser to support multiple formats

Different MPS VR modules use varying image formats. This change
enhances the common MPS image parser to handle multiple image types,
improving its flexibility and reusability.

- Add support for 7-column ATE images without write/read command info.
- Add support for 8-column ATE images with write/read command info,
  which specifying byte, word, block, or process call operations.

Tested successfully by updating Yosemite5 VRs.

Change-Id: I9b60634c3a051c30ab2f56b895c2ef8db047ad13
Signed-off-by: Kevin Tung <kevin.tung.openbmc@gmail.com>
diff --git a/i2c-vr/mps/mp297x.cpp b/i2c-vr/mps/mp297x.cpp
index e9ae9cd..febff3e 100644
--- a/i2c-vr/mps/mp297x.cpp
+++ b/i2c-vr/mps/mp297x.cpp
@@ -76,7 +76,7 @@
 sdbusplus::async::task<bool> MP297X::verifyImage(const uint8_t* image,
                                                  size_t imageSize)
 {
-    if (!co_await parseImage(image, imageSize))
+    if (!co_await parseImage(image, imageSize, MPSImageType::type1))
     {
         error("Image verification failed: image parsing failed");
         co_return false;
diff --git a/i2c-vr/mps/mp2x6xx.cpp b/i2c-vr/mps/mp2x6xx.cpp
index a558f04..4d53b40 100644
--- a/i2c-vr/mps/mp2x6xx.cpp
+++ b/i2c-vr/mps/mp2x6xx.cpp
@@ -76,7 +76,7 @@
 sdbusplus::async::task<bool> MP2X6XX::verifyImage(const uint8_t* image,
                                                   size_t imageSize)
 {
-    if (!co_await parseImage(image, imageSize))
+    if (!co_await parseImage(image, imageSize, MPSImageType::type1))
     {
         error("Image verification failed: image parsing failed");
         co_return false;
diff --git a/i2c-vr/mps/mp5998.cpp b/i2c-vr/mps/mp5998.cpp
index 85939a5..5e189e7 100644
--- a/i2c-vr/mps/mp5998.cpp
+++ b/i2c-vr/mps/mp5998.cpp
@@ -57,7 +57,7 @@
 sdbusplus::async::task<bool> MP5998::verifyImage(const uint8_t* image,
                                                  size_t imageSize)
 {
-    if (!co_await parseImage(image, imageSize))
+    if (!co_await parseImage(image, imageSize, MPSImageType::type1))
     {
         error("Image verification failed: image parsing failed");
         co_return false;
diff --git a/i2c-vr/mps/mps.cpp b/i2c-vr/mps/mps.cpp
index ee0abf9..a82d5a6 100644
--- a/i2c-vr/mps/mps.cpp
+++ b/i2c-vr/mps/mps.cpp
@@ -10,9 +10,20 @@
            !tokens[0].starts_with('*');
 }
 
-MPSData MPSImageParser::extractData(const std::vector<std::string_view>& tokens)
+MPSData MPSImageParser::extractType0Data(
+    const std::vector<std::string_view>& tokens)
 {
     MPSData data;
+    static constexpr size_t type0TokensSize = 7;
+
+    if (tokens.size() != type0TokensSize)
+    {
+        lg2::error("Invalid token count for Type0 image line: "
+                   "expected {EXPECTED}, got {ACTUAL}",
+                   "EXPECTED", type0TokensSize, "ACTUAL", tokens.size());
+        return data;
+    }
+
     data.page = getVal<uint8_t>(tokens, ATE::pageNum);
     data.addr = getVal<uint8_t>(tokens, ATE::regAddrHex);
 
@@ -28,9 +39,96 @@
     return data;
 }
 
-std::vector<MPSData> MPSImageParser::getRegistersData()
+MPSData MPSImageParser::extractType1Data(
+    const std::vector<std::string_view>& tokens)
 {
-    std::vector<MPSData> registersData;
+    MPSData data;
+    static constexpr size_t type0TokensSize = 8;
+
+    if (tokens.size() != type0TokensSize)
+    {
+        lg2::error("Invalid token count for Type1 image line: "
+                   "expected {EXPECTED}, got {ACTUAL}",
+                   "EXPECTED", type0TokensSize, "ACTUAL", tokens.size());
+        return data;
+    }
+
+    data.page = getVal<uint8_t>(tokens, ATE::pageNum);
+    auto addr = getVal<uint16_t>(tokens, ATE::regAddrHex);
+    auto cmdType = getVal<std::string>(tokens, ATE::writeType);
+    int blockDataBytes = 0;
+
+    if (cmdType.starts_with("P"))
+    {
+        // Check if these tokens represent a P1 or P2 process call.
+        // The upper byte of 'addr' is the command code, and the lower byte
+        // is the LSB of the data.
+        // Example:
+        // addr = 0x0F11 and data = 0x18, this sends 0x1811 to command 0x0F
+        static constexpr uint16_t processCallAddrMask = 0xFF00;
+        data.data[0] = static_cast<uint8_t>(addr & ~processCallAddrMask);
+        data.data[1] = getVal<uint8_t>(tokens, ATE::regDataHex);
+        data.addr = static_cast<uint8_t>((addr & processCallAddrMask) >> 8);
+        data.length = 2;
+        return data;
+    }
+    else if (cmdType.starts_with("B"))
+    {
+        // Command types starting with 'B' indicate block r/w commands.
+        // The number following 'B' specifies the number of data bytes.
+        if (cmdType.size() > 1 && std::isdigit(cmdType[1]))
+        {
+            blockDataBytes = std::stoi(cmdType.substr(1));
+        }
+    }
+
+    std::string regData = getVal<std::string>(tokens, ATE::regDataHex);
+    size_t byteCount = std::min(regData.length() / 2, size_t(4));
+    size_t dataIndex = 0;
+
+    if (blockDataBytes > 0)
+    {
+        data.data[dataIndex++] = static_cast<uint8_t>(blockDataBytes);
+    }
+
+    for (size_t i = 0; i < byteCount; ++i)
+    {
+        data.data[dataIndex + (byteCount - 1 - i)] = static_cast<uint8_t>(
+            std::stoul(regData.substr(i * 2, 2), nullptr, 16));
+    }
+    data.length = static_cast<uint8_t>(dataIndex + byteCount);
+    data.addr = getVal<uint8_t>(tokens, ATE::regAddrHex);
+    return data;
+}
+
+std::vector<MPSData> MPSImageParser::parse(
+    const uint8_t* image, size_t imageSize, MPSImageType imageType)
+{
+    lineTokens = TokenizedLines(image, imageSize);
+    std::vector<MPSData> results;
+
+    using ExtractDataFunc =
+        std::function<MPSData(const std::vector<std::string_view>&)>;
+    ExtractDataFunc extractDataFunc;
+
+    switch (imageType)
+    {
+        case MPSImageType::type0:
+            extractDataFunc = [this](const auto& tokens) {
+                return extractType0Data(tokens);
+            };
+            break;
+        case MPSImageType::type1:
+            extractDataFunc = [this](const auto& tokens) {
+                return extractType1Data(tokens);
+            };
+            break;
+        default:
+            lg2::error("Unsupported or unknown MPS image type: {TYPE}", "TYPE",
+                       static_cast<int>(imageType));
+            return results;
+    }
+
     for (const auto& tokens : lineTokens)
     {
         if (tokens[0].starts_with("END"))
@@ -40,22 +138,36 @@
 
         if (isValidDataTokens(tokens))
         {
-            registersData.push_back(extractData(tokens));
+            auto data = extractDataFunc(tokens);
+            if (data.length == 0)
+            {
+                return {};
+            }
+            results.push_back(data);
         }
     }
-    return registersData;
+
+    return results;
 }
 
 sdbusplus::async::task<bool> MPSVoltageRegulator::parseImage(
-    const uint8_t* image, size_t imageSize)
+    const uint8_t* image, size_t imageSize, MPSImageType imageType)
 {
-    parser = std::make_unique<MPSImageParser>(image, imageSize);
-
     configuration = std::make_unique<MPSConfig>();
-    configuration->registersData = parser->getRegistersData();
 
-    if (!co_await parseDeviceConfiguration())
+    try
     {
+        configuration->registersData =
+            parser->parse(image, imageSize, imageType);
+
+        if (!co_await parseDeviceConfiguration())
+        {
+            co_return false;
+        }
+    }
+    catch (const std::exception& e)
+    {
+        lg2::error("Failed to parse MPS image: {ERR}", "ERR", e.what());
         co_return false;
     }
 
diff --git a/i2c-vr/mps/mps.hpp b/i2c-vr/mps/mps.hpp
index 73361af..078cdf5 100644
--- a/i2c-vr/mps/mps.hpp
+++ b/i2c-vr/mps/mps.hpp
@@ -32,6 +32,23 @@
     colCount,
 };
 
+/**
+ * @brief Represents the format of an MPS configuration image.
+ *
+ * type0: 7-columns ATE image without write/read command information.
+ *
+ * type1: 8-columns ATE image that includes write/read command information,
+ *        specifying byte, word, block, or process call operations.
+ *
+ * typeUnknown: Unknown or unsupported image format.
+ */
+enum class MPSImageType : uint8_t
+{
+    type0 = 0,
+    type1,
+    typeUnknown = 0xFF
+};
+
 enum class MPSPage : uint8_t
 {
     page0 = 0,
@@ -68,6 +85,7 @@
 class TokenizedLines
 {
   public:
+    TokenizedLines() = default;
     TokenizedLines(const uint8_t* d, size_t s) :
         data(reinterpret_cast<const char*>(d), s)
     {}
@@ -177,9 +195,12 @@
 class MPSImageParser
 {
   public:
-    MPSImageParser(const uint8_t* image, size_t imageSize) :
-        lineTokens(image, imageSize)
-    {}
+    MPSImageParser() = default;
+    virtual ~MPSImageParser() = default;
+    MPSImageParser(const MPSImageParser&) = delete;
+    MPSImageParser& operator=(const MPSImageParser&) = delete;
+    MPSImageParser(MPSImageParser&&) = default;
+    MPSImageParser& operator=(MPSImageParser&&) = default;
 
     template <typename>
     inline static constexpr bool always_false = false;
@@ -236,16 +257,17 @@
     static bool isValidDataTokens(const std::vector<std::string_view>& tokens);
 
     /**
-     * @brief Convert tokenized line into MPSData structure.
+     * @brief Parse image buffer into a list of MPSData entries.
      */
-    MPSData extractData(const std::vector<std::string_view>& tokens);
-
-    /**
-     * @brief Collect all register data entries from the parsed image.
-     */
-    std::vector<MPSData> getRegistersData();
+    virtual std::vector<MPSData> parse(
+        const uint8_t* image, size_t imageSize,
+        MPSImageType imageType = MPSImageType::typeUnknown);
 
     TokenizedLines lineTokens;
+
+  private:
+    MPSData extractType0Data(const std::vector<std::string_view>& tokens);
+    MPSData extractType1Data(const std::vector<std::string_view>& tokens);
 };
 
 /**
@@ -271,8 +293,9 @@
      * @param imageSize Size of the image data
      * @return async task returning true if parsing succeeds
      */
-    sdbusplus::async::task<bool> parseImage(const uint8_t* image,
-                                            size_t imageSize);
+    sdbusplus::async::task<bool> parseImage(
+        const uint8_t* image, size_t imageSize,
+        MPSImageType imageType = MPSImageType::typeUnknown);
 
     /**
      * @brief Group register data by page, optionally masked and shifted.
@@ -285,7 +308,7 @@
 
   protected:
     phosphor::i2c::I2C i2cInterface;
-    std::unique_ptr<MPSImageParser> parser;
+    std::unique_ptr<MPSImageParser> parser = std::make_unique<MPSImageParser>();
     std::unique_ptr<MPSConfig> configuration;
 };