ipmid: Improve fetch fru sdr delays

The GetManagedObject call is very heavy and it is called on each fru sdr
entry. Update the algorithm to use GetSubtreePaths isntead.

Tested:
Getting all sdr took 25 seconds to 12 seconds on system with a lot of
sensor + fru.

Fusion-Link:
- platform11
 - http://fusion2/28f7dd96-2096-3a73-90d1-3748b7975e4a
 - http://fusion2/48c96ca5-e333-399a-86ad-63a8dbf5a11f
- platform5: http://fusion2/c802786c-fa76-3b05-9e3c-7a3f07817f40
- platform15: http://fusion2/446b18d8-ca74-3352-b113-a3456ae3ff9b
- platform17: http://fusion2/13f92a2f-f08e-3436-8673-5ed66d0be0c9

Platforms-Affected: IPMI
Google-Bug-Id: 408304241
Google-Bug-Id: 411479972
Change-Id: Iff2b1f8ccd6cb3c481a6c9ac3709cda781c22dda
Signed-off-by: Willy Tu <wltu@google.com>
(cherry picked from commit 263704cbb46db2861c7fea5952083ea6590f20f7)
diff --git a/recipes-phosphor/ipmi/phosphor-ipmi-host/0009-Remove-GetManagedObject-call-for-fru-sdr.patch b/recipes-phosphor/ipmi/phosphor-ipmi-host/0009-Remove-GetManagedObject-call-for-fru-sdr.patch
new file mode 100644
index 0000000..29f70dc
--- /dev/null
+++ b/recipes-phosphor/ipmi/phosphor-ipmi-host/0009-Remove-GetManagedObject-call-for-fru-sdr.patch
@@ -0,0 +1,235 @@
+From 580ca1cb798dc5a479078f0fb51ea78429220d2e Mon Sep 17 00:00:00 2001
+From: Willy Tu <wltu@google.com>
+Date: Thu, 17 Apr 2025 21:19:14 +0000
+Subject: [PATCH] Remove GetManagedObject call for fru sdr
+
+Current algorithm will make a GetManagedObject against EntityManager for
+each Fru to get entityId/Instance information. Instead of doing that
+heavy call, we can just call GetSubtreePaths against ObjectManager to
+find objects with the dus interface that we want... and then call
+EntityManager to fetch the property if it they are the one generating
+it.
+
+Tested:
+Getting all sdr took 25 seconds to 12 seconds on system with a lot of
+sensor + fru.
+
+
+Patch Tracking Bug: b/411479972
+Upstream info / review: http://go/openbmccl/79480
+Upstream-Status: Submitted
+Justification: Will need more testing to sync to top of ipmid to pull in the patch normally.
+
+Change-Id: I7e7ec6f68fe6ee3bef6bd55948d853fba06ed695
+Signed-off-by: Willy Tu <wltu@google.com>
+---
+ dbus-sdr/storagecommands.cpp | 135 ++++++++++++++++++-----------------
+ 1 file changed, 68 insertions(+), 67 deletions(-)
+
+diff --git a/dbus-sdr/storagecommands.cpp b/dbus-sdr/storagecommands.cpp
+index a680743..cd5b098 100644
+--- a/dbus-sdr/storagecommands.cpp
++++ b/dbus-sdr/storagecommands.cpp
+@@ -25,6 +25,7 @@
+ #include <ipmid/api.hpp>
+ #include <ipmid/message.hpp>
+ #include <ipmid/types.hpp>
++#include <ipmid/utils.hpp>
+ #include <phosphor-logging/log.hpp>
+ #include <sdbusplus/message/types.hpp>
+ #include <sdbusplus/timer.hpp>
+@@ -33,6 +34,7 @@
+ #include <fstream>
+ #include <functional>
+ #include <iostream>
++#include <optional>
+ #include <stdexcept>
+ #include <string_view>
+ 
+@@ -79,6 +81,7 @@ using ObjectType =
+ using ManagedObjectType =
+     boost::container::flat_map<sdbusplus::message::object_path, ObjectType>;
+ using ManagedEntry = std::pair<sdbusplus::message::object_path, ObjectType>;
++using Paths = std::vector<std::string>;
+ 
+ constexpr static const char* selLoggerServiceName =
+     "xyz.openbmc_project.Logging.IPMI";
+@@ -593,73 +596,94 @@ ipmi_ret_t getFruSdrs([[maybe_unused]] ipmi::Context::ptr ctx, size_t index,
+         return IPMI_CC_RESPONSE_ERROR;
+     }
+     std::string name;
++    uint8_t entityID = 0;
++    uint8_t entityInstance = 0x1;
+ 
+ #ifdef USING_ENTITY_MANAGER_DECORATORS
+-
+-    boost::container::flat_map<std::string, Value>* entityData = nullptr;
+-
+-    // todo: this should really use caching, this is a very inefficient lookup
+     boost::system::error_code ec;
+ 
+-    ManagedObjectType entities = ctx->bus->yield_method_call<ManagedObjectType>(
+-        ctx->yield, ec, entityManagerServiceName,
+-        "/xyz/openbmc_project/inventory", "org.freedesktop.DBus.ObjectManager",
+-        "GetManagedObjects");
+-
++    Paths subtreePaths = ctx->bus->yield_method_call<Paths>(
++        ctx->yield, ec, "xyz.openbmc_project.ObjectMapper",
++        "/xyz/openbmc_project/object_mapper",
++        "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths",
++        "/xyz/openbmc_project/inventory", 0,
++        std::array<const char*, 2>{
++            "xyz.openbmc_project.Inventory.Decorator.I2CDevice",
++            "xyz.openbmc_project.Inventory.Decorator.Ipmi",
++        });
+     if (ec)
+     {
+-        phosphor::logging::log<phosphor::logging::level::ERR>(
+-            "GetMangagedObjects for ipmiStorageGetFruInvAreaInfo failed",
+-            phosphor::logging::entry("ERROR=%s", ec.message().c_str()));
+-
++        std::fprintf(
++            stderr,
++            "GetSubTreePaths for ipmiStorageGetFruInvAreaInfo failed: %s\n",
++            ec.message().c_str());
+         return ipmi::ccResponseError;
+     }
+ 
+-    auto entity =
+-        std::find_if(entities.begin(), entities.end(),
+-                     [bus, address, &entityData, &name](ManagedEntry& entry) {
+-        auto findFruDevice = entry.second.find(
+-            "xyz.openbmc_project.Inventory.Decorator.I2CDevice");
+-        if (findFruDevice == entry.second.end())
++    bool foundEntity = false;
++    for (const auto& path : subtreePaths)
++    {
++        ipmi::PropertyMap i2cProperties;
++        boost::system::error_code ec = ipmi::getAllDbusProperties(
++            ctx, "xyz.openbmc_project.EntityManager", path,
++            "xyz.openbmc_project.Inventory.Decorator.I2CDevice", i2cProperties);
++        if (ec)
+         {
+-            return false;
++            continue;
+         }
+ 
+-        // Integer fields added via Entity-Manager json are uint64_ts by
+-        // default.
+-        auto findBus = findFruDevice->second.find("Bus");
+-        auto findAddress = findFruDevice->second.find("Address");
+-
+-        if (findBus == findFruDevice->second.end() ||
+-            findAddress == findFruDevice->second.end())
++        std::optional<uint64_t> maybeBus;
++        std::optional<uint64_t> maybeAddress;
++        std::optional<std::string> maybeName;
++        for (const auto& [key, val] : i2cProperties)
+         {
+-            return false;
++            if (key == "Bus")
++            {
++                maybeBus = std::get<uint64_t>(val);
++            }
++            else if (key == "Address")
++            {
++                maybeAddress = std::get<uint64_t>(val);
++            }
++            else if (key == "Name")
++            {
++                maybeName = std::get<std::string>(val);
++            }
+         }
+-        if ((std::get<uint64_t>(findBus->second) != bus) ||
+-            (std::get<uint64_t>(findAddress->second) != address))
++        if (!maybeBus || *maybeBus != bus || !maybeAddress ||
++            *maybeAddress != address)
+         {
+-            return false;
++            continue;
+         }
+ 
+-        auto fruName = findFruDevice->second.find("Name");
+-        if (fruName != findFruDevice->second.end())
++        if (maybeName.has_value())
+         {
+-            name = std::get<std::string>(fruName->second);
++            name = *maybeName;
+         }
+ 
+-        // At this point we found the device entry and should return
+-        // true.
+-        auto findIpmiDevice =
+-            entry.second.find("xyz.openbmc_project.Inventory.Decorator.Ipmi");
+-        if (findIpmiDevice != entry.second.end())
++        ipmi::PropertyMap entityData;
++        ec = ipmi::getAllDbusProperties(
++            ctx, "xyz.openbmc_project.EntityManager", path,
++            "xyz.openbmc_project.Inventory.Decorator.Ipmi", entityData);
++        if (!ec)
+         {
+-            entityData = &(findIpmiDevice->second);
++            for (const auto& [key, val] : entityData)
++            {
++                if (key == "EntityId")
++                {
++                    entityID = std::get<uint64_t>(val);
++                }
++                else if (key == "EntityInstance")
++                {
++                    entityInstance = std::get<uint64_t>(val);
++                }
++            }
+         }
++        foundEntity = true;
++        break;
++    }
+ 
+-        return true;
+-    });
+-
+-    if (entity == entities.end())
++    if (!foundEntity)
+     {
+         if constexpr (DEBUG)
+         {
+@@ -667,7 +691,6 @@ ipmi_ret_t getFruSdrs([[maybe_unused]] ipmi::Context::ptr ctx, size_t index,
+                                  "not found for Fru\n");
+         }
+     }
+-
+ #endif
+ 
+ #ifdef USING_FRU_DEVICE_PROPERTY_OVERRIDE_FRU_NAME
+@@ -710,28 +733,6 @@ ipmi_ret_t getFruSdrs([[maybe_unused]] ipmi::Context::ptr ctx, size_t index,
+     resp.body.deviceType = 0x10;
+     resp.body.deviceTypeModifier = 0x0;
+ 
+-    uint8_t entityID = 0;
+-    uint8_t entityInstance = 0x1;
+-
+-#ifdef USING_ENTITY_MANAGER_DECORATORS
+-    if (entityData)
+-    {
+-        auto entityIdProperty = entityData->find("EntityId");
+-        auto entityInstanceProperty = entityData->find("EntityInstance");
+-
+-        if (entityIdProperty != entityData->end())
+-        {
+-            entityID = static_cast<uint8_t>(
+-                std::get<uint64_t>(entityIdProperty->second));
+-        }
+-        if (entityInstanceProperty != entityData->end())
+-        {
+-            entityInstance = static_cast<uint8_t>(
+-                std::get<uint64_t>(entityInstanceProperty->second));
+-        }
+-    }
+-#endif
+-
+     resp.body.entityID = entityID;
+     resp.body.entityInstance = entityInstance;
+ 
+-- 
+2.49.0.805.g082f7c87e0-goog
+
diff --git a/recipes-phosphor/ipmi/phosphor-ipmi-host_%.bbappend b/recipes-phosphor/ipmi/phosphor-ipmi-host_%.bbappend
index d157e08..499de8a 100644
--- a/recipes-phosphor/ipmi/phosphor-ipmi-host_%.bbappend
+++ b/recipes-phosphor/ipmi/phosphor-ipmi-host_%.bbappend
@@ -11,6 +11,7 @@
   file://0006-dbus-sdr-fix-write-permission-for-external-sen.patch \
   file://0007-dbus-sdr-Add-limited-log-on-sensorTree-Resets.patch \
   file://0008-user_mgmt-Recover-corrupted-nv-files.patch \
+  file://0009-Remove-GetManagedObject-call-for-fru-sdr.patch \
 "
 
 EXTRA_OEMESON:append:gbmc = " -Dfru-device-property-override-fru-name=disabled"