support specifying MB FRU eeprom path

The MB FRUID should be exposed with ID 0 for BIOS to get the data to
fill out SMBIOS table during POST. However, current IDs depend on the
ordering of the FruDevice object paths.

(1) Using the object with "Type: EEPROM" and "Name: MB FRU" in the
EntityManager to figure out the MB FRU eeprom path. Other FRUIDs still
depend on the ordering of the FruDevice object paths.

example of the entity-manager config:
```
    {
        "Address": "$address",
        "Bus": "$bus",
        "Name": "MB FRU",
        "Type": "EEPROM"
    },
```

(2) For System GUID that BIOS populates into SMBIOS table during POST,
also use the MB FRU eeprom path instead of the hardcoding eeprom path.

Change-Id: If950c471f92b99180bab1a629d56d3a912d3b5f4
Signed-off-by: Cosmo Chou <cosmo.chou@quantatw.com>
diff --git a/include/commandutils.hpp b/include/commandutils.hpp
index 5658c1c..f52b9b7 100644
--- a/include/commandutils.hpp
+++ b/include/commandutils.hpp
@@ -97,6 +97,8 @@
     }
 }
 
+std::optional<std::pair<uint8_t, uint8_t>> getMbFruDevice(void);
+
 namespace ipmi
 {
 using DbusVariant = std::variant<std::string, bool, uint8_t, uint16_t, int16_t,
diff --git a/meson.build b/meson.build
index 77b19e5..f22dddf 100644
--- a/meson.build
+++ b/meson.build
@@ -89,6 +89,7 @@
 
 zfboemcmds_lib = library(
   'zfboemcmds',
+  'src/commandutils.cpp',
   'src/oemcommands.cpp',
   'src/appcommands.cpp',
   'src/storagecommands.cpp',
diff --git a/src/appcommands.cpp b/src/appcommands.cpp
index ed9e365..ecbc2b4 100644
--- a/src/appcommands.cpp
+++ b/src/appcommands.cpp
@@ -72,21 +72,34 @@
     int fd = -1;
     ssize_t bytes_rd;
     int ret = 0;
+    std::string eepromPath = FRU_EEPROM;
+
+    // find the eeprom path of MB FRU
+    auto device = getMbFruDevice();
+    if (device)
+    {
+        auto [bus, address] = *device;
+        std::stringstream ss;
+        ss << "/sys/bus/i2c/devices/" << static_cast<int>(bus) << "-"
+           << std::setw(4) << std::setfill('0') << std::hex
+           << static_cast<int>(address) << "/eeprom";
+        eepromPath = ss.str();
+    }
 
     errno = 0;
 
     // Check if file is present
-    if (access(FRU_EEPROM, F_OK) == -1)
+    if (access(eepromPath.c_str(), F_OK) == -1)
     {
-        std::cerr << "Unable to access: " << FRU_EEPROM << std::endl;
+        std::cerr << "Unable to access: " << eepromPath << std::endl;
         return errno;
     }
 
     // Open the file
-    fd = open(FRU_EEPROM, O_RDONLY);
+    fd = open(eepromPath.c_str(), O_RDONLY);
     if (fd == -1)
     {
-        std::cerr << "Unable to open: " << FRU_EEPROM << std::endl;
+        std::cerr << "Unable to open: " << eepromPath << std::endl;
         return errno;
     }
 
diff --git a/src/commandutils.cpp b/src/commandutils.cpp
new file mode 100644
index 0000000..007bb09
--- /dev/null
+++ b/src/commandutils.cpp
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c)  2023-present Facebook.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <commandutils.hpp>
+#include <ipmid/utils.hpp>
+#include <phosphor-logging/log.hpp>
+
+std::optional<std::pair<uint8_t, uint8_t>> getMbFruDevice(void)
+{
+    static std::optional<std::pair<uint8_t, uint8_t>> device = std::nullopt;
+
+    if (device)
+    {
+        return device;
+    }
+
+    sdbusplus::bus_t dbus(ipmid_get_sd_bus_connection());
+    auto mapperCall = dbus.new_method_call("xyz.openbmc_project.ObjectMapper",
+                                           "/xyz/openbmc_project/object_mapper",
+                                           "xyz.openbmc_project.ObjectMapper",
+                                           "GetSubTreePaths");
+    static constexpr int32_t depth = 0;
+    static constexpr auto iface = "xyz.openbmc_project.Configuration.EEPROM";
+    static constexpr auto entityManager = "xyz.openbmc_project.EntityManager";
+    static constexpr std::array<const char*, 1> interface = {iface};
+    mapperCall.append("/xyz/openbmc_project/inventory/", depth, interface);
+
+    std::vector<std::string> paths;
+    try
+    {
+        auto resp = dbus.call(mapperCall);
+        resp.read(paths);
+    }
+    catch (const sdbusplus::exception_t& e)
+    {
+        phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
+        return std::nullopt;
+    }
+
+    const std::string suffix = "/MB_FRU";
+    for (const auto& path : paths)
+    {
+        if (path.ends_with(suffix))
+        {
+            uint8_t fruBus = std::get<uint64_t>(
+                ipmi::getDbusProperty(dbus, entityManager, path, iface, "Bus"));
+            uint8_t fruAddr = std::get<uint64_t>(ipmi::getDbusProperty(
+                dbus, entityManager, path, iface, "Address"));
+            device = std::make_pair(fruBus, fruAddr);
+            break;
+        }
+    }
+
+    return device;
+}
diff --git a/src/oemcommands.cpp b/src/oemcommands.cpp
index 3206a0a..8d1d0d5 100644
--- a/src/oemcommands.cpp
+++ b/src/oemcommands.cpp
@@ -1255,21 +1255,34 @@
     int fd = -1;
     ssize_t len;
     int ret = 0;
+    std::string eepromPath = FRU_EEPROM;
+
+    // find the eeprom path of MB FRU
+    auto device = getMbFruDevice();
+    if (device)
+    {
+        auto [bus, address] = *device;
+        std::stringstream ss;
+        ss << "/sys/bus/i2c/devices/" << static_cast<int>(bus) << "-"
+           << std::setw(4) << std::setfill('0') << std::hex
+           << static_cast<int>(address) << "/eeprom";
+        eepromPath = ss.str();
+    }
 
     errno = 0;
 
     // Check if file is present
-    if (access(FRU_EEPROM, F_OK) == -1)
+    if (access(eepromPath.c_str(), F_OK) == -1)
     {
-        std::cerr << "Unable to access: " << FRU_EEPROM << std::endl;
+        std::cerr << "Unable to access: " << eepromPath << std::endl;
         return errno;
     }
 
     // Open the file
-    fd = open(FRU_EEPROM, O_WRONLY);
+    fd = open(eepromPath.c_str(), O_WRONLY);
     if (fd == -1)
     {
-        std::cerr << "Unable to open: " << FRU_EEPROM << std::endl;
+        std::cerr << "Unable to open: " << eepromPath << std::endl;
         return errno;
     }
 
diff --git a/src/storagecommands.cpp b/src/storagecommands.cpp
index d64531e..022e377 100644
--- a/src/storagecommands.cpp
+++ b/src/storagecommands.cpp
@@ -236,9 +236,17 @@
 
     deviceHashes.clear();
 
-    // hash the object paths to create unique device id's. increment on
-    // collision
-    [[maybe_unused]] std::hash<std::string> hasher;
+    uint8_t fruHash = 0;
+    uint8_t mbFruBus = 0, mbFruAddr = 0;
+
+    auto device = getMbFruDevice();
+    if (device)
+    {
+        std::tie(mbFruBus, mbFruAddr) = *device;
+        deviceHashes.emplace(0, std::make_pair(mbFruBus, mbFruAddr));
+        fruHash++;
+    }
+
     for (const auto& fru : frus)
     {
         auto fruIface = fru.second.find("xyz.openbmc_project.FruDevice");
@@ -260,37 +268,10 @@
 
         uint8_t fruBus = std::get<uint32_t>(busFind->second);
         uint8_t fruAddr = std::get<uint32_t>(addrFind->second);
-
-        uint8_t fruHash = 0;
-        // Need to revise this strategy for dev id
-        /*
-        if (fruBus != 0 || fruAddr != 0)
+        if (fruBus != mbFruBus || fruAddr != mbFruAddr)
         {
-          fruHash = hasher(fru.first.str);
-          // can't be 0xFF based on spec, and 0 is reserved for baseboard
-          if (fruHash == 0 || fruHash == 0xFF)
-          {
-            fruHash = 1;
-          }
-        }
-        */
-        std::pair<uint8_t, uint8_t> newDev(fruBus, fruAddr);
-
-        bool emplacePassed = false;
-        while (!emplacePassed)
-        {
-            auto resp = deviceHashes.emplace(fruHash, newDev);
-            emplacePassed = resp.second;
-            if (!emplacePassed)
-            {
-                fruHash++;
-                // can't be 0xFF based on spec, and 0 is reserved for
-                // baseboard
-                if (fruHash == 0XFF)
-                {
-                    fruHash = 0x1;
-                }
-            }
+            deviceHashes.emplace(fruHash, std::make_pair(fruBus, fruAddr));
+            fruHash++;
         }
     }
     auto deviceFind = deviceHashes.find(devId);