Add MAC to FRU EEPROM

If FRU device and MAC offset configuration is found from entity-manager,
Set MAC command writes MAC to the FRU eeprom.

If FRU device config is not found or write to the device failed, MAC is
programmed using the existing method.

Tested:
issue ipmi command to write and read MAC

1. On system has the FRU EEPROM, MAC is programmed to the eeprom correctly.
	#read MAC
	ipmitool raw 0x30 0xa2 0
 	 01 ff ff ff ff ff ff
	#write MAC
	ipmitool raw 0x30 0xa1 0x00 0x16 0xc1 0x5f 0xb9 0x43 0x47
	#read MAC
	ipmitool raw 0x30 0xa2 0
	 01 16 c1 5f b9 43 47

2. On system that the eeprom is write protected, return as follow
	#read MAC
	ipmitool raw 0x30 0xa2 0
	01 ff ff ff ff ff ff
	#write MAC
	ipmitool raw 0x30 0xa1 0x0 0xbe 0x7f 0x17 0xff 0x59 0x7e
	Unable to send RAW command (channel=0x0 netfn=0x30 lun=0x0 cmd=0xa1 rsp=0xd5): Command not supported in present state
	#read MAC
	ipmitool raw 0x30 0xa2 0
	 01 ff ff ff ff ff ff

3. On system that does not has FRU EEPROM, MAC is programmed the same
way as before into filesystem.

Signed-off-by: Zhikui Ren <zhikui.ren@intel.com>
Change-Id: I74e6e4270523a9abb020745bf4b93b4411bce597
diff --git a/src/manufacturingcommands.cpp b/src/manufacturingcommands.cpp
index 9f16d95..c44aa70 100644
--- a/src/manufacturingcommands.cpp
+++ b/src/manufacturingcommands.cpp
@@ -820,10 +820,171 @@
 static constexpr const char* factoryEthAddrBaseFileName =
     "/var/sofs/factory-settings/network/mac/eth";
 
+using ObjectType = boost::container::flat_map<
+    std::string, boost::container::flat_map<std::string, DbusVariant>>;
+using ManagedObjectType =
+    boost::container::flat_map<sdbusplus::message::object_path, ObjectType>;
+
+bool findFruDevice(const std::shared_ptr<sdbusplus::asio::connection>& bus,
+                   boost::asio::yield_context& yield, uint64_t& macOffset,
+                   uint64_t& busNum, uint64_t& address)
+{
+    boost::system::error_code ec;
+
+    // GetAll the objects under service FruDevice
+    ec = boost::system::errc::make_error_code(boost::system::errc::success);
+    auto obj = bus->yield_method_call<ManagedObjectType>(
+        yield, ec, "xyz.openbmc_project.EntityManager", "/",
+        "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
+    if (ec)
+    {
+        phosphor::logging::log<phosphor::logging::level::ERR>(
+            "GetMangagedObjects failed",
+            phosphor::logging::entry("ERROR=%s", ec.message().c_str()));
+        return false;
+    }
+
+    for (const auto& [path, fru] : obj)
+    {
+        for (const auto& [intf, propMap] : fru)
+        {
+            if (intf == "xyz.openbmc_project.Inventory.Item.Board.Motherboard")
+            {
+                auto findBus = propMap.find("FruBus");
+                auto findAddress = propMap.find("FruAddress");
+                auto findMacOffset = propMap.find("MacOffset");
+                if (findBus == propMap.end() || findAddress == propMap.end() ||
+                    findMacOffset == propMap.end())
+                {
+                    continue;
+                }
+
+                auto fruBus = std::get_if<uint64_t>(&findBus->second);
+                auto fruAddress = std::get_if<uint64_t>(&findAddress->second);
+                auto macFruOffset =
+                    std::get_if<uint64_t>(&findMacOffset->second);
+                if (!fruBus || !fruAddress || !macFruOffset)
+                {
+                    phosphor::logging::log<phosphor::logging::level::INFO>(
+                        "ERROR: MotherBoard FRU config data type invalid, not "
+                        "used");
+                    return false;
+                }
+                busNum = *fruBus;
+                address = *fruAddress;
+                macOffset = *macFruOffset;
+                return true;
+            }
+        }
+    }
+    return false;
+}
+
+bool readMacFromFru(ipmi::Context::ptr ctx, uint8_t macIndex,
+                    std::array<uint8_t, maxEthSize>& ethData)
+{
+    constexpr uint64_t fruEnd = 0xff;
+    uint64_t macOffset = fruEnd;
+    uint64_t fruBus = 0;
+    uint64_t fruAddress = 0;
+
+    if (findFruDevice(ctx->bus, ctx->yield, macOffset, fruBus, fruAddress))
+    {
+        phosphor::logging::log<phosphor::logging::level::INFO>(
+            "Found mac fru",
+            phosphor::logging::entry("BUS=%d", static_cast<uint8_t>(fruBus)),
+            phosphor::logging::entry("ADDRESS=%d",
+                                     static_cast<uint8_t>(fruAddress)));
+
+        macOffset += macIndex * maxEthSize;
+        if ((macOffset + maxEthSize) > fruEnd)
+        {
+            phosphor::logging::log<phosphor::logging::level::ERR>(
+                "ERROR: read fru mac failed, offset invalid");
+            return false;
+        }
+        std::vector<uint8_t> writeData;
+        writeData.push_back(static_cast<uint8_t>(macOffset & 0xff));
+        std::vector<uint8_t> readBuf(maxEthSize);
+        std::string i2cBus = "/dev/i2c-" + std::to_string(fruBus);
+        ipmi::Cc retI2C =
+            ipmi::i2cWriteRead(i2cBus, fruAddress, writeData, readBuf);
+        if (retI2C == ipmi::ccSuccess)
+        {
+            std::copy(readBuf.begin(), readBuf.end(), ethData.data());
+            return true;
+        }
+    }
+    return false;
+}
+
+ipmi::Cc writeMacToFru(ipmi::Context::ptr ctx, uint8_t macIndex,
+                       std::array<uint8_t, maxEthSize>& ethData)
+{
+    constexpr uint64_t fruEnd = 0xff;
+    uint64_t macOffset = fruEnd;
+    uint64_t fruBus = 0;
+    uint64_t fruAddress = 0;
+
+    if (findFruDevice(ctx->bus, ctx->yield, macOffset, fruBus, fruAddress))
+    {
+        phosphor::logging::log<phosphor::logging::level::INFO>(
+            "Found mac fru",
+            phosphor::logging::entry("BUS=%d", static_cast<uint8_t>(fruBus)),
+            phosphor::logging::entry("ADDRESS=%d",
+                                     static_cast<uint8_t>(fruAddress)));
+
+        macOffset += macIndex * maxEthSize;
+        if ((macOffset + maxEthSize) > fruEnd)
+        {
+            phosphor::logging::log<phosphor::logging::level::ERR>(
+                "ERROR: write mac fru failed, offset invalid.");
+            return ipmi::ccParmOutOfRange;
+        }
+        std::vector<uint8_t> writeData;
+        writeData.push_back(static_cast<uint8_t>(macOffset & 0xff));
+        std::for_each(ethData.cbegin(), ethData.cend(),
+                      [&](uint8_t i) { writeData.push_back(i); });
+
+        std::string i2cBus = "/dev/i2c-" + std::to_string(fruBus);
+        std::vector<uint8_t> readBuf;
+        ipmi::Cc ret =
+            ipmi::i2cWriteRead(i2cBus, fruAddress, writeData, readBuf);
+        switch (ret)
+        {
+            case ipmi::ccSuccess:
+                // chip is write protected, if write is success but fails verify
+                writeData.resize(1);
+                readBuf.resize(maxEthSize);
+                if (ipmi::ccSuccess ==
+                    ipmi::i2cWriteRead(i2cBus, fruAddress, writeData, readBuf))
+                {
+                    if (std::equal(ethData.begin(), ethData.end(),
+                                   readBuf.begin()))
+                    {
+                        return ipmi::ccSuccess;
+                    }
+                    phosphor::logging::log<phosphor::logging::level::INFO>(
+                        "INFO: write mac fru verify failed, fru may be write "
+                        "protected.");
+                }
+                return ipmi::ccCommandNotAvailable;
+            default: // assumes no actual eeprom for other failure
+                phosphor::logging::log<phosphor::logging::level::ERR>(
+                    "ERROR: write mac fru failed, assume no eeprom is "
+                    "available.");
+                break;
+        }
+    }
+    // no FRU eeprom found
+    return ipmi::ccDestinationUnavailable;
+}
+
 ipmi::RspType<> setManufacturingData(ipmi::Context::ptr ctx, uint8_t dataType,
                                      std::array<uint8_t, maxEthSize> ethData)
 {
-    // mfg filter logic will restrict this command executing only in mfg mode.
+    // mfg filter logic will restrict this command executing only in mfg
+    // mode.
     if (dataType >= maxSupportedEth)
     {
         return ipmi::responseParmOutOfRange();
@@ -831,6 +992,14 @@
 
     constexpr uint8_t invalidData = 0;
     constexpr uint8_t validData = 1;
+
+    ipmi::Cc ret = writeMacToFru(ctx, dataType, ethData);
+    if (ret != ipmi::ccDestinationUnavailable)
+    {
+        resetMtmTimer(ctx);
+        return response(ret);
+    }
+
     constexpr uint8_t ethAddrStrSize =
         19; // XX:XX:XX:XX:XX:XX + \n + null termination;
     std::vector<uint8_t> buff(ethAddrStrSize);
@@ -857,7 +1026,8 @@
 ipmi::RspType<uint8_t, std::array<uint8_t, maxEthSize>>
     getManufacturingData(ipmi::Context::ptr ctx, uint8_t dataType)
 {
-    // mfg filter logic will restrict this command executing only in mfg mode.
+    // mfg filter logic will restrict this command executing only in mfg
+    // mode.
     if (dataType >= maxSupportedEth)
     {
         return ipmi::responseParmOutOfRange();
@@ -871,6 +1041,11 @@
                            std::ifstream::in);
     if (!iEthFile.good())
     {
+        if (readMacFromFru(ctx, dataType, ethData))
+        {
+            resetMtmTimer(ctx);
+            return ipmi::responseSuccess(validData, ethData);
+        }
         return ipmi::responseSuccess(invalidData, ethData);
     }
     std::string ethStr;
@@ -884,8 +1059,8 @@
     return ipmi::responseSuccess(validData, ethData);
 }
 
-/** @brief implements slot master write read IPMI command which can be used for
- * low-level I2C/SMBus write, read or write-read access for PCIE slots
+/** @brief implements slot master write read IPMI command which can be used
+ * for low-level I2C/SMBus write, read or write-read access for PCIE slots
  * @param reserved - skip 6 bit
  * @param addressType - address type
  * @param bbSlotNum - baseboard slot number
@@ -936,8 +1111,8 @@
         return ipmi::responseInvalidFieldRequest();
     }
 
-    // Allow single byte write as it is offset byte to read the data, rest allow
-    // only in Special mode.
+    // Allow single byte write as it is offset byte to read the data, rest
+    // allow only in Special mode.
     if (writeCount > 1)
     {
         if (mtm.getMfgMode() == SpecialMode::none)
@@ -973,9 +1148,9 @@
 
 ipmi::RspType<> clearCMOS()
 {
-    // There is an i2c device on bus 4, the slave address is 0x38. Based on the
-    // spec, writing 0x1 to address 0x61 on this device, will trigger the clear
-    // CMOS action.
+    // There is an i2c device on bus 4, the slave address is 0x38. Based on
+    // the spec, writing 0x1 to address 0x61 on this device, will trigger
+    // the clear CMOS action.
     constexpr uint8_t slaveAddr = 0x38;
     std::string i2cBus = "/dev/i2c-4";
     std::vector<uint8_t> writeData = {0x61, 0x1};