Add Fru Write Method

This allows us to write eeprom frus.

Change-Id: I76e39e667cdd6ed2edf151c788b2a1be0859ee23
Signed-off-by: James Feist <james.feist@linux.intel.com>
diff --git a/src/FruDevice.cpp b/src/FruDevice.cpp
index 4b993d3..606d4f8 100644
--- a/src/FruDevice.cpp
+++ b/src/FruDevice.cpp
@@ -17,6 +17,8 @@
 #include <Utils.hpp>
 #include <boost/container/flat_map.hpp>
 #include <ctime>
+#include <chrono>
+#include <thread>
 #include <sdbusplus/asio/connection.hpp>
 #include <sdbusplus/asio/object_server.hpp>
 #include <dbus/properties.hpp>
@@ -29,10 +31,13 @@
 #include <regex>
 #include <sys/inotify.h>
 #include <xyz/openbmc_project/Common/error.hpp>
+#include <errno.h>
 
 namespace fs = std::experimental::filesystem;
 static constexpr bool DEBUG = false;
 static size_t UNKNOWN_BUS_OBJECT_COUNT = 0;
+constexpr size_t MAX_FRU_SIZE = 512;
+constexpr size_t MAX_EEPROM_PAGE_INDEX = 255;
 
 const static constexpr char *BASEBOARD_FRU_LOCATION =
     "/etc/fru/baseboard.fru.bin";
@@ -484,6 +489,101 @@
     return true;
 }
 
+bool writeFru(uint8_t bus, uint8_t address, const std::vector<uint8_t> &fru)
+{
+    boost::container::flat_map<std::string, std::string> tmp;
+    if (fru.size() > MAX_FRU_SIZE)
+    {
+        std::cerr << "Invalid fru.size() during writeFru\n";
+        return false;
+    }
+    // verify legal fru by running it through fru parsing logic
+    if (!formatFru(reinterpret_cast<const std::vector<char> &>(fru), tmp))
+    {
+        std::cerr << "Invalid fru format during writeFru\n";
+        return false;
+    }
+    // baseboard fru
+    if (bus == 0 && address == 0)
+    {
+        std::ofstream file(BASEBOARD_FRU_LOCATION, std::ios_base::binary);
+        if (!file.good())
+        {
+            std::cerr << "Error opening file " << BASEBOARD_FRU_LOCATION
+                      << "\n";
+            throw sdbusplus::xyz::openbmc_project::Common::Error::
+                InternalFailure();
+            return false;
+        }
+        file.write(reinterpret_cast<const char *>(fru.data()), fru.size());
+        return file.good();
+    }
+    else
+    {
+        std::string i2cBus = "/dev/i2c-" + std::to_string(bus);
+
+        int file = open(i2cBus.c_str(), O_RDWR | O_CLOEXEC);
+        if (file < 0)
+        {
+            std::cerr << "unable to open i2c device " << i2cBus << "\n";
+            throw sdbusplus::xyz::openbmc_project::Common::Error::
+                InternalFailure();
+            return false;
+        }
+        if (ioctl(file, I2C_SLAVE_FORCE, address) < 0)
+        {
+            std::cerr << "unable to set device address\n";
+            close(file);
+            throw sdbusplus::xyz::openbmc_project::Common::Error::
+                InternalFailure();
+            return false;
+        }
+
+        constexpr const size_t RETRY_MAX = 2;
+        uint16_t index = 0;
+        size_t retries = RETRY_MAX;
+        while (index < fru.size())
+        {
+            if ((index && ((index % (MAX_EEPROM_PAGE_INDEX + 1)) == 0)) &&
+                (retries == RETRY_MAX))
+            {
+                // The 4K EEPROM only uses the A2 and A1 device address bits
+                // with the third bit being a memory page address bit.
+                if (ioctl(file, I2C_SLAVE_FORCE, ++address) < 0)
+                {
+                    std::cerr << "unable to set device address\n";
+                    close(file);
+                    throw sdbusplus::xyz::openbmc_project::Common::Error::
+                        InternalFailure();
+                    return false;
+                }
+            }
+
+            if (i2c_smbus_write_byte_data(file, index & 0xFF, fru[index]) < 0)
+            {
+                if (!retries--)
+                {
+                    std::cerr << "error writing fru: " << strerror(errno)
+                              << "\n";
+                    close(file);
+                    throw sdbusplus::xyz::openbmc_project::Common::Error::
+                        InternalFailure();
+                    return false;
+                }
+            }
+            else
+            {
+                retries = RETRY_MAX;
+                index++;
+            }
+            // most eeproms require 5-10ms between writes
+            std::this_thread::sleep_for(std::chrono::milliseconds(10));
+        }
+        close(file);
+        return true;
+    }
+}
+
 void rescanBusses(
     boost::asio::io_service &io, BusMap &busMap,
     boost::container::flat_map<std::pair<size_t, size_t>,
@@ -594,6 +694,20 @@
                 reinterpret_cast<std::vector<uint8_t> &>(device->second);
             return ret;
         });
+
+    iface->register_method("WriteFru", [&](const uint8_t bus,
+                                           const uint8_t address,
+                                           const std::vector<uint8_t> &data) {
+        if (!writeFru(bus, address, data))
+        {
+            throw sdbusplus::xyz::openbmc_project::Common::Error::
+                InvalidArgument();
+            return;
+        }
+        // schedule rescan on success
+        rescanBusses(io, busmap, dbusInterfaceMap, systemBus, objServer);
+
+    });
     iface->initialize();
 
     std::function<void(sdbusplus::message::message & message)> eventHandler =