fru-device: retry with bytewise read

certain powersupplies, e.g. SuperMicro PWS-920P-SQ
require that their FRU eeprom be read bytewise.
otherwise, only garbage is read.

Tested: on Tyan S8030 board. FRU eeprom is now read correctly.

Change-Id: I36a0ce8d8f955d4fe3867dbd63fefda63b1a9b13
Signed-off-by: Alexander Hansen <alexander.hansen@9elements.com>
diff --git a/src/fru_device.cpp b/src/fru_device.cpp
index 2045b4e..11f2872 100644
--- a/src/fru_device.cpp
+++ b/src/fru_device.cpp
@@ -262,13 +262,30 @@
     return (ret == static_cast<int>(msgs.size())) ? msgs[1].len : -1;
 }
 
-static int64_t readBlockData(bool is16bit, int file, uint16_t address,
-                             off_t offset, size_t len, uint8_t* buf)
+static int64_t readData(bool is16bit, bool isBytewise, int file,
+                        uint16_t address, off_t offset, size_t len,
+                        uint8_t* buf)
 {
     if (!is16bit)
     {
-        return i2c_smbus_read_i2c_block_data(file, static_cast<uint8_t>(offset),
-                                             len, buf);
+        if (!isBytewise)
+        {
+            return i2c_smbus_read_i2c_block_data(
+                file, static_cast<uint8_t>(offset), len, buf);
+        }
+
+        std::span<uint8_t> bufspan{buf, len};
+        for (size_t i = 0; i < len; i++)
+        {
+            int byte = i2c_smbus_read_byte_data(
+                file, static_cast<uint8_t>(offset + i));
+            if (byte < 0)
+            {
+                return static_cast<int64_t>(byte);
+            }
+            bufspan[i] = static_cast<uint8_t>(byte);
+        }
+        return static_cast<int64_t>(len);
     }
 
     offset = htobe16(offset);
@@ -296,10 +313,11 @@
         return readFromEeprom(file, offset, length, outbuf);
     };
     FRUReader reader(std::move(readFunc));
-    std::vector<uint8_t> device = readFRUContents(reader, errorMessage);
+    std::pair<std::vector<uint8_t>, bool> pair = readFRUContents(reader,
+                                                                 errorMessage);
 
     close(file);
-    return device;
+    return pair.first;
 }
 
 std::set<int> findI2CEeproms(int i2cBus,
@@ -468,19 +486,37 @@
 
             auto readFunc = [is16BitBool, file, ii](off_t offset, size_t length,
                                                     uint8_t* outbuf) {
-                return readBlockData(is16BitBool, file, ii, offset, length,
-                                     outbuf);
+                return readData(is16BitBool, false, file, ii, offset, length,
+                                outbuf);
             };
             FRUReader reader(std::move(readFunc));
             std::string errorMessage = "bus " + std::to_string(bus) +
                                        " address " + std::to_string(ii);
-            std::vector<uint8_t> device = readFRUContents(reader, errorMessage);
-            if (device.empty())
+            std::pair<std::vector<uint8_t>, bool> pair =
+                readFRUContents(reader, errorMessage);
+            const bool foundHeader = pair.second;
+
+            if (!foundHeader && !is16BitBool)
+            {
+                // certain FRU eeproms require bytewise reading.
+                // otherwise garbage is read. e.g. SuperMicro PWS 920P-SQ
+
+                auto readFunc = [is16BitBool, file, ii](off_t offset,
+                                                        size_t length,
+                                                        uint8_t* outbuf) {
+                    return readData(is16BitBool, true, file, ii, offset, length,
+                                    outbuf);
+                };
+                FRUReader readerBytewise(std::move(readFunc));
+                pair = readFRUContents(readerBytewise, errorMessage);
+            }
+
+            if (pair.first.empty())
             {
                 continue;
             }
 
-            devices->emplace(ii, device);
+            devices->emplace(ii, pair.first);
             fruAddresses[bus].insert(ii);
         }
         return 1;
diff --git a/src/fru_utils.cpp b/src/fru_utils.cpp
index ab3283b..0bb92b9 100644
--- a/src/fru_utils.cpp
+++ b/src/fru_utils.cpp
@@ -743,15 +743,15 @@
     return false;
 }
 
-std::vector<uint8_t> readFRUContents(FRUReader& reader,
-                                     const std::string& errorHelp)
+std::pair<std::vector<uint8_t>, bool>
+    readFRUContents(FRUReader& reader, const std::string& errorHelp)
 {
     std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> blockData{};
     off_t baseOffset = 0x0;
 
     if (!findFRUHeader(reader, errorHelp, blockData, baseOffset))
     {
-        return {};
+        return {{}, false};
     }
 
     std::vector<uint8_t> device;
@@ -779,7 +779,7 @@
         {
             std::cerr << "Fru area offsets are not in required order as per "
                          "Section 17 of Fru specification\n";
-            return {};
+            return {{}, true};
         }
         prevOffset = areaOffset;
 
@@ -797,7 +797,7 @@
         {
             std::cerr << "failed to read " << errorHelp << " base offset "
                       << baseOffset << "\n";
-            return {};
+            return {{}, true};
         }
 
         // Ignore data type (blockData is already unsigned).
@@ -827,7 +827,7 @@
             {
                 std::cerr << "failed to read " << errorHelp << " base offset "
                           << baseOffset << "\n";
-                return {};
+                return {{}, true};
             }
 
             // Ok, let's check the record length, which is in bytes (unsigned,
@@ -859,7 +859,7 @@
         {
             std::cerr << "failed to read " << errorHelp << " base offset "
                       << baseOffset << "\n";
-            return {};
+            return {{}, true};
         }
 
         device.insert(device.end(), blockData.begin(),
@@ -869,7 +869,7 @@
         fruLength -= std::min(requestLength, fruLength);
     }
 
-    return device;
+    return {device, true};
 }
 
 unsigned int getHeaderAreaFieldOffset(fruAreas area)
diff --git a/src/fru_utils.hpp b/src/fru_utils.hpp
index e2c6a54..c2b81cc 100644
--- a/src/fru_utils.hpp
+++ b/src/fru_utils.hpp
@@ -155,9 +155,10 @@
 /// \brief Read and validate FRU contents.
 /// \param reader the FRUReader to read via
 /// \param errorHelp and a helper string for failures
-/// \return the FRU contents from the file
-std::vector<uint8_t> readFRUContents(FRUReader& reader,
-                                     const std::string& errorHelp);
+/// \return the FRU contents from the file and bool indicating if the FRU Header
+/// was found
+std::pair<std::vector<uint8_t>, bool>
+    readFRUContents(FRUReader& reader, const std::string& errorHelp);
 
 /// \brief Validate an IPMI FRU common header
 /// \param blockData the bytes comprising the common header